///|
/// Half-open range in the original file, never a runtime address.
pub struct ImportByteRange {
  offset : Int
  size : Int
} derive(Eq, Debug)

///|
/// Supported 32-bit limits only. Values retain the full unsigned range.
pub struct ImportLimits {
  flags : Int
  minimum : UInt
  maximum : UInt?
} derive(Eq, Debug)

///|
pub struct ImportDeclaration {
  ordinal : Int
  kind : String
  kind_index : Int
  module_name : String
  field : String
  encoding : ImportByteRange
  module_encoding : ImportByteRange
  field_encoding : ImportByteRange
  /// Includes the kind byte, not just its following type encoding.
  descriptor : ImportByteRange
  module_length_prefix_bytes : Int
  module_utf8_bytes : Int
  field_length_prefix_bytes : Int
  field_utf8_bytes : Int
  /// A declaration, not a validated type referent or a decoded signature.
  type_index : UInt?
  limits : ImportLimits?
  value_type : String?
  mutability : Int?
}

///|
pub struct ImportKindCounts {
  functions : Int
  tables : Int
  memories : Int
  globals : Int
  tags : Int
} derive(Eq, Debug)

///|
pub struct ImportModule {
  module_name : String
  entry_count : Int
  kind_counts : ImportKindCounts
  entry_bytes : Int
  ordinals : Array[Int]
}

///|
/// Parent and child totals are decompositions, not additional costs.
pub struct ImportsLedger {
  import_section_total_bytes : Int
  section_header_bytes : Int
  count_prefix_bytes : Int
  entry_bytes : Int
  module_name_encoding_bytes : Int
  module_length_prefix_bytes : Int
  module_utf8_bytes : Int
  field_name_encoding_bytes : Int
  field_length_prefix_bytes : Int
  field_utf8_bytes : Int
  descriptor_bytes : Int
} derive(Eq, Debug)

///|
pub struct ImportsReport {
  file_size : Int
  sha256 : String
  supported_scope : String
  section : ImportByteRange?
  count_encoding : ImportByteRange?
  entry_count : Int
  kind_counts : ImportKindCounts
  ledger : ImportsLedger
  /// Sorted by declaration bytes descending, full module name ascending.
  modules : Array[ImportModule]
  /// Complete physical declaration order; text sorting does not change this.
  entries : Array[ImportDeclaration]
}

///|
fn imports_name(
  reader : Reader,
  label : String,
) -> Result[(String, ImportByteRange, Int, Int), WasmError] {
  let start = reader.position()
  let size = match read_u32_as_int(reader) {
    Ok(n) => n
    Err(e) => return Err(e)
  }
  let prefix = reader.position() - start
  let name_start = reader.position()
  let bytes = match reader.read_bytes(size) {
    Ok(b) => b
    Err(e) => return Err(e)
  }
  let value = @utf8.decode(bytes) catch {
    _ =>
      return Err(
        InvalidImportEncoding(
          name_start,
          "invalid UTF-8 in import " + label + " name starting here",
        ),
      )
  }
  Ok((value, { offset: start, size: reader.position() - start, }, prefix, size))
}

///|
fn imports_limits(
  reader : Reader,
  table : Bool,
) -> Result[ImportLimits, WasmError] {
  let offset = reader.position()
  let flags = match reader.read_byte() {
    Ok(n) => n
    Err(e) => return Err(e)
  }
  let label = if table { "table" } else { "memory" }
  // Check width and supported forms BEFORE reading either bound. Never reuse
  // the legacy low-bit skipper for memory64/table64 or future flag shapes.
  if (flags & 4) != 0 {
    return Err(
      InvalidImportEncoding(
        offset,
        "unsupported " + label + "64 limits flags " + flags.to_string(),
      ),
    )
  }
  if !table && flags == 2 {
    return Err(
      InvalidImportEncoding(offset, "shared memory limits require a maximum"),
    )
  }
  if flags != 0 && flags != 1 && (table || flags != 3) {
    return Err(
      InvalidImportEncoding(
        offset,
        "unsupported " + label + " limits flags " + flags.to_string(),
      ),
    )
  }
  let minimum = match reader.read_u32_leb() {
    Ok(n) => n
    Err(e) => return Err(e)
  }
  let maximum = if flags == 1 || flags == 3 {
    let value = match reader.read_u32_leb() {
      Ok(n) => n
      Err(e) => return Err(e)
    }
    if minimum > value {
      return Err(
        InvalidImportEncoding(offset, label + " minimum exceeds maximum"),
      )
    }
    Some(value)
  } else {
    None
  }
  Ok({ flags, minimum, maximum, })
}

