///|
/// MicroPython-style bytecode IR.
///
/// NOTE: This is an internal representation (not a stable public API).
/// The intent is to keep the VM stack-based, with explicit jumps and
/// a small constant/name pool similar in spirit to MicroPython.

///|
pub(all) enum BcOp {
  Nop
  PopTop
  DupTop
  RotTwo
  RotThree

  // Stack pushes.
  LoadConst(Int32) // consts[idx]
  LoadName(Int32) // names[idx]

  // Stack pops.
  StoreName(Int32) // names[idx]
  DeleteName(Int32) // names[idx]

  // Attribute / subscript.
  LoadAttr(Int32) // names[idx]
  StoreAttr(Int32) // names[idx]
  DeleteAttr(Int32) // names[idx]
  LoadSubscr
  StoreSubscr
  DeleteSubscr

  // Builders.
  BuildTuple(Int32) // count
  BuildList(Int32) // count
  BuildMap(Int32) // pair_count
  BuildSet(Int32) // count
  BuildSlice // pops start, stop, step and pushes a slice instance

  // Collection mutation helpers (used for literal unpacking and call args).
  // Stack (top is right):
  // - ListAppend:  [list, value] -> [list]
  // - ListExtend:  [list, iterable] -> [list]
  // - SetAdd:      [set, value] -> [set]
  // - SetUpdate:   [set, iterable] -> [set]
  // - DictSetItem: [dict, key, value] -> [dict]
  // - DictUpdate:  [dict, mapping] -> [dict]
  // - KwListExtendFromDict: [kw_list, mapping] -> [kw_list]
  // - ListToTuple: [list] -> [tuple]
  ListAppend
  ListExtend
  SetAdd
  SetUpdate
  DictSetItem
  DictUpdate
  KwListExtendFromDict
  ListToTuple

  // f-string evaluation.
  // Pops a raw f-string source text and pushes the formatted result string.
  EvalFString

  // Unpacking.
  UnpackSequence(Int32) // count; pops iterable and pushes items (reversed) for STORE
  UnpackEx(Int32, Int32) // before, after; pops iterable and pushes items + list-rest (reversed) for STORE

  // Iteration (MicroPython-style).
  GetIter
  ForIter(Int32) // absolute pc to jump to on StopIteration; pops iterator on stop

  // Operators.
  Unary(UnaryOp)
  Binary(BinaryOp)
  Compare(CompareOp)

  // Control flow.
  Jump(Int32) // absolute pc
  JumpIfFalse(Int32) // absolute pc; consumes TOS
  JumpIfTrue(Int32) // absolute pc; consumes TOS
  JumpIfFalseOrPop(Int32) // absolute pc; keeps TOS on jump, else pops it
  JumpIfTrueOrPop(Int32) // absolute pc; keeps TOS on jump, else pops it
  // Jump that unwinds `finally`/`with` handlers first (used by break/continue).
  UnwindJump(Int32, Int32) // absolute pc; number of finally handlers to unwind

  // Exceptions.
  SetupExcept(Int32) // absolute pc for exception handler
  SetupFinally(Int32) // absolute pc for finally handler
  PopExcept
  EnterFinally(Int32) // jump to finally handler and mark it active
  EndExcept
  EndFinally
  CheckExceptionMatch // pops handler type value; pushes bool
  LoadException // pushes current exception instance value
  LoadExceptionType // pushes exception type value for `__exit__` (`` or a string fallback)
  LoadExceptionTypeName // pushes exception type name (string), for `__aexit__`
  LoadExceptionTraceback // pushes traceback object value
  HasPendingException // pushes bool; true when a pending exception is being unwound
  ClearPendingException // clears the pending exception (for suppressing in `with`/`async with`)
  // Exception groups (PEP 654).
  // Wrap a non-group pending exception into an ExceptionGroup, for `except*`.
  EnsureExceptionGroup
  // `except*` handler helpers. These ops operate on the current except handler
  // block and track remaining/unhandled sub-exceptions.
  ExceptStarInit
  ExceptStarMatch // pops handler type value; pushes bool; on match sets current exception to the matched group
  ExceptStarEndHandler // restores base exception; pushes bool indicating whether remaining exceptions exist
  ExceptStarRaiseRemaining // raises the remaining (unhandled) ExceptionGroup
  // Raise an exception value (instance or class) using Python's `raise` statement semantics.
  // Uses the VM's current pending exception (if any) as implicit context.
  Raise
  // `raise exc from cause` (cause may be None).
  RaiseFrom
  Reraise
  // Assertions.
  // - `AssertFail` pops the message value and raises AssertionError.
  // - `AssertFailNone` raises AssertionError with no message expression.
  AssertFail
  AssertFailNone

