///|
/// Serialized dialect name used by Wasm MilkIR extension operations.
pub const WASM_DIALECT : String = "wasm"

///|
fn imm(values : Array[Int]) -> FixedArray[Int] {
  FixedArray::makei(values.length(), fn(i) { values[i] })
}

///|
fn bool_imm(value : Bool) -> Int {
  if value {
    1
  } else {
    0
  }
}

///|
fn imm_bool(value : Int) -> Bool {
  value != 0
}

///|
fn descriptor_for_spec(spec : WasmOpcodeSpec) -> @milkir.ExtOpDescriptor {
  ExtOpDescriptor(WASM_DIALECT, spec.wire_name, spec.immediate_kinds.length())
}

///|
pub fn descriptor(opcode : WasmOpcode) -> @milkir.ExtOpDescriptor {
  let (id, _) = opcode_payload(opcode)
  descriptor_for_spec(wasm_opcode_spec(id))
}

///|
pub fn descriptor_by_name(name : String) -> @milkir.ExtOpDescriptor? {
  wasm_opcode_spec_by_wire_name(name).map(descriptor_for_spec)
}

///|
pub fn encode(opcode : WasmOpcode) -> @milkir.ExtOp {
  let (id, immediates) = opcode_payload(opcode)
  let spec = wasm_opcode_spec(id)
  if validate_wasm_opcode_immediates(spec, immediates) is Some(message) {
    abort("invalid \{spec.typed_constructor} encoding: \{message}")
  }
  ExtOp(WASM_DIALECT, spec.wire_name, immediates)
}

///|
fn decode_result(ext : @milkir.ExtOp) -> Result[WasmOpcode, String] {
  if ext.dialect != WASM_DIALECT {
    return Err(
      "expected Wasm MilkIR dialect '\{WASM_DIALECT}', got '\{ext.dialect}'",
    )
  }
  let spec = match wasm_opcode_spec_by_wire_name(ext.opcode) {
    Some(spec) => spec
    None => return Err("unknown Wasm MilkIR extension opcode '\{ext.opcode}'")
  }
  match validate_wasm_opcode_immediates(spec, ext.immediates) {
    Some(message) => Err(message)
    None =>
      match opcode_from_payload(spec.id, ext.immediates) {
        Some(opcode) => Ok(opcode)
        None => Err("unsupported Wasm MilkIR extension opcode '\{ext.opcode}'")
      }
  }
}

///|
pub fn decode(ext : @milkir.ExtOp) -> WasmOpcode? {
  match decode_result(ext) {
    Ok(opcode) => Some(opcode)
    Err(_) => None
  }
}

///|
pub fn decode_error(ext : @milkir.ExtOp) -> String? {
  match decode_result(ext) {
    Ok(_) => None
    Err(message) => Some(message)
  }
}

///|
/// Validate one read-only Wasm extension instruction at an adapter seam.
pub fn validate_extension(view : @milkir.ExtensionInstView) -> String? {
  let opcode = match decode_result(view.op) {
    Ok(opcode) => opcode
    Err(message) => return Some(message)
  }
  let (id, _) = opcode_payload(opcode)
  let spec = wasm_opcode_spec(id)
  if validate_wasm_instruction_shape(spec, view) is Some(message) {
    return Some(message)
  }
  match opcode {
    ArrayNewFixed(_, count) if view.operand_types.length() != count =>
      Some(
        "malformed Wasm MilkIR extension 'array_new_fixed': expected \{count} operands, got \{view.operand_types.length()}",
      )
    SpillLocalsForThrow(count) if view.operand_types.length() != count =>
      Some(
        "malformed Wasm MilkIR extension 'spill_locals_for_throw': expected \{count} operands, got \{view.operand_types.length()}",
      )
    _ => None
  }
}

///|
/// Verify core MilkIR invariants and every extension against the Wasm schema.
pub fn verify_function(
  func : @milkir.Function,
) -> Unit raise @milkir.VerifyError {
  func.verify_with_dialect_validator(
    WASM_DIALECT,
    validate_extension,
    validate_global_value,
  )
}

///|
pub fn decode_opcode(opcode : @milkir.Opcode) -> WasmOpcode? {
  match opcode {
    Ext(ext, _) => decode(ext)
    _ => None
  }
}