// Copyright 2026 Joshua Tenner. Original Starshine code; see ../../LICENSE.

///|
// Each suspended expression retains its own local-initialization COW owner.
// Frames borrow instruction arrays and preserve the stack aliases used by the
// existing control entry/finish helpers. EH still uses its existing dispatcher.
priv struct TcExprFrame {
  instrs : Array[Instruction]
  mut index : Int32
  mut state : TcState
  mut owner : TcInitializationOwner?
  context : Bool
}

///|
priv enum TcExprContinuation {
  BlockFrame(TcExprFrame, TcState, Array[ValType])
  LoopFrame(TcExprFrame, TcState, Array[ValType])
  IfThenFrame(
    TcExprFrame,
    TcState,
    Array[ValType],
    Array[ValType],
    Array[Instruction]
  )
  IfElseFrame(TcExprFrame, TcState, TcState, Array[ValType])
}

///|
fn tc_expr_frame(
  xs : Array[Instruction],
  st : TcState,
  context : Bool,
) -> TcExprFrame {
  { instrs: xs, index: 0, state: st, owner: None, context, }
}

///|
fn tc_expr_child(
  entry : TcState,
  params : Array[ValType],
  labels : Array[ValType],
  xs : Array[Instruction],
) -> TcExprFrame {
  tc_expr_frame(
    xs,
    TcState::{
      env: entry.env.with_label(labels),
      stack: tc_stack_from_types(params),
      reachable: entry.reachable,
      polymorphic: false,
      escape: NoTcEscape,
      reachable_escape_depths: [],
      initialized_locals: tc_fork_initialized_for_body(
        entry.initialized_locals,
        xs,
      ),
    },
    true,
  )
}

///|
// Keep ordinary diagnostics byte-for-byte. Deep control trees cannot safely use
// recursive Instruction::Show; retain the control kind, block type and exact
// instruction location, with an explicit bounded-rendering marker instead.
fn tc_expr_instruction_context(ins : Instruction) -> String {
  let pending : Array[(Instruction, Int32)] = [(ins, 0)]
  let mut nodes = 0
  let mut bounded = true
  while pending.length() > 0 {
    let (item, depth) = pending.pop().unwrap()
    nodes += 1
    if depth > 32 || nodes > 256 {
      bounded = false
      break
    }
    match item {
      Block(_, Expr(xs)) | Loop(_, Expr(xs)) => {
        if xs.length() > 256 - nodes - pending.length() {
          bounded = false
          break
        }
        for x in xs {
          pending.push((x, depth + 1))
        }
      }
      If(_, left, right) => {
        let right = right.unwrap_or([])
        if left.length() + right.length() > 256 - nodes - pending.length() {
          bounded = false
          break
        }
        for x in left {
          pending.push((x, depth + 1))
        }
        for x in right {
          pending.push((x, depth + 1))
        }
      }
      Try(_, Expr(xs), catches, _) => {
        if xs.length() > 256 - nodes - pending.length() {
          bounded = false
          break
        }
        for x in xs {
          pending.push((x, depth + 1))
        }
        for catch_ in catches {
          let child = match catch_ {
            LegacyCatch(_, Expr(child)) | LegacyCatchAll(Expr(child)) => child
          }
          if child.length() > 256 - nodes - pending.length() {
            bounded = false
            break
          }
          for x in child {
            pending.push((x, depth + 1))
          }
        }
        if !bounded {
          break
        }
      }
      TryTable(_, _, Expr(xs)) => {
        if xs.length() > 256 - nodes - pending.length() {
          bounded = false
          break
        }
        for x in xs {
          pending.push((x, depth + 1))
        }
      }
      _ => ()
    }
  }
  if bounded {
    return "\{ins}"
  }
  match ins {
    Block(bt, _) => "Block(\{bt}, )"
    Loop(bt, _) => "Loop(\{bt}, )"
    If(bt, _, _) => "If(\{bt}, )"
    Try(bt, _, _, _) => "Try(\{bt}, )"
    TryTable(bt, _, _) => "TryTable(\{bt}, )"
    _ => "\{ins}"
  }
}

///|
fn tc_expr_wrap_error(frame : TcExprFrame, reason : String) -> String {
  if !frame.context {
    return reason
  }
  let ins = tc_expr_instruction_context(frame.instrs[frame.index])
  "instruction[\{frame.index}] \{ins}: \{reason}; stack=\{frame.state.stack}"
}