  // Async helpers.
  Await // pops coroutine, pushes awaited result

  // `match` statement helpers.
  // - MatchPattern: pops subject; pushes either None (no match) or a list of (name, value) tuples.
  // - MatchBind: pops bindings list; binds names; pushes a restore token.
  // - MatchRestore: pops restore token; restores/deletes previous bindings.
  MatchPattern(Int32) // patterns[idx]
  MatchBind
  MatchRestore

  // Generator expression helper.
  // Pops the first iterator (already converted with GetIter), clones current locals,
  // binds it into the cloned locals (when iter_name_idx >= 0), ensures cells for
  // all target names, then returns a generator created via `generator_new`.
  //
  // `template` is a `Value::Function` whose body is the generator body for the genexp.
  // `target_names_const` is a Value::Tuple of Value::Str items.
  GenExpNew(Int32, Int32, Int32) // template const idx, iter_name names idx (or -1), target_names_const idx
  // Like GenExpNew, but returns an async generator expression (created via `async_genexp_new`).
  // `spec` points to `code.genexps`.
  AsyncGenExpNew(Int32, Int32, Int32) // genexps[idx], iter_name names idx (or -1), target_names_const idx

  // Calls.
  CallFunction(Int32) // positional arg count
  // CALL_FUNCTION_KW-style: pushes keyword values then a tuple of keyword names.
  CallFunctionKw(Int32) // positional arg count
  // Varargs/kwargs call helper (used for *args/**kwargs support).
  // Stack: [callee, positional_list, keyword_pairs_list] -> [result]
  CallFunctionVar

  // Function definitions.
  // Stack effect (top is right):
  //   [decorators..., defaults...] -> [defined_value]
  //
  // `template` is a `Value::Function` carrying name/params/body/flags.
  MakeFunction(Int32, Int32, Int32)

  // Class definitions.
  // Stack effect (top is right):
  //   [decorators..., base_specs, keyword_specs] -> [defined_value]
  //
  // `template` is a `Value::Function` whose body is the class suite.
  MakeClass(Int32, Int32, Int32) // template const idx, class name idx, decorator count

  // Type aliases.
  // Stack: [value_thunk_function] -> [type_alias_instance]
  MakeTypeAlias(Int32) // names[idx]

  // Annotations.
  // Pops annotation value and stores it into __annotations__[name] (creating dict if needed).
  StoreAnnotation(Int32) // names[idx]

  // Augmented assignment.
  // Stack: [rhs] -> []
  AugAssignName(Int32, BinaryOp) // names[idx]
  // Stack: [target, rhs] -> []
  AugAssignAttr(Int32, BinaryOp) // names[idx]
  // Stack: [target, index, rhs] -> []
  AugAssignSubscr(BinaryOp)

  // Imports.
  ImportName(Int32, Int32) // consts[module_name_str], names[alias_idx] or -1
  FromImport(Int32, Int32, Int32) // consts[module_name_str], consts[names_tuple], consts[aliases_tuple]

  // Scope checks.
  CheckInFunctionScope(String) // error message when not in a function scope
  CheckInCoroutineScope(String) // error message when not in a coroutine scope

  // Function boundaries.
  ReturnValue
}

///|
pub(all) struct GenExpSpec {
  elt : Expr
  clauses : Array[CompClause]
  filters : Array[Expr]
}

///|
pub(all) struct BcCode {
  name : String
  filename : String
  consts : Array[Value]
  names : Array[String]
  patterns : Array[Pattern]
  genexps : Array[GenExpSpec]
  ops : Array[BcOp]
  // Optional source mapping; aligned with `ops`.
  spans : Array[Span?]
}

///|
pub fn BcCode::empty(name : String, filename : String) -> BcCode {
  BcCode::{
    name,
    filename,
    consts: [],
    names: [],
    patterns: [],
    genexps: [],
    ops: [],
    spans: [],
  }
}

