///|
/// Parsing for canonical section (8) and start section (9).

///|
pub(all) enum StringEncoding {
  Utf8
  Utf16
  Latin1Utf16
} derive(Debug, Eq)

///|
pub(all) enum CanonOpt {
  StringEncoding(StringEncoding)
  Memory(Int)
  Realloc(Int)
  PostReturn(Int)
  CoreType(Int)
  Gc
  Async
  Callback(Int)
} derive(Debug, Eq)

///|
pub(all) enum Canon {
  Lift(Int, Array[CanonOpt], Int) // core funcidx, opts, typeidx
  Lower(Int, Array[CanonOpt]) // funcidx, opts
  BackpressureInc
  BackpressureDec
  ContextGet(@types.ValueType, Int) // valtype, context index
  ContextSet(@types.ValueType, Int) // valtype, context index
  TaskReturn(ValType?, Array[CanonOpt]) // result, opts
  TaskCancel
  ThreadIndex
  ThreadYield(Bool) // cancellable?
  ThreadSuspendThenResume(Bool) // cancellable?
  ThreadYieldThenResume(Bool) // cancellable?
  ThreadSuspendThenPromote(Bool) // cancellable?
  ThreadYieldThenPromote(Bool) // cancellable?
  ThreadResumeLater
  ThreadSuspend(Bool) // cancellable?
  ThreadNewIndirect(Int, Int) // core functype index, core table index
  WaitableSetNew
  WaitableSetWait(Bool, Int) // cancellable?, core memidx
  WaitableSetPoll(Bool, Int) // cancellable?, core memidx
  WaitableSetDrop
  WaitableJoin
  SubtaskCancel(Bool)
  SubtaskDrop
  StreamNew(Int) // stream typeidx
  StreamRead(Int, Array[CanonOpt]) // stream typeidx, opts
  StreamWrite(Int, Array[CanonOpt]) // stream typeidx, opts
  StreamCancelRead(Int, Bool) // stream typeidx, async?
  StreamCancelWrite(Int, Bool) // stream typeidx, async?
  StreamDropReadable(Int) // stream typeidx
  StreamDropWritable(Int) // stream typeidx
  FutureNew(Int) // future typeidx
  FutureRead(Int, Array[CanonOpt]) // future typeidx, opts
  FutureWrite(Int, Array[CanonOpt]) // future typeidx, opts
  FutureCancelRead(Int, Bool) // future typeidx, async?
  FutureCancelWrite(Int, Bool) // future typeidx, async?
  FutureDropReadable(Int) // future typeidx
  FutureDropWritable(Int) // future typeidx
  ErrorContextNew(Array[CanonOpt]) // opts
  ErrorContextDebugMessage(Array[CanonOpt]) // opts
  ErrorContextDrop
  ResourceNew(Int) // typeidx
  ResourceDrop(Int) // typeidx
  ResourceRep(Int) // typeidx
} derive(Debug, Eq)

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

///|
fn parse_context_core_valtype(
  reader : Reader,
) -> @types.ValueType raise ComponentParseError {
  let b = reader.read_u8()
  match b {
    0x7f => I32
    _ => raise InvalidCanonSection
  }
}

///|
fn parse_prim_valtype_from_sleb(v : Int) -> PrimValType? {
  match v {
    -1 => Some(Bool)
    -2 => Some(S8)
    -3 => Some(U8)
    -4 => Some(S16)
    -5 => Some(U16)
    -6 => Some(S32)
    -7 => Some(U32)
    -8 => Some(S64)
    -9 => Some(U64)
    -10 => Some(F32)
    -11 => Some(F64)
    -12 => Some(Char)
    -13 => Some(String)
    -28 => Some(ErrorContext)
    _ => None
  }
}

///|
fn parse_canon_valtype(reader : Reader) -> ValType raise ComponentParseError {
  let v = reader.read_sleb_i32()
  if v >= 0 {
    TypeIdx(v)
  } else {
    match parse_prim_valtype_from_sleb(v) {
      Some(p) => Prim(p)
      None => raise InvalidCanonSection
    }
  }
}