///|
fn tc_expr_error(
  frame : TcExprFrame,
  pending : Array[TcExprContinuation],
  reason : String,
) -> TcResult {
  let mut error = tc_expr_wrap_error(frame, reason)
  while pending.length() > 0 {
    let parent = match pending.pop().unwrap() {
      BlockFrame(p, _, _)
      | LoopFrame(p, _, _)
      | IfThenFrame(p, _, _, _, _)
      | IfElseFrame(p, _, _, _) => p
    }
    error = tc_expr_wrap_error(parent, error)
  }
  Err(error)
}

///|
fn tc_iterative_expr(expr : Expr, state : TcState, context : Bool) -> TcResult {
  let mut frame = tc_expr_frame(expr.0, state, context)
  let pending : Array[TcExprContinuation] = []
  while true {
    if frame.index == frame.instrs.length() {
      if pending.is_empty() {
        return Ok(frame.state)
      }
      let child = frame.state
      let continuation = pending.pop().unwrap()
      let (parent, finished) = match continuation {
        BlockFrame(parent, entry, results) =>
          (parent, tc_state_finish_block(parent.state, entry, child, results))
        LoopFrame(parent, entry, results) =>
          (parent, tc_state_finish_loop(parent.state, entry, child, results))
        IfThenFrame(parent, entry, params, results, right) => {
          // Check else instructions before normalizing either arm's end stack,
          // matching the existing if error precedence and entry-mask ownership.
          pending.push(IfElseFrame(parent, entry, child, results))
          frame = tc_expr_child(entry, params, results, right)
          continue
        }
        IfElseFrame(parent, entry, left, results) =>
          (
            parent,
            tc_state_finish_if(parent.state, entry, left, child, results),
          )
      }
      match finished {
        Err(error) => return tc_expr_error(parent, pending, error)
        Ok(out) => {
          parent.state = out
          parent.index += 1
          frame = parent
          continue
        }
      }
    }
    let ins = frame.instrs[frame.index]
    match ins {
      Block(bt, Expr(xs)) | Loop(bt, Expr(xs)) | If(bt, xs, _) => {
        let (params, results) = match frame.state.env.expand_blocktype(bt) {
          Err(error) => return tc_expr_error(frame, pending, error)
          Ok(x) => x
        }
        let condition = match ins {
          If(_, _, _) => frame.state.pop_expect(ValType::i32())
          _ => Ok(frame.state)
        }
        let entry = match condition {
          Err(error) => return tc_expr_error(frame, pending, error)
          Ok(st) =>
            match st.pop_types(params) {
              Err(error) => return tc_expr_error(frame, pending, error)
              Ok(entry) => entry
            }
        }
        let labels = match ins {
          Loop(_, _) => params
          _ => results
        }
        let child = tc_expr_child(entry, params, labels, xs)
        pending.push(
          match ins {
            Block(_, _) => BlockFrame(frame, entry, results)
            Loop(_, _) => LoopFrame(frame, entry, results)
            If(_, _, right) =>
              IfThenFrame(frame, entry, params, results, right.unwrap_or([]))
            _ => abort("control frame invariant")
          },
        )
        frame = child
        continue
      }
      _ => ()
    }
    let checked = match ins {
      LocalGet(idx) =>
        match typecheck_local_get_error(frame.state, idx) {
          None => {
            frame.index += 1
            continue
          }
          Some(error) => Err(error)
        }
      LocalSet(idx) | LocalTee(idx) => {
        let LocalIdx(raw) = idx
        let local_id = raw.reinterpret_as_int()
        if frame.owner is None &&
          local_id >= 0 &&
          local_id < frame.state.initialized_locals.length() &&
          !frame.state.initialized_locals[local_id] {
          frame.owner = Some(TcInitializationOwner::new())
        }
        let initialization_owner = frame.owner
        match ins {
          LocalSet(_) =>
            typecheck_local_set(frame.state, idx, initialization_owner~)
          _ => typecheck_local_tee(frame.state, idx, initialization_owner~)
        }
      }
      _ => Typecheck::typecheck(ins, frame.state)
    }
    match checked {
      Ok(out) => {
        frame.state = out
        frame.index += 1
      }
      Err(error) => return tc_expr_error(frame, pending, error)
    }
  } nobreak {
    abort("expression traversal invariant")
  }
}