///| WebAssembly module model. The model deliberately keeps raw section spans:

///| callers can inspect a module cheaply and opt in to detailed decoding only

///|
/// for the sections they need.
pub enum SectionKind {
  Custom
  Type
  Import
  Function
  Table
  Memory
  Global
  Export
  Start
  Element
  Code
  Data
  DataCount
  Tag
} derive(Eq)

///|
pub fn SectionKind::id(self : SectionKind) -> Int {
  match self {
    Custom => 0
    Type => 1
    Import => 2
    Function => 3
    Table => 4
    Memory => 5
    Global => 6
    Export => 7
    Start => 8
    Element => 9
    Code => 10
    Data => 11
    DataCount => 12
    Tag => 13
  }
}

///|
pub fn SectionKind::label(self : SectionKind) -> String {
  match self {
    Custom => "custom"
    Type => "type"
    Import => "import"
    Function => "function"
    Table => "table"
    Memory => "memory"
    Global => "global"
    Export => "export"
    Start => "start"
    Element => "element"
    Code => "code"
    Data => "data"
    DataCount => "data-count"
    Tag => "tag"
  }
}

///|
pub fn section_kind_from_id(id : Int) -> SectionKind? {
  match id {
    0 => Some(Custom)
    1 => Some(Type)
    2 => Some(Import)
    3 => Some(Function)
    4 => Some(Table)
    5 => Some(Memory)
    6 => Some(Global)
    7 => Some(Export)
    8 => Some(Start)
    9 => Some(Element)
    10 => Some(Code)
    11 => Some(Data)
    12 => Some(DataCount)
    13 => Some(Tag)
    _ => None
  }
}

///|
pub struct Section {
  id : Int
  kind : SectionKind
  payload : Span
  full : Span
  custom_name : String?
}

///|
pub fn Section::payload_size(self : Section) -> Int {
  self.payload.size()
}

///|
pub fn Section::is_named(self : Section, name : String) -> Bool {
  match self.custom_name {
    Some(value) => value == name
    None => false
  }
}

///|
pub struct Module {
  bytes : Array[Int]
  sections : Array[Section]
  version : Int
}

///|
pub fn Module::version(self : Module) -> Int {
  self.version
}

///|
pub fn Module::byte_length(self : Module) -> Int {
  self.bytes.length()
}

///|
pub fn Module::sections(self : Module) -> Array[Section] {
  self.sections
}

///|
pub fn Module::section_count(self : Module) -> Int {
  self.sections.length()
}

///|
pub fn Module::find_section(self : Module, kind : SectionKind) -> Section? {
  for section in self.sections {
    if section.kind == kind {
      return Some(section)
    }
  }
  None
}

///|
pub fn Module::custom_sections(self : Module, name : String) -> Array[Section] {
  let found : Array[Section] = []
  for section in self.sections {
    if section.is_named(name) {
      found.push(section)
    }
  }
  found
}

///|
pub fn Module::section_bytes(self : Module, section : Section) -> Array[Int] {
  let output : Array[Int] = []
  let mut position = section.payload.start
  while position < section.payload.end {
    output.push(self.bytes[position])
    position = position + 1
  }
  output
}

///|
pub enum ValueType {
  I32
  I64
  F32
  F64
  V128
  FuncRef
  ExternRef
}

///|
pub fn ValueType::label(self : ValueType) -> String {
  match self {
    I32 => "i32"
    I64 => "i64"
    F32 => "f32"
    F64 => "f64"
    V128 => "v128"
    FuncRef => "funcref"
    ExternRef => "externref"
  }
}

///|
fn value_type_from_byte(byte : Int) -> ValueType? {
  match byte {
    0x7f => Some(I32)
    0x7e => Some(I64)
    0x7d => Some(F32)
    0x7c => Some(F64)
    0x7b => Some(V128)
    0x70 => Some(FuncRef)
    0x6f => Some(ExternRef)
    _ => None
  }
}

///|
pub struct FunctionType {
  params : Array[ValueType]
  results : Array[ValueType]
}

///|
pub fn FunctionType::params(self : FunctionType) -> Array[ValueType] {
  self.params
}

///|
pub fn FunctionType::results(self : FunctionType) -> Array[ValueType] {
  self.results
}

///|
pub fn FunctionType::signature(self : FunctionType) -> String {
  let mut text = "("
  let mut first = true
  for value in self.params {
    if !first {
      text = text + ", "
    }
    text = text + value.label()
    first = false
  }
  text = text + ") -> ("
  first = true
  for value in self.results {
    if !first {
      text = text + ", "
    }
    text = text + value.label()
    first = false
  }
  text + ")"
}

///|
pub enum ExternalKind {
  FunctionExternal
  TableExternal
  MemoryExternal
  GlobalExternal
  TagExternal
} derive(Eq)

///|
pub fn ExternalKind::label(self : ExternalKind) -> String {
  match self {
    FunctionExternal => "function"
    TableExternal => "table"
    MemoryExternal => "memory"
    GlobalExternal => "global"
    TagExternal => "tag"
  }
}

///|
pub struct Import {
  module_name : String
  name : String
  kind : ExternalKind
  type_index : Int?
}

///|
pub struct Export {
  name : String
  kind : ExternalKind
  index : Int
}

///|
pub struct Limits {
  minimum : Int
  maximum : Int?
  shared : Bool
  memory64 : Bool
}

///|
pub fn Limits::description(self : Limits) -> String {
  let text = "min=" + self.minimum.to_string()
  match self.maximum {
    Some(maximum) => text + ", max=" + maximum.to_string()
    None => text + ", max=unbounded"
  }
}

///|
pub struct TableInfo {
  element_type : ValueType
  limits : Limits
}

///|
pub struct MemoryInfo {
  limits : Limits
}

///|
pub struct GlobalInfo {
  value_type : ValueType
  mutable : Bool
  init_span : Span
}

///|
pub struct TagInfo {
  type_index : Int
}

///|
pub struct CodeBody {
  locals : Array[ValueType]
  instruction_span : Span
  byte_length : Int
}

///|
pub struct DecodedModule {
  binary : Module
  types : Array[FunctionType]
  imports : Array[Import]
  function_types : Array[Int]
  tables : Array[TableInfo]
  memories : Array[MemoryInfo]
  globals : Array[GlobalInfo]
  exports : Array[Export]
  start_function : Int?
  code_bodies : Array[CodeBody]
  element_segments : Array[ElementSegment]
  data_segments : Array[DataSegment]
  data_count : Int?
  tags : Array[TagInfo]
  warnings : Array[String]
}

///|
pub fn DecodedModule::binary(self : DecodedModule) -> Module {
  self.binary
}

///|
pub fn DecodedModule::types(self : DecodedModule) -> Array[FunctionType] {
  self.types
}

///|
pub fn DecodedModule::imports(self : DecodedModule) -> Array[Import] {
  self.imports
}

///|
pub fn DecodedModule::exports(self : DecodedModule) -> Array[Export] {
  self.exports
}

///|
pub fn DecodedModule::warnings(self : DecodedModule) -> Array[String] {
  self.warnings
}

///|
pub fn DecodedModule::defined_function_count(self : DecodedModule) -> Int {
  self.function_types.length()
}

///|
pub fn DecodedModule::defined_tag_count(self : DecodedModule) -> Int {
  self.tags.length()
}

///|
pub fn DecodedModule::imported_function_count(self : DecodedModule) -> Int {
  let mut count = 0
  for item in self.imports {
    if item.kind == FunctionExternal {
      count = count + 1
    }
  }
  count
}