///|
fn parse_canon_resultlist(
  reader : Reader,
) -> ValType? raise ComponentParseError {
  let tag = reader.read_u8()
  if tag == 0x00 {
    Some(parse_canon_valtype(reader))
  } else if tag == 0x01 {
    let must_be_zero = reader.read_u8()
    if must_be_zero != 0x00 {
      raise InvalidCanonSection
    }
    None
  } else {
    raise InvalidCanonSection
  }
}

///|
fn parse_opts(reader : Reader) -> Array[CanonOpt] raise ComponentParseError {
  let n = reader.read_leb_u32()
  let opts : Array[CanonOpt] = []
  for _ in 0.. CanonOpt::StringEncoding(Utf8)
      0x01 => StringEncoding(Utf16)
      0x02 => StringEncoding(Latin1Utf16)
      0x03 => Memory(reader.read_leb_u32())
      0x04 => Realloc(reader.read_leb_u32())
      0x05 => PostReturn(reader.read_leb_u32())
      0x08 => CoreType(reader.read_leb_u32())
      0x09 => Gc
      0x06 => Async
      0x07 => Callback(reader.read_leb_u32())
      _ => raise InvalidCanonSection
    }
    opts.push(opt)
  }
  opts
}

///|
fn parse_canon(reader : Reader) -> Canon raise ComponentParseError {
  let tag = reader.read_u8()
  match tag {
    0x00 => {
      // 0x00 0x00 f: opts: ft:
      let must_be_func = reader.read_u8()
      if must_be_func != 0x00 {
        raise InvalidCanonSection
      }
      let core_func = reader.read_leb_u32()
      let opts = parse_opts(reader)
      let ft = reader.read_typeidx()
      Lift(core_func, opts, ft)
    }
    0x01 => {
      // 0x01 0x00 f: opts:
      let must_be_func = reader.read_u8()
      if must_be_func != 0x00 {
        raise InvalidCanonSection
      }
      let func = reader.read_leb_u32()
      let opts = parse_opts(reader)
      Lower(func, opts)
    }
    0x05 => TaskCancel
    0x06 => {
      let async_flag = reader.read_u8()
      if async_flag != 0x00 && async_flag != 0x01 {
        raise InvalidCanonSection
      }
      SubtaskCancel(async_flag == 0x01)
    }
    0x02 => ResourceNew(reader.read_typeidx())
    0x03 => ResourceDrop(reader.read_typeidx())
    0x04 => ResourceRep(reader.read_typeidx())
    0x0a => {
      let vt = parse_context_core_valtype(reader)
      let idx = reader.read_leb_u32()
      if idx != 0 {
        raise InvalidCanonSection
      }
      ContextGet(vt, idx)
    }
    0x0b => {
      let vt = parse_context_core_valtype(reader)
      let idx = reader.read_leb_u32()
      if idx != 0 {
        raise InvalidCanonSection
      }
      ContextSet(vt, idx)
    }
    0x09 => {
      let result = parse_canon_resultlist(reader)
      let opts = parse_opts(reader)
      TaskReturn(result, opts)
    }
    0x0c => {
      let flag = reader.read_u8()
      if flag != 0x00 && flag != 0x01 {
        raise InvalidCanonSection
      }
      ThreadYield(flag == 0x01)
    }
    0x26 => ThreadIndex
    0x27 => {
      let ty = reader.read_leb_u32()
      let table = reader.read_leb_u32()
      ThreadNewIndirect(ty, table)
    }
    0x28 => ThreadResumeLater
    0x29 => {
      let flag = reader.read_u8()
      if flag != 0x00 && flag != 0x01 {
        raise InvalidCanonSection
      }
      ThreadSuspend(flag == 0x01)
    }
    0x2a => {
      let flag = reader.read_u8()
      if flag != 0x00 && flag != 0x01 {
        raise InvalidCanonSection
      }
      ThreadSuspendThenResume(flag == 0x01)
    }
    0x2b => {
      let flag = reader.read_u8()
      if flag != 0x00 && flag != 0x01 {
        raise InvalidCanonSection
      }
      ThreadYieldThenResume(flag == 0x01)
    }
    0x2c => {
      let flag = reader.read_u8()
      if flag != 0x00 && flag != 0x01 {
        raise InvalidCanonSection
      }
      ThreadSuspendThenPromote(flag == 0x01)
    }
    0x2d => {
      let flag = reader.read_u8()
      if flag != 0x00 && flag != 0x01 {
        raise InvalidCanonSection
      }
      ThreadYieldThenPromote(flag == 0x01)
    }
    0x0d => SubtaskDrop
    0x0e => StreamNew(reader.read_typeidx())
    0x0f => {
      let ty = reader.read_typeidx()
      let opts = parse_opts(reader)
      StreamRead(ty, opts)
    }
    0x10 => {
      let ty = reader.read_typeidx()
      let opts = parse_opts(reader)
      StreamWrite(ty, opts)
    }
    0x11 => {
      let ty = reader.read_typeidx()
      let async_flag = reader.read_u8()
      if async_flag != 0x00 && async_flag != 0x01 {
        raise InvalidCanonSection
      }
      StreamCancelRead(ty, async_flag == 0x01)
    }
    0x12 => {
      let ty = reader.read_typeidx()
      let async_flag = reader.read_u8()
      if async_flag != 0x00 && async_flag != 0x01 {
        raise InvalidCanonSection
      }
      StreamCancelWrite(ty, async_flag == 0x01)
    }
    0x13 => StreamDropReadable(reader.read_typeidx())
    0x14 => StreamDropWritable(reader.read_typeidx())
    0x15 => FutureNew(reader.read_typeidx())
    0x16 => {
      let ty = reader.read_typeidx()
      let opts = parse_opts(reader)
      FutureRead(ty, opts)
    }
    0x17 => {
      let ty = reader.read_typeidx()
      let opts = parse_opts(reader)
      FutureWrite(ty, opts)
    }
    0x18 => {
      let ty = reader.read_typeidx()
      let async_flag = reader.read_u8()
      if async_flag != 0x00 && async_flag != 0x01 {
        raise InvalidCanonSection
      }
      FutureCancelRead(ty, async_flag == 0x01)
    }
    0x19 => {
      let ty = reader.read_typeidx()
      let async_flag = reader.read_u8()
      if async_flag != 0x00 && async_flag != 0x01 {
        raise InvalidCanonSection
      }
      FutureCancelWrite(ty, async_flag == 0x01)
    }
    0x1a => FutureDropReadable(reader.read_typeidx())
    0x1b => FutureDropWritable(reader.read_typeidx())
    0x1c => {
      let opts = parse_opts(reader)
      ErrorContextNew(opts)
    }
    0x1d => {
      let opts = parse_opts(reader)
      ErrorContextDebugMessage(opts)
    }
    0x1e => ErrorContextDrop
    0x1f => WaitableSetNew
    0x20 => {
      let flag = reader.read_u8()
      if flag != 0x00 && flag != 0x01 {
        raise InvalidCanonSection
      }
      WaitableSetWait(flag == 0x01, reader.read_leb_u32())
    }
    0x21 => {
      let flag = reader.read_u8()
      if flag != 0x00 && flag != 0x01 {
        raise InvalidCanonSection
      }
      WaitableSetPoll(flag == 0x01, reader.read_leb_u32())
    }
    0x22 => WaitableSetDrop
    0x23 => WaitableJoin
    0x24 => BackpressureInc
    0x25 => BackpressureDec
    _ => raise UnsupportedCanonOpcode(tag)
  }
}

///|
pub fn parse_canon_section(
  payload : Bytes,
) -> Array[Canon] raise ComponentParseError {
  let reader = Reader::Reader(payload)
  let n = reader.read_leb_u32()
  let canons : Array[Canon] = []
  for _ in 0.. Start raise ComponentParseError {
  let reader = Reader::Reader(payload)
  let func_idx = reader.read_leb_u32()
  let n = reader.read_leb_u32()
  let args : Array[Int] = []
  for _ in 0..