///|
fn imports_value_type(
  reader : Reader,
  table : Bool,
) -> Result[String, WasmError] {
  let offset = reader.position()
  let byte = match reader.read_byte() {
    Ok(n) => n
    Err(e) => return Err(e)
  }
  match byte {
    0x70 => Ok("funcref")
    0x6f => Ok("externref")
    0x7f if !table => Ok("i32")
    0x7e if !table => Ok("i64")
    0x7d if !table => Ok("f32")
    0x7c if !table => Ok("f64")
    0x7b if !table => Ok("v128")
    _ =>
      Err(
        InvalidImportEncoding(
          offset,
          "unsupported " +
          (if table { "table reference" } else { "global value" }) +
          " type encoding 0x" +
          byte.to_string(radix=16),
        ),
      )
  }
}

///|
fn imports_descriptor(
  reader : Reader,
) -> Result[(Int, UInt?, ImportLimits?, String?, Int?), WasmError] {
  let offset = reader.position()
  let kind = match reader.read_byte() {
    Ok(n) => n
    Err(e) => return Err(e)
  }
  match kind {
    0 => {
      let index = match reader.read_u32_leb() {
        Ok(n) => n
        Err(e) => return Err(e)
      }
      Ok((kind, Some(index), None, None, None))
    }
    1 => {
      let value_type = match imports_value_type(reader, true) {
        Ok(n) => n
        Err(e) => return Err(e)
      }
      let limits = match imports_limits(reader, true) {
        Ok(n) => n
        Err(e) => return Err(e)
      }
      Ok((kind, None, Some(limits), Some(value_type), None))
    }
    2 => {
      let limits = match imports_limits(reader, false) {
        Ok(n) => n
        Err(e) => return Err(e)
      }
      Ok((kind, None, Some(limits), None, None))
    }
    3 => {
      let value_type = match imports_value_type(reader, false) {
        Ok(n) => n
        Err(e) => return Err(e)
      }
      let at = reader.position()
      let mutability = match reader.read_byte() {
        Ok(n) => n
        Err(e) => return Err(e)
      }
      if mutability != 0 && mutability != 1 {
        return Err(
          InvalidImportEncoding(
            at,
            "invalid global mutability " +
            mutability.to_string() +
            " (expected 0 or 1)",
          ),
        )
      }
      Ok((kind, None, None, Some(value_type), Some(mutability)))
    }
    4 => {
      let at = reader.position()
      let attribute = match reader.read_byte() {
        Ok(n) => n
        Err(e) => return Err(e)
      }
      if attribute != 0 {
        return Err(
          InvalidImportEncoding(
            at,
            "unsupported tag attribute " +
            attribute.to_string() +
            " (expected 0)",
          ),
        )
      }
      let index = match reader.read_u32_leb() {
        Ok(n) => n
        Err(e) => return Err(e)
      }
      Ok((kind, Some(index), None, None, None))
    }
    _ =>
      Err(
        InvalidImportEncoding(
          offset,
          "unknown import kind " +
          kind.to_string() +
          " (supported kinds: 0..4); descriptor cannot be skipped",
        ),
      )
  }
}

///|
fn imports_kind_name(kind : Int) -> String {
  ["function", "table", "memory", "global", "tag"][kind]
}

///|
fn imports_counts(counts : Array[Int]) -> ImportKindCounts {
  {
    functions: counts[0],
    tables: counts[1],
    memories: counts[2],
    globals: counts[3],
    tags: counts[4],
  }
}

///|
fn imports_scope() -> String {
  "Import declaration encoding bytes, not host implementation or dependency package sizes. " +
  "Supported: function u32 type index; table funcref/externref with 32-bit limits flags 0/1; memory 32-bit flags 0/1/3 (shared requires maximum); " +
  "global i32/i64/f32/f64/v128/funcref/externref with mutability 0/1; tag attribute 0 and u32 type index. " +
  "Typed references, other GC forms, memory64/table64 and other flags are unsupported. " +
  "Declared type indices are not validated and signatures are not decoded. Names are exact UTF-8 strings, not interpreted package names, paths, URLs, permissions or environment compatibility claims. " +
  "Declarations do not establish runtime calls or permissions used. No removal, recompiled or transfer savings are inferred. " +
  "All ranges are original file offsets, not runtime addresses; parent/child totals are decompositions. Top limits text entries only; summaries and JSON remain complete."
}

