///|
/// Minimal helpers for recognizing WebAssembly Component Model binaries.
///
/// For the first incremental step, we only sniff the standard wasm magic plus
/// a known version number to distinguish core modules vs components.

///|
pub(all) enum BinaryKind {
  CoreModule
  Component
  UnknownPreamble(Int, Int)
} derive(Debug, Eq)

///|
pub impl Show for BinaryKind with fn output(self, logger) {
  logger.write_string(@types.compact_show_repr(Repr(self).to_string()))
}

///|
pub fn sniff_binary_kind(bytes : Bytes) -> BinaryKind? {
  if bytes.length() < 8 {
    return None
  }
  if bytes[0].to_int() != 0x00 ||
    bytes[1].to_int() != 0x61 ||
    bytes[2].to_int() != 0x73 ||
    bytes[3].to_int() != 0x6d {
    return None
  }
  // Component Model splits the legacy 32-bit "version" field into:
  // - version: u16
  // - layer: u16 (0 = core module, 1 = component)
  let version = bytes[4].to_int() | (bytes[5].to_int() << 8)
  let layer = bytes[6].to_int() | (bytes[7].to_int() << 8)
  if layer == 0 && version == 0x01 {
    Some(CoreModule)
  } else if layer == 1 && version == 0x0D {
    Some(Component)
  } else {
    Some(UnknownPreamble(version, layer))
  }
}

///|
pub struct Section {
  id : Int
  payload : Bytes
} derive(Debug, Eq)

///|
pub struct ComponentBinary {
  version : Int
  layer : Int
  sections : Array[Section]
} derive(Debug, Eq)

///|
pub suberror ComponentParseError {
  InvalidMagic
  UnexpectedEndOfInput
  Leb128TooLarge
  NotAComponent
  UnsupportedComponentPreamble(Int, Int)
  InvalidTypeSection
  InvalidInstanceSection
  InvalidAliasSection
  InvalidCanonSection
  InvalidStartSection
  UnsupportedCanonOpcode(Int)
  InvalidUtf8
  UnsupportedTypeOpcode(Int)
}

///|
pub fn ComponentParseError::to_string(self : ComponentParseError) -> String {
  match self {
    InvalidMagic => "invalid wasm magic"
    UnexpectedEndOfInput => "unexpected end of input"
    Leb128TooLarge => "leb128 value too large"
    NotAComponent => "input is not a component binary"
    UnsupportedComponentPreamble(version, layer) =>
      "unsupported component preamble: version=\{version} layer=\{layer}"
    InvalidTypeSection => "invalid type section"
    InvalidInstanceSection => "invalid instance section"
    InvalidAliasSection => "invalid alias section"
    InvalidCanonSection => "invalid canon section"
    InvalidStartSection => "invalid start section"
    UnsupportedCanonOpcode(op) => "unsupported canon opcode: \{op}"
    InvalidUtf8 => "invalid utf-8"
    UnsupportedTypeOpcode(op) => "unsupported component type opcode: \{op}"
  }
}

///|
pub impl Show for ComponentParseError with fn output(self, logger) {
  logger.write_string(self.to_string())
}

///|
priv struct Reader {
  data : Bytes
  mut pos : Int
}

///|
fn Reader::Reader(data : Bytes) -> Reader {
  { data, pos: 0, }
}

///|
fn Reader::is_eof(self : Reader) -> Bool {
  self.pos >= self.data.length()
}

///|
fn Reader::read_u8(self : Reader) -> Int raise ComponentParseError {
  if self.pos >= self.data.length() {
    raise UnexpectedEndOfInput
  }
  let b = self.data[self.pos].to_int()
  self.pos += 1
  b
}

