///|
/// A WASM number type of a host import or an export.
#derive(Eq, Debug)
pub(all) enum ValueType {
  I32
  I64
  F32
  F64
}

///|
pub extend ValueType with Eq::{equal, not_equal}

///|
pub extend ValueType with @debug.Debug::{to_repr}

///|
/// A WASM number value.
#derive(Eq, Debug)
pub(all) enum Value {
  I32(Int32)
  I64(Int64)
  F32(Float)
  F64(Double)
}

///|
pub extend Value with Eq::{equal, not_equal}

///|
pub extend Value with @debug.Debug::{to_repr}

///|
/// The type of a value.
pub fn Value::value_type(self : Value) -> ValueType {
  match self {
    I32(_) => I32
    I64(_) => I64
    F32(_) => F32
    F64(_) => F64
  }
}

///|
/// The parameter and result types of a function.
#derive(Eq, Debug)
pub(all) struct Signature {
  params : Array[ValueType]
  results : Array[ValueType]
}

///|
pub extend Signature with Eq::{equal, not_equal}

///|
pub extend Signature with @debug.Debug::{to_repr}

///|
pub fn Signature::new(
  params : Array[ValueType],
  results : Array[ValueType],
) -> Signature {
  { params, results, }
}

///|
/// `(i64, i64) -> i64`, as WAT writes a signature.
pub impl Show for Signature with fn output(self, logger) {
  let name = (t : ValueType) => {
    match t {
      I32 => "i32"
      I64 => "i64"
      F32 => "f32"
      F64 => "f64"
    }
  }
  let results = match self.results {
    [] => "()"
    [one] => name(one)
    many => "(" + many.map(name).join(", ") + ")"
  }
  logger.write_string(
    "(" + self.params.map(name).join(", ") + ") -> " + results,
  )
}

///|
pub extend Signature with Show::{to_string, output}

///|
/// A limit value that breaks its rule: the name of the limit, the value and the rule.
pub suberror LimitError {
  LimitError(String, Int64, String)
}

///|
/// A number of 64 KiB pages of linear memory, from 1 to 65536.
#derive(Eq, Debug)
pub struct PageCount {
  priv pages : Int32
}

///|
pub extend PageCount with Eq::{equal, not_equal}

///|
pub extend PageCount with @debug.Debug::{to_repr}

///|
pub fn PageCount::parse(pages : Int32) -> PageCount raise LimitError {
  guard pages >= 1 && pages <= 65536 else {
    raise LimitError("page count", pages.to_int64(), "from 1 to 65536 pages")
  }
  { pages, }
}

///|
/// The default memory ceiling: 4096 pages (256 MiB).
pub fn PageCount::default_ceiling() -> PageCount {
  { pages: 4096, }
}

///|
pub fn PageCount::pages(self : PageCount) -> Int32 {
  self.pages
}

///|
/// An instruction budget for one call, from 1 to 9223372036854775807 instructions (the Int64
/// maximum). The native engine counts in a 64-bit integer (wamr/UPSTREAM.md).
#derive(Eq, Debug)
pub struct Budget {
  priv instructions : Int64
}

///|
pub extend Budget with Eq::{equal, not_equal}

///|
pub extend Budget with @debug.Debug::{to_repr}

///|
/// Makes a budget of `instructions` for one call. It raises `LimitError` when `instructions` is less
/// than 1. Every positive Int64 is a budget.
pub fn Budget::parse(instructions : Int64) -> Budget raise LimitError {
  guard instructions >= 1L else {
    raise LimitError(
      "instruction budget", instructions, "from 1 to 9223372036854775807 instructions",
    )
  }
  { instructions, }
}

///|
/// The number of instructions of the budget, from 1 to 9223372036854775807.
pub fn Budget::instructions(self : Budget) -> Int64 {
  self.instructions
}

///|
/// A deadline for one call, in milliseconds, from 1 to 2147483647.
#derive(Eq, Debug)
pub struct Deadline {
  priv milliseconds : Int32
}

///|
pub extend Deadline with Eq::{equal, not_equal}

///|
pub extend Deadline with @debug.Debug::{to_repr}

///|
pub fn Deadline::parse(milliseconds : Int32) -> Deadline raise LimitError {
  guard milliseconds >= 1 else {
    raise LimitError(
      "deadline",
      milliseconds.to_int64(),
      "from 1 to 2147483647 milliseconds",
    )
  }
  { milliseconds, }
}

///|
pub fn Deadline::milliseconds(self : Deadline) -> Int32 {
  self.milliseconds
}

///|
/// A WASM feature beyond the core set that both engines take: WASM 1.0 with mutable globals, sign
/// extension, saturating conversions, multi-value, bulk memory and reference types.
#derive(Eq, Debug)
pub(all) enum Feature {
  Simd
  Threads
  Memory64
  ExceptionHandling
  Gc
  TailCall
  MultiMemory
  Unknown(String)
}

///|
pub extend Feature with Eq::{equal, not_equal}

///|
pub extend Feature with @debug.Debug::{to_repr}