///|
/// Inspect core import declarations only after the ordinary module checks.
/// No analyze, graph, function inventory or optional-content partial success.
pub fn inspect_imports(data : Bytes) -> Result[ImportsReport, WasmError] {
  let parsed = match parse_module(data) {
    Ok(m) => m
    Err(e) => return Err(e)
  }
  let entries : Array[ImportDeclaration] = []
  let counts = [0, 0, 0, 0, 0]
  let by_module : Map[String, Array[Int]] = Map([])
  let mut section : ImportByteRange? = None
  let mut count_encoding : ImportByteRange? = None
  let mut section_total = 0
  let mut header_bytes = 0
  for item in parsed.sections {
    if !same_kind(item.kind, Import) {
      continue
    }
    let reader = match reader_at_payload(data, item) {
      Ok(r) => r
      Err(e) => return Err(e)
    }
    section = Some({ offset: item.offset, size: item.total_size, })
    section_total = item.total_size
    header_bytes = item.total_size - item.payload_size
    let count_start = reader.position()
    let mut remaining = match reader.read_u32_leb() {
      Ok(n) => n
      Err(e) => return Err(e)
    }
    count_encoding = Some({
      offset: count_start,
      size: reader.position() - count_start,
    })
    while remaining > 0U {
      let start = reader.position()
      let (module_name, module_encoding, module_prefix, module_utf8) = match
        imports_name(reader, "module") {
        Ok(n) => n
        Err(e) => return Err(e)
      }
      let (field, field_encoding, field_prefix, field_utf8) = match
        imports_name(reader, "field") {
        Ok(n) => n
        Err(e) => return Err(e)
      }
      let descriptor_start = reader.position()
      let (kind, type_index, limits, value_type, mutability) = match
        imports_descriptor(reader) {
        Ok(n) => n
        Err(e) => return Err(e)
      }
      let ordinal = entries.length()
      entries.push({
        ordinal,
        kind: imports_kind_name(kind),
        kind_index: counts[kind],
        module_name,
        field,
        encoding: { offset: start, size: reader.position() - start, },
        module_encoding,
        field_encoding,
        descriptor: {
          offset: descriptor_start,
          size: reader.position() - descriptor_start,
        },
        module_length_prefix_bytes: module_prefix,
        module_utf8_bytes: module_utf8,
        field_length_prefix_bytes: field_prefix,
        field_utf8_bytes: field_utf8,
        type_index,
        limits,
        value_type,
        mutability,
      })
      counts[kind] += 1
      let ordinals = by_module.get(module_name).unwrap_or([])
      ordinals.push(ordinal)
      by_module.set(module_name, ordinals)
      remaining -= 1U
    }
    match finish_section(reader, Import) {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
  }
  let modules : Array[ImportModule] = []
  for name, ordinals in by_module {
    let totals = [0, 0, 0, 0, 0]
    let mut bytes = 0
    for ordinal in ordinals {
      let entry = entries[ordinal]
      bytes += entry.encoding.size
      for kind in 0..<5 {
        if entry.kind == imports_kind_name(kind) {
          totals[kind] += 1
        }
      }
    }
    modules.push({
      module_name: name,
      entry_count: ordinals.length(),
      kind_counts: imports_counts(totals),
      entry_bytes: bytes,
      ordinals,
    })
  }
  modules.sort_by(fn(a, b) {
    let order = b.entry_bytes.compare(a.entry_bytes)
    if order == 0 {
      a.module_name.compare(b.module_name)
    } else {
      order
    }
  })
  let mut entry_bytes = 0
  let mut module_prefix = 0
  let mut module_utf8 = 0
  let mut field_prefix = 0
  let mut field_utf8 = 0
  let mut descriptor_bytes = 0
  for entry in entries {
    entry_bytes += entry.encoding.size
    module_prefix += entry.module_length_prefix_bytes
    module_utf8 += entry.module_utf8_bytes
    field_prefix += entry.field_length_prefix_bytes
    field_utf8 += entry.field_utf8_bytes
    descriptor_bytes += entry.descriptor.size
  }
  Ok({
    file_size: data.length(),
    sha256: experiment_sha256(data),
    supported_scope: imports_scope(),
    section,
    count_encoding,
    entry_count: entries.length(),
    kind_counts: imports_counts(counts),
    modules,
    entries,
    ledger: {
      import_section_total_bytes: section_total,
      section_header_bytes: header_bytes,
      count_prefix_bytes: count_encoding.map(fn(r) { r.size }).unwrap_or(0),
      entry_bytes,
      module_name_encoding_bytes: module_prefix + module_utf8,
      module_length_prefix_bytes: module_prefix,
      module_utf8_bytes: module_utf8,
      field_name_encoding_bytes: field_prefix + field_utf8,
      field_length_prefix_bytes: field_prefix,
      field_utf8_bytes: field_utf8,
      descriptor_bytes,
    },
  })
}