///|
fn Reader::read_bytes(
  self : Reader,
  n : Int,
) -> Bytes raise ComponentParseError {
  if self.pos + n > self.data.length() {
    raise UnexpectedEndOfInput
  }
  let bytes : Array[Byte] = []
  for i in 0.. String raise ComponentParseError {
  let buf = StringBuilder::StringBuilder()
  let mut i = 0
  while i < bytes.length() {
    let b = bytes[i].to_int()
    if b < 0x80 {
      buf.write_char(b.unsafe_to_char())
      i = i + 1
    } else if b < 0xC0 {
      raise InvalidUtf8
    } else if b < 0xE0 {
      if i + 1 >= bytes.length() {
        raise InvalidUtf8
      }
      let b2 = bytes[i + 1].to_int()
      if (b2 & 0xC0) != 0x80 {
        raise InvalidUtf8
      }
      let cp = ((b & 0x1F) << 6) | (b2 & 0x3F)
      if cp < 0x80 {
        raise InvalidUtf8
      }
      buf.write_char(cp.unsafe_to_char())
      i = i + 2
    } else if b < 0xF0 {
      if i + 2 >= bytes.length() {
        raise InvalidUtf8
      }
      let b2 = bytes[i + 1].to_int()
      let b3 = bytes[i + 2].to_int()
      if (b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80 {
        raise InvalidUtf8
      }
      let cp = ((b & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F)
      if cp < 0x800 || (cp >= 0xD800 && cp <= 0xDFFF) {
        raise InvalidUtf8
      }
      buf.write_char(cp.unsafe_to_char())
      i = i + 3
    } else if b < 0xF8 {
      if i + 3 >= bytes.length() {
        raise InvalidUtf8
      }
      let b2 = bytes[i + 1].to_int()
      let b3 = bytes[i + 2].to_int()
      let b4 = bytes[i + 3].to_int()
      if (b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80 || (b4 & 0xC0) != 0x80 {
        raise InvalidUtf8
      }
      let cp = ((b & 0x07) << 18) |
        ((b2 & 0x3F) << 12) |
        ((b3 & 0x3F) << 6) |
        (b4 & 0x3F)
      if cp < 0x10000 || cp > 0x10FFFF {
        raise InvalidUtf8
      }
      buf.write_char(cp.unsafe_to_char())
      i = i + 4
    } else {
      raise InvalidUtf8
    }
  }
  buf.to_string()
}

///|
fn Reader::read_name(self : Reader) -> String raise ComponentParseError {
  let len = self.read_leb_u32()
  let bytes = self.read_bytes(len)
  decode_utf8_validated(bytes)
}

///|
fn Reader::read_u16_le(self : Reader) -> Int raise ComponentParseError {
  let b0 = self.read_u8()
  let b1 = self.read_u8()
  b0 | (b1 << 8)
}

///|
fn Reader::read_leb_u32(self : Reader) -> Int raise ComponentParseError {
  for result = 0, shift = 0 {
    if shift > 28 {
      raise Leb128TooLarge
    }
    let byte = self.read_u8()
    if shift == 28 && (byte & 0x7F) > 0x0F {
      raise Leb128TooLarge
    }
    let new_result = result | ((byte & 0x7F) << shift)
    if (byte & 0x80) == 0 {
      break new_result
    } else {
      continue new_result, shift + 7
    }
  }
}

///|
fn Reader::read_sleb_i32(self : Reader) -> Int raise ComponentParseError {
  for result = 0, shift = 0 {
    if shift >= 35 {
      raise Leb128TooLarge
    }
    let b = self.read_u8()
    let low = b & 0x7F
    let new_result = result | (low << shift)
    if (b & 0x80) == 0 {
      if shift < 32 && (b & 0x40) != 0 {
        break new_result | (-1 << (shift + 7))
      } else {
        break new_result
      }
    } else {
      continue new_result, shift + 7
    }
  }
}

///|
fn Reader::read_typeidx(self : Reader) -> Int raise ComponentParseError {
  self.read_leb_u32()
}

///|
pub fn parse_component_binary(
  bytes : Bytes,
) -> ComponentBinary raise ComponentParseError {
  if bytes.length() < 8 {
    raise UnexpectedEndOfInput
  }
  if bytes[0].to_int() != 0x00 ||
    bytes[1].to_int() != 0x61 ||
    bytes[2].to_int() != 0x73 ||
    bytes[3].to_int() != 0x6d {
    raise InvalidMagic
  }
  let reader = Reader::Reader(bytes)
  // Skip magic.
  reader.read_bytes(4) |> ignore
  let version = reader.read_u16_le()
  let layer = reader.read_u16_le()
  if layer != 1 {
    raise NotAComponent
  }
  if version != 0x0D {
    raise UnsupportedComponentPreamble(version, layer)
  }
  // Section framing: id (u8), size (leb u32), payload (bytes).
  let sections : Array[Section] = []
  while !reader.is_eof() {
    let id = reader.read_u8()
    let size = reader.read_leb_u32()
    let payload = reader.read_bytes(size)
    sections.push({ id, payload, })
  }
  { version, layer, sections, }
}