///|
/// The name of a feature, for example `simd` or `exception-handling`.
pub fn Feature::name(self : Feature) -> String {
  match self {
    Simd => "simd"
    Threads => "threads"
    Memory64 => "memory64"
    ExceptionHandling => "exception-handling"
    Gc => "gc"
    TailCall => "tail-call"
    MultiMemory => "multi-memory"
    Unknown(what) => "unknown (\{what})"
  }
}

///|
fn Feature::of(feature : @binary.Feature) -> Feature {
  match feature {
    Simd => Simd
    Threads => Threads
    Memory64 => Memory64
    ExceptionHandling => ExceptionHandling
    Gc => Gc
    TailCall => TailCall
    MultiMemory => MultiMemory
    Unknown(what) => Unknown(what)
  }
}

///|
/// The features that a module needs, from a pure scan of its bytes. Both engines run this scan
/// before they compile.
pub fn Feature::scan(bytes : Bytes) -> Array[Feature] raise EngineError {
  let module_ = @binary.Module::parse(bytes) catch {
    DecodeError(at, rule) => raise Invalid("byte \{at}: \{rule}")
  }
  module_.features().map(Feature::of)
}

///|
/// Whether an engine enforces a bound.
#derive(Eq, Debug)
pub(all) enum Bound {
  Enforced
  NotEnforced
}

///|
pub extend Bound with Eq::{equal, not_equal}

///|
pub extend Bound with @debug.Debug::{to_repr}

///|
/// The bounds that an engine enforces.
#derive(Eq, Debug)
pub(all) struct Bounds {
  memory : Bound
  budget : Bound
  interruption : Bound
}

///|
pub extend Bounds with Eq::{equal, not_equal}

///|
pub extend Bounds with @debug.Debug::{to_repr}

///|
/// The kind of a trap.
#derive(Eq, Debug)
pub(all) enum TrapKind {
  Unreachable
  MemoryOutOfBounds
  DivideByZero
  IntegerOverflow
  InvalidConversion
  StackExhausted
  IndirectCall
  TableOutOfBounds
  Host
  Other
}

///|
pub extend TrapKind with Eq::{equal, not_equal}

///|
pub extend TrapKind with @debug.Debug::{to_repr}

///|
/// An error of a module load or an instantiation.
#derive(Debug)
pub suberror EngineError {
  /// The bytes are not a valid module, with the reason.
  Invalid(String)
  /// The module needs a feature that the engine does not support.
  FeatureUnsupported(Feature)
  /// An import is missing or has another signature: the import and the detail.
  LinkError(String, String)
  /// The memory ceiling is smaller than the initial pages: the ceiling and the initial pages.
  CeilingBelowInitial(Int32, Int32)
  /// The start function of the module failed: a trap, an exhausted budget, an interruption, a memory
  /// limit or a host error.
  Start(CallError)
}

///|
pub extend EngineError with @debug.Debug::{to_repr}

///|
/// The error of a host function, with its message.
#derive(Eq, Debug)
pub(all) suberror HostError {
  HostError(String)
  /// The host function ends the guest's call with an exit code, as WASI `proc_exit` does. After code 0
  /// the instance takes more calls, as upstream Extism; after another code it takes no more.
  Exit(Int32)
}

///|
pub extend HostError with Eq::{equal, not_equal}

///|
pub extend HostError with @debug.Debug::{to_repr}

///|
/// An error of a call.
#derive(Debug)
pub(all) suberror CallError {
  /// The guest trapped.
  Trap(TrapKind)
  /// The call ran more instructions than its budget.
  BudgetExhausted
  /// The call passed its deadline.
  Interrupted
  /// The call failed after the ceiling refused a `memory.grow`.
  MemoryLimit
  /// A host function failed.
  Host(HostError)
  /// The instance exports no function with this name.
  UnknownExport(String)
  /// The arguments do not match the signature of the export: the export and the detail.
  ArgumentMismatch(String, String)
  /// An earlier call trapped, so the instance takes no more calls.
  Unusable
}

///|
pub extend CallError with @debug.Debug::{to_repr}

///|
/// A guest memory access that is outside the memory: the offset and the length.
#derive(Eq, Debug)
pub(all) suberror MemoryError {
  OutOfBounds(Int64, Int32)
}

///|
pub extend MemoryError with Eq::{equal, not_equal}

///|
pub extend MemoryError with @debug.Debug::{to_repr}

///|
/// The kind and the type of an import or an export.
#derive(Eq, Debug)
pub(all) enum ExternKind {
  /// A function; `None` when its signature has a type other than the four number types.
  Function(Signature?)
  Table
  Memory
  Global
  Tag
}

///|
pub extend ExternKind with Eq::{equal, not_equal}

///|
pub extend ExternKind with @debug.Debug::{to_repr}

///|
#derive(Eq, Debug)
pub(all) struct ImportDescriptor {
  module_ : String
  name : String
  kind : ExternKind
}

///|
pub extend ImportDescriptor with Eq::{equal, not_equal}

///|
pub extend ImportDescriptor with @debug.Debug::{to_repr}

///|
#derive(Eq, Debug)
pub(all) struct ExportDescriptor {
  name : String
  kind : ExternKind
}

///|
pub extend ExportDescriptor with Eq::{equal, not_equal}

///|
pub extend ExportDescriptor with @debug.Debug::{to_repr}