///|
/// 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.

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

///|
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))
  }
}

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

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

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

///|
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 : Int32
}

///|
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) -> Int32 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_view(
  self : Reader,
  n : Int32,
) -> BytesView raise ComponentParseError {
  let start = self.pos
  if n < 0 || n > self.data.length() - start {
    raise UnexpectedEndOfInput
  }
  self.pos += n
  self.data[start:self.pos]
}

///|
fn Reader::read_bytes(
  self : Reader,
  n : Int32,
) -> Bytes raise ComponentParseError {
  self.read_view(n).to_owned()
}

///|
fn Reader::read_blob(self : Reader) -> Bytes raise ComponentParseError {
  self.read_bytes(self.read_leb_u32())
}

///|
fn Reader::read_name(self : Reader) -> String raise ComponentParseError {
  let bytes = self.read_view(self.read_leb_u32())
  @utf8.decode(bytes, ignore_bom=false) catch {
    _ => raise InvalidUtf8
  }
}

///|
fn Reader::read_count(self : Reader) -> Int32 raise ComponentParseError {
  let count = self.read_leb_u32()
  if count < 0 {
    raise UnexpectedEndOfInput
  }
  count
}

///|
fn[A] Reader::read_vector(
  self : Reader,
  read_entry : (Reader) -> A raise ComponentParseError,
) -> Array[A] raise ComponentParseError {
  let count = self.read_count()
  let entries = []
  for _ in 0.. A raise ComponentParseError,
) -> A? raise ComponentParseError {
  match self.read_u8() {
    0 => None
    1 => Some(read_entry(self))
    _ => raise InvalidTypeSection
  }
}

///|
fn[A] parse_vector_section(
  payload : Bytes,
  read_entry : (Reader) -> A raise ComponentParseError,
  invalid : ComponentParseError,
) -> Array[A] raise ComponentParseError {
  let reader = Reader(payload)
  let entries = reader.read_vector(read_entry)
  if !reader.is_eof() {
    raise invalid
  }
  entries
}

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

///|
fn Reader::read_leb_u32(self : Reader) -> Int32 raise ComponentParseError {
  @binary.read_u32(self, Reader::read_u8, () => Leb128TooLarge)
}

///|
fn Reader::read_s33(self : Reader) -> Int64 raise ComponentParseError {
  @binary.read_s33(self, Reader::read_u8, () => Leb128TooLarge)
}

///|
fn Reader::read_typeidx(self : Reader) -> Int32 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.pos = 4
  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, }
}