///|
fn bc_op_repr(op : BcOp) -> String {
  fn unary_repr(u : UnaryOp) -> String {
    match u {
      UnaryOp::Pos => "Pos"
      UnaryOp::Neg => "Neg"
      UnaryOp::Not => "Not"
      UnaryOp::Invert => "Invert"
    }
  }

  fn binary_repr(b : BinaryOp) -> String {
    match b {
      BinaryOp::Add => "Add"
      BinaryOp::Sub => "Sub"
      BinaryOp::Mul => "Mul"
      BinaryOp::MatMul => "MatMul"
      BinaryOp::Div => "Div"
      BinaryOp::FloorDiv => "FloorDiv"
      BinaryOp::Mod => "Mod"
      BinaryOp::Pow => "Pow"
      BinaryOp::ShiftLeft => "ShiftLeft"
      BinaryOp::ShiftRight => "ShiftRight"
      BinaryOp::BitAnd => "BitAnd"
      BinaryOp::BitXor => "BitXor"
      BinaryOp::BitOr => "BitOr"
    }
  }

  fn compare_repr(c : CompareOp) -> String {
    match c {
      CompareOp::Eq => "Eq"
      CompareOp::NotEq => "NotEq"
      CompareOp::Lt => "Lt"
      CompareOp::Lte => "Lte"
      CompareOp::Gt => "Gt"
      CompareOp::Gte => "Gte"
      CompareOp::In => "In"
      CompareOp::NotIn => "NotIn"
      CompareOp::Is => "Is"
      CompareOp::IsNot => "IsNot"
    }
  }

  match op {
    BcOp::Nop => "Nop"
    BcOp::PopTop => "PopTop"
    BcOp::DupTop => "DupTop"
    BcOp::RotTwo => "RotTwo"
    BcOp::RotThree => "RotThree"
    BcOp::LoadConst(i) => "LoadConst(" + i.to_string() + ")"
    BcOp::LoadName(i) => "LoadName(" + i.to_string() + ")"
    BcOp::StoreName(i) => "StoreName(" + i.to_string() + ")"
    BcOp::DeleteName(i) => "DeleteName(" + i.to_string() + ")"
    BcOp::LoadAttr(i) => "LoadAttr(" + i.to_string() + ")"
    BcOp::StoreAttr(i) => "StoreAttr(" + i.to_string() + ")"
    BcOp::DeleteAttr(i) => "DeleteAttr(" + i.to_string() + ")"
    BcOp::LoadSubscr => "LoadSubscr"
    BcOp::StoreSubscr => "StoreSubscr"
    BcOp::DeleteSubscr => "DeleteSubscr"
    BcOp::BuildTuple(n) => "BuildTuple(" + n.to_string() + ")"
    BcOp::BuildList(n) => "BuildList(" + n.to_string() + ")"
    BcOp::BuildMap(n) => "BuildMap(" + n.to_string() + ")"
    BcOp::BuildSet(n) => "BuildSet(" + n.to_string() + ")"
    BcOp::BuildSlice => "BuildSlice"
    BcOp::ListAppend => "ListAppend"
    BcOp::ListExtend => "ListExtend"
    BcOp::SetAdd => "SetAdd"
    BcOp::SetUpdate => "SetUpdate"
    BcOp::DictSetItem => "DictSetItem"
    BcOp::DictUpdate => "DictUpdate"
    BcOp::KwListExtendFromDict => "KwListExtendFromDict"
    BcOp::ListToTuple => "ListToTuple"
    BcOp::EvalFString => "EvalFString"
    BcOp::UnpackSequence(n) => "UnpackSequence(" + n.to_string() + ")"
    BcOp::UnpackEx(before, after) =>
      "UnpackEx(" + before.to_string() + ", " + after.to_string() + ")"
    BcOp::GetIter => "GetIter"
    BcOp::ForIter(pc) => "ForIter(" + pc.to_string() + ")"
    BcOp::Unary(u) => "Unary(" + unary_repr(u) + ")"
    BcOp::Binary(b) => "Binary(" + binary_repr(b) + ")"
    BcOp::Compare(c) => "Compare(" + compare_repr(c) + ")"
    BcOp::Jump(pc) => "Jump(" + pc.to_string() + ")"
    BcOp::JumpIfFalse(pc) => "JumpIfFalse(" + pc.to_string() + ")"
    BcOp::JumpIfTrue(pc) => "JumpIfTrue(" + pc.to_string() + ")"
    BcOp::JumpIfFalseOrPop(pc) => "JumpIfFalseOrPop(" + pc.to_string() + ")"
    BcOp::JumpIfTrueOrPop(pc) => "JumpIfTrueOrPop(" + pc.to_string() + ")"
    BcOp::UnwindJump(pc, n) =>
      "UnwindJump(" + pc.to_string() + ", " + n.to_string() + ")"
    BcOp::SetupExcept(pc) => "SetupExcept(" + pc.to_string() + ")"
    BcOp::SetupFinally(pc) => "SetupFinally(" + pc.to_string() + ")"
    BcOp::PopExcept => "PopExcept"
    BcOp::EnterFinally(pc) => "EnterFinally(" + pc.to_string() + ")"
    BcOp::EndExcept => "EndExcept"
    BcOp::EndFinally => "EndFinally"
    BcOp::CheckExceptionMatch => "CheckExceptionMatch"
    BcOp::LoadException => "LoadException"
    BcOp::LoadExceptionType => "LoadExceptionType"
    BcOp::LoadExceptionTypeName => "LoadExceptionTypeName"
    BcOp::LoadExceptionTraceback => "LoadExceptionTraceback"
    BcOp::HasPendingException => "HasPendingException"
    BcOp::ClearPendingException => "ClearPendingException"
    BcOp::EnsureExceptionGroup => "EnsureExceptionGroup"
    BcOp::ExceptStarInit => "ExceptStarInit"
    BcOp::ExceptStarMatch => "ExceptStarMatch"
    BcOp::ExceptStarEndHandler => "ExceptStarEndHandler"
    BcOp::ExceptStarRaiseRemaining => "ExceptStarRaiseRemaining"
    BcOp::Raise => "Raise"
    BcOp::RaiseFrom => "RaiseFrom"
    BcOp::Reraise => "Reraise"
    BcOp::AssertFail => "AssertFail"
    BcOp::AssertFailNone => "AssertFailNone"
    BcOp::Await => "Await"
    BcOp::MatchPattern(i) => "MatchPattern(" + i.to_string() + ")"
    BcOp::MatchBind => "MatchBind"
    BcOp::MatchRestore => "MatchRestore"
    BcOp::GenExpNew(template, iter_name, target_names) =>
      "GenExpNew(" +
      template.to_string() +
      ", " +
      iter_name.to_string() +
      ", " +
      target_names.to_string() +
      ")"
    BcOp::AsyncGenExpNew(template, iter_name, target_names) =>
      "AsyncGenExpNew(" +
      template.to_string() +
      ", " +
      iter_name.to_string() +
      ", " +
      target_names.to_string() +
      ")"
    BcOp::CallFunction(n) => "CallFunction(" + n.to_string() + ")"
    BcOp::CallFunctionKw(n) => "CallFunctionKw(" + n.to_string() + ")"
    BcOp::CallFunctionVar => "CallFunctionVar"
    BcOp::MakeFunction(template, default_count, decorator_count) =>
      "MakeFunction(" +
      template.to_string() +
      ", " +
      default_count.to_string() +
      ", " +
      decorator_count.to_string() +
      ")"
    BcOp::MakeClass(template, name_idx, decorator_count) =>
      "MakeClass(" +
      template.to_string() +
      ", " +
      name_idx.to_string() +
      ", " +
      decorator_count.to_string() +
      ")"
    BcOp::MakeTypeAlias(name_idx) =>
      "MakeTypeAlias(" + name_idx.to_string() + ")"
    BcOp::StoreAnnotation(i) => "StoreAnnotation(" + i.to_string() + ")"
    BcOp::AugAssignName(i, op) =>
      "AugAssignName(" + i.to_string() + ", " + binary_repr(op) + ")"
    BcOp::AugAssignAttr(i, op) =>
      "AugAssignAttr(" + i.to_string() + ", " + binary_repr(op) + ")"
    BcOp::AugAssignSubscr(op) => "AugAssignSubscr(" + binary_repr(op) + ")"
    BcOp::ImportName(mod_idx, alias_idx) =>
      "ImportName(" + mod_idx.to_string() + ", " + alias_idx.to_string() + ")"
    BcOp::FromImport(mod_idx, names_idx, aliases_idx) =>
      "FromImport(" +
      mod_idx.to_string() +
      ", " +
      names_idx.to_string() +
      ", " +
      aliases_idx.to_string() +
      ")"
    BcOp::CheckInFunctionScope(msg) => "CheckInFunctionScope(" + msg + ")"
    BcOp::CheckInCoroutineScope(msg) => "CheckInCoroutineScope(" + msg + ")"
    BcOp::ReturnValue => "ReturnValue"
  }
}

///|
pub fn bc_disassemble(code : BcCode) -> String {
  let b = StringBuilder()
  b.write_string("BcCode(" + code.name + ")\n")
  for i = 0; i < code.ops.length(); i = i + 1 {
    let op = code.ops[i]
    b.write_string(i.to_string())
    b.write_string(": ")
    b.write_string(bc_op_repr(op))
    match code.spans[i] {
      Some(span) =>
        b.write_string(
          "  # " + span.line.to_string() + ":" + span.column.to_string(),
        )
      None => ()
    }
    b.write_string("\n")
  }
  b.to_string()
}