///|
/// Check if an expression is an anonymous function/class/arrow definition
/// per ECMAScript IsAnonymousFunctionDefinition (§15.2)
fn is_anonymous_function_definition(expr : @ast.Expr) -> Bool {
  let mut current = expr
  for ;; {
    match current {
      Grouping(inner, _) => current = inner
      FuncExpr(None, _, _, _, _) => return true
      FuncExprExt(None, _, _, _, _, _) => return true
      ArrowFunc(_, _, _, _) => return true
      ArrowFuncExt(_, _, _, _, _) => return true
      ClassExpr(None, _, _, _, _) => return true
      GeneratorExpr(None, _, _, _, _) => return true
      GeneratorExprExt(None, _, _, _, _, _) => return true
      AsyncFuncExpr(None, _, _, _, _) => return true
      AsyncFuncExprExt(None, _, _, _, _, _) => return true
      AsyncArrowFunc(_, _, _, _) => return true
      AsyncArrowFuncExt(_, _, _, _, _) => return true
      AsyncGeneratorExpr(None, _, _, _, _) => return true
      AsyncGeneratorExprExt(None, _, _, _, _, _) => return true
      _ => return false
    }
  }
}

///|
pub(all) enum Signal {
  Normal(Value)
  ReturnSignal(Value)
  BreakSignal(Value?, String?) // (completion_value (None=empty), label?)
  ContinueSignal(Value?, String?) // (completion_value (None=empty), label?)
}

///|
/// Immutable per-call execution context passed through the evaluation pipeline.
/// Replaces the mutable `strict` and `current_generator` fields that were
/// previously on the Interpreter struct, eliminating fragile save/restore patterns.
pub(all) struct ExecContext {
  strict : Bool
  current_generator : GeneratorObject?
}

///|
/// Microtask record for the event loop microtask queue
/// Stores a callback function and argument list to pass to it
pub(all) struct Microtask {
  callback : Value // The function to call
  args : Array[Value] // Arguments to pass (Promise jobs pass one, queueMicrotask passes none)
}

///|
/// Timer task for setTimeout/setInterval (task queue per WHATWG spec)
/// Timers are processed one at a time with microtask checkpoints between each
pub(all) struct TimerTask {
  id : Int // Unique timer ID for clearTimeout/clearInterval
  callback : Value // The function to call
  args : Array[Value] // Additional arguments to pass to the callback
  delay : Int // Absolute fire-at time in virtual ms (used for ordering)
  period : Int // Interval repeat period (0 for setTimeout)
  is_interval : Bool // Whether this is a setInterval (repeats)
  insertion_order : Int // Stable sort tiebreaker for equal delays
}

///|
/// Host-provided callback that resolves a module specifier to its exports.
/// Called by exec_import when a specifier is not already in module_registry.
/// Return a Map[String, Value] of exported names → values, or raise an error.
pub(all) struct ModuleLoader((String) -> Map[String, Value] raise Error)

///|
/// Host environment state — concerns that belong to the runtime container
/// rather than the JavaScript execution model itself.
///
/// Separating these fields makes the engine/host boundary explicit and
/// enables future host-environment variations (different I/O, timer
/// semantics, or module resolution) without touching execution internals.
pub(all) struct HostEnv {
  output : Array[String] // console.log / print accumulator
  microtask_queue : Array[Microtask] // Promise microtask queue (WHATWG spec)
  timer_queue : @priority_queue.PriorityQueue[TimerTask] // setTimeout / setInterval task queue
  timer_id_counter : Ref[Int] // Next timer ID (monotonically increasing)
  timer_insertion_counter : Ref[Int] // Stable-sort tiebreaker for equal delays
  cancelled_timer_ids : Map[Int, Bool] // IDs cancelled during an active callback
  module_loader : ModuleLoader? // Optional host-provided module resolver
}

///|
pub(all) struct Interpreter {
  host : HostEnv // Host environment (I/O, event loop, module loading)
  global : Environment
  global_this : Value
  annex_b : Bool // Enable Annex B legacy features (--annex-b flag)
  realm_state : RealmState // Per-realm mutable engine state owner
  // ES Modules support
  module_registry : Map[String, Map[String, Value]] // module specifier -> exports namespace
  mut module_exports : Map[String, Value] // current module's exports being built
  mut module_export_bindings : Array[(String, String)] // deferred: (export_name, local_binding_name)
  // Generator runtime state
  generator_objects : Map[Int, GeneratorObject] // live generator instances keyed by ID
  gen_id_counter : Ref[Int] // monotonically increasing ID for new generator instances
  // Compatibility access to realm_state.symbols for existing runtime/stdlib code.
  // This is an alias, not a second symbol-state owner.
  symbols : SymbolState
  // Stdlib dispatch hooks — populated by the wiring layer in the root package
  mut stdlib_hooks : StdlibHooks
  // §19.2.1.3 (#A.6): true while the current function frame is evaluating
  // one of its own parameter defaults. Direct eval in that window raises
  // SyntaxError if its var names conflict with this frame's parameter-scope
  // names (and non-arrow `arguments`). Reset to false on every function entry
  // so nested IIFEs / arrows / recursive calls don't inherit the outer frame's
  // default-eval state. This is Interpreter-owned transient frame state;
  // future per-evaluation facts should prefer ExecContext or a named frame
  // record over module-level mutable state.
  mut in_nonarrow_param_default_eval : Bool
  mut param_default_eval_var_conflicts : @set.Set[String]?
}

///|
pub fn Interpreter::new(
  annex_b? : Bool = false,
  module_loader? : ModuleLoader? = None,
  setup_builtins? : (Environment, Array[String], RealmState, Bool) -> Unit = fn(
    _,
    _,
    _,
    _,
  ) {

  },
  setup_harness? : (Environment, Array[String], Value) -> Unit = fn(_, _, _) {

  },
  stdlib_hooks? : StdlibHooks = default_stdlib_hooks(),
) -> Interpreter {
  let global = Environment::new()
  global.is_var_scope = true
  let output : Array[String] = []
  // Create global object for 'this' in global context
  let global_this : Value = Object({
    bag: PropertyBag(),
    prototype: Null,
    callable: None,
    class_name: "global",
    extensible: true,
    arraybuffer_state: None,
  })
  let realm_state = RealmState()
  global.realm_state = Some(realm_state)
  let symbols = realm_state.symbols
  let host : HostEnv = {
    output,
    microtask_queue: [],
    timer_queue: @priority_queue.PriorityQueue([]),
    timer_id_counter: { val: 1 },
    timer_insertion_counter: { val: 0 },
    cancelled_timer_ids: Map([]),
    module_loader,
  }
  let interp : Interpreter = {
    host,
    global,
    global_this,
    annex_b,
    realm_state,
    module_registry: Map([]),
    module_exports: Map([]),
    module_export_bindings: [],
    generator_objects: Map([]),
    gen_id_counter: { val: 0 },
    symbols,
    stdlib_hooks,
    in_nonarrow_param_default_eval: false,
    param_default_eval_var_conflicts: None,
  }
  global.interpreter_context = Some(interp)
  with_cleared_active_callee_realm_unit(realm_state, fn() {
    setup_builtins(global, output, realm_state, annex_b)
    // Patch global object's [[Prototype]] to Object.prototype now that builtins
    // are set up and [[ObjectPrototype]] is defined (§19.1 — global object
    // inherits from %Object.prototype%).
    match global.bindings.get("[[ObjectPrototype]]") {
      Some(binding) =>
        match global_this {
          Object(data) => data.prototype = binding.value
          _ => ()
        }
      None => ()
    }
    // Bind 'this' and 'globalThis' in global scope
    global.def_builtin("this", global_this)
    global.def_builtin("globalThis", global_this)
    // Mirror global built-in functions onto the global object (globalThis.X)
    // so that this.isFinite, this.isNaN, etc. work correctly per the ES spec
    let global_func_names = [
      "eval", "isFinite", "isNaN", "parseInt", "parseFloat", "encodeURIComponent",
      "decodeURIComponent", "encodeURI", "decodeURI", "escape", "unescape", "String",
      "Number", "Boolean", "Object", "Array", "Function", "RegExp", "Error", "TypeError",
      "RangeError", "ReferenceError", "SyntaxError", "URIError", "EvalError", "Date",
      "Math", "JSON", "Symbol", "Map", "Set", "WeakMap", "WeakSet", "Promise", "Proxy",
      "Reflect", "ArrayBuffer", "DataView", "Int8Array", "Uint8Array", "Uint8ClampedArray",
      "Int16Array", "Uint16Array", "Int32Array", "Uint32Array", "Float32Array", "Float64Array",
    ]
    let builtin_desc : PropDescriptor = {
      writable: true,
      enumerable: false,
      configurable: true,
      getter: None,
      setter: None,
      is_accessor: false,
    }
    match global_this {
      Object(data) =>
        for name in global_func_names {
          match global.bindings.get(name) {
            Some(binding) => {
              data.bag.properties[name] = binding.value
              data.bag.descriptors[name] = { ..builtin_desc }
            }
            None => ()
          }
        }
      _ => ()
    }
    // Mirror global constants (undefined, NaN, Infinity) onto the global object
    // with correct property descriptors per ES spec §19.1
    match global_this {
      Object(data) => {
        data.bag.properties["undefined"] = Undefined
        data.bag.properties["NaN"] = Number(0.0 / 0.0)
        data.bag.properties["Infinity"] = Number(1.0 / 0.0)
        // All three are { writable: false, enumerable: false, configurable: false }
        let frozen_desc : PropDescriptor = {
          writable: false,
          enumerable: false,
          configurable: false,
          getter: None,
          setter: None,
          is_accessor: false,
        }
        data.bag.descriptors["undefined"] = { ..frozen_desc }
        data.bag.descriptors["NaN"] = { ..frozen_desc }
        data.bag.descriptors["Infinity"] = { ..frozen_desc }
      }
      _ => ()
    }
    // Set up GeneratorFunction constructor
    setup_generator_function_constructor(
      global,
      global_this,
      well_known_symbols=realm_state.well_known_symbols,
    )
    // Set up AsyncFunction constructor
    setup_async_function_constructor(
      global,
      well_known_symbols=realm_state.well_known_symbols,
    )
    // Set up AsyncGeneratorFunction constructor and shared %AsyncGeneratorPrototype%
    setup_async_generator_function_constructor(
      global,
      well_known_symbols=realm_state.well_known_symbols,
    )
    // Set up test262 harness host functions (print, $262)
    setup_harness(global, output, global_this)
  })
  interp
}

///|
/// Check for duplicate parameter names. Raises SyntaxError in strict mode.
fn check_duplicate_params(params : Array[String]) -> Unit raise Error {
  let seen = @set.Set::default()
  for p in params {
    if seen.contains(p) {
      raise @errors.SyntaxError(
        message="Duplicate parameter name not allowed in this context",
      )
    }
    seen.add(p)
  }
}

///|
/// Check for duplicate parameter names from Param array (extended params).
fn params_have_rest_pattern(params : Array[@ast.Param]) -> Bool {
  for p in params {
    if p.is_rest_pattern {
      return true
    }
  }
  false
}

///|
fn check_duplicate_binding_name(
  seen : @set.Set[String],
  name : String,
) -> Unit raise Error {
  if seen.contains(name) {
    raise @errors.SyntaxError(
      message="Duplicate parameter name not allowed in this context",
    )
  }
  seen.add(name)
}

///|
fn check_duplicate_pattern_binding_names(
  pattern : @ast.Pattern,
  seen : @set.Set[String],
) -> Unit raise Error {
  for name in @static_semantics.bound_names(pattern) {
    check_duplicate_binding_name(seen, name)
  }
}

///|
fn check_duplicate_params_ext(
  params : Array[@ast.Param],
  rest_param : String?,
) -> Unit raise Error {
  let seen = @set.Set::default()
  let has_rest_pattern = params_have_rest_pattern(params)
  for p in params {
    match p.pattern {
      Some(pat) => check_duplicate_pattern_binding_names(pat, seen)
      None => check_duplicate_binding_name(seen, p.name)
    }
  }
  match rest_param {
    Some(rn) => if !has_rest_pattern { check_duplicate_binding_name(seen, rn) }
    None => ()
  }
}

///|
fn params_include_arguments(
  params : Array[String],
  rest_param : String?,
) -> Bool {
  for p in params {
    if p == "arguments" {
      return true
    }
  }
  rest_param is Some("arguments")
}

///|
fn pattern_includes_arguments(pattern : @ast.Pattern) -> Bool {
  let stack : Array[@ast.Pattern] = [pattern]
  while stack.length() > 0 {
    match stack.pop() {
      Some(IdentPat("arguments")) => return true
      Some(IdentPat(_)) | Some(AssignTarget(_)) => ()
      Some(DefaultPat(inner, _)) => stack.push(inner)
      Some(ArrayPat(elements, rest)) => {
        match rest {
          Some(rest) => stack.push(rest)
          None => ()
        }
        elements.rev_each(element => {
          match element {
            Some(child) => stack.push(child)
            None => ()
          }
        })
      }
      Some(ObjectPat(properties, rest)) => {
        match rest {
          Some(rest) => stack.push(rest)
          None => ()
        }
        properties.rev_each(property => stack.push(property.value))
      }
      None => ()
    }
  }
  false
}

///|
fn ext_params_include_arguments(
  params : Array[@ast.Param],
  rest_param : String?,
) -> Bool {
  for p in params {
    if p.name == "arguments" {
      return true
    }
    match p.pattern {
      Some(pat) => if pattern_includes_arguments(pat) { return true }
      None => ()
    }
  }
  rest_param is Some("arguments")
}

///|
fn validate_strict_pattern_binding_names(
  pattern : @ast.Pattern,
) -> Unit raise Error {
  for name in @static_semantics.bound_names(pattern) {
    @static_semantics.validate_strict_binding_name(name)
  }
}

///|
fn validate_strict_param_binding_names_ext(
  params : Array[@ast.Param],
  rest_param : String?,
) -> Unit raise Error {
  let has_rest_pattern = params_have_rest_pattern(params)
  for p in params {
    match p.pattern {
      Some(pat) => validate_strict_pattern_binding_names(pat)
      None => @static_semantics.validate_strict_binding_name(p.name)
    }
  }
  match rest_param {
    Some(rn) =>
      if !has_rest_pattern {
        @static_semantics.validate_strict_binding_name(rn)
      }
    None => ()
  }
}

///|
/// Apply parameter-list early errors for functions built via the
/// Function / GeneratorFunction / AsyncFunction / AsyncGeneratorFunction
/// constructors. Per spec these fire at construction time, not at call.
///
/// Three checks:
///   1. Non-simple parameter list with `"use strict"` directive in body
///      is a SyntaxError (§15.1.1 / §14.1.2).
///   2. Strict-mode (or non-simple) parameter list must not contain
///      duplicate names.
///   3. Strict-mode parameter names must not be `eval`, `arguments`,
///      or a strict-reserved word (§15.1.5).
pub fn validate_function_constructor_params(
  is_simple : Bool,
  param_names : Array[String],
  rest_name : String?,
  body : Array[@ast.Stmt],
) -> Unit raise Error {
  let body_has_use_strict = @static_semantics.has_use_strict(body)
  if !is_simple && body_has_use_strict {
    raise @errors.SyntaxError(
      message="Illegal 'use strict' directive in function with non-simple parameter list",
    )
  }
  let body_strict = body_has_use_strict
  if !is_simple || body_strict {
    let seen = @set.Set::default()
    for p in param_names {
      if seen.contains(p) {
        raise @errors.SyntaxError(
          message="Duplicate parameter name not allowed in this context",
        )
      }
      seen.add(p)
    }
    match rest_name {
      Some(n) =>
        if seen.contains(n) {
          raise @errors.SyntaxError(
            message="Duplicate parameter name not allowed in this context",
          )
        }
      None => ()
    }
  }
  if body_strict {
    for p in param_names {
      @static_semantics.validate_strict_binding_name(p)
    }
    match rest_name {
      Some(n) => @static_semantics.validate_strict_binding_name(n)
      None => ()
    }
  }
}

///|
pub fn validate_function_constructor_params_ext(
  params : Array[@ast.Param],
  rest_param : String?,
  body : Array[@ast.Stmt],
) -> Unit raise Error {
  if @static_semantics.has_use_strict(body) {
    raise @errors.SyntaxError(
      message="Illegal 'use strict' directive in function with non-simple parameter list",
    )
  }
  check_duplicate_params_ext(params, rest_param)
}

///|
fn is_function_strict(enclosing_strict : Bool, body : Array[@ast.Stmt]) -> Bool {
  @static_semantics.body_is_strict(body, enclosing_strict)
}

///|
/// §10.2.11 `ContainsExpression` / `HasParameterExpressions` for Ext
/// function parameter lists. True iff any param carries a default
/// initializer or a pattern that itself contains an expression
/// (nested default, computed key, or assignment target). A plain
/// destructuring pattern like `{a}` or `[a, b]` with no nested
/// initializers has NO expression per spec and must not trigger the
/// split; otherwise `function f({a}) { var a; return a }` would hoist
/// `var a` into body_env and shadow the destructured parameter.
/// Plain rest without a pattern is also not a parameter expression.
pub fn has_parameter_expressions(params : Array[@ast.Param]) -> Bool {
  for p in params {
    if p.default_val is Some(_) {
      return true
    }
    match p.pattern {
      Some(pat) => if pattern_contains_expression(pat) { return true }
      None => ()
    }
  }
  false
}

///|
priv enum ParameterExpressionWork {
  VisitParameterPattern(@ast.Pattern)
  ParameterExpressionMarker
}

///|
/// Returns true iff a BindingPattern contains any Initializer,
/// computed property key, or assignment target — i.e. the AST
/// subtrees the spec classifies as expressions inside
/// `ContainsExpression`.
fn pattern_contains_expression(pattern : @ast.Pattern) -> Bool {
  let work = [ParameterExpressionWork::VisitParameterPattern(pattern)]
  while work.pop() is Some(item) {
    match item {
      ParameterExpressionMarker => return true
      VisitParameterPattern(pattern) =>
        match pattern {
          IdentPat(_) => ()
          DefaultPat(_, _) => return true
          // AssignTarget shows up only for destructuring-assignment forms, not
          // BindingPattern parameters, but treat it as an expression defensively.
          AssignTarget(_) => return true
          ArrayPat(elements, rest) => {
            match rest {
              Some(rest) => work.push(VisitParameterPattern(rest))
              None => ()
            }
            for i = elements.length() - 1; i >= 0; i = i - 1 {
              match elements[i] {
                Some(element) => work.push(VisitParameterPattern(element))
                None => ()
              }
            }
          }
          ObjectPat(properties, rest) => {
            match rest {
              Some(rest) => work.push(VisitParameterPattern(rest))
              None => ()
            }
            for i = properties.length() - 1; i >= 0; i = i - 1 {
              let property = properties[i]
              work.push(VisitParameterPattern(property.value))
              if property.computed_key is Some(_) {
                work.push(ParameterExpressionMarker)
              }
              if property.default_val is Some(_) {
                work.push(ParameterExpressionMarker)
              }
            }
          }
        }
    }
  }
  false
}

///|
pub fn validate_function_signature(
  enclosing_strict : Bool,
  name : String?,
  params : Array[String],
  body : Array[@ast.Stmt],
) -> Unit raise Error {
  if is_function_strict(enclosing_strict, body) {
    match name {
      Some(n) => @static_semantics.validate_strict_binding_name(n)
      None => ()
    }
    check_duplicate_params(params)
    for p in params {
      @static_semantics.validate_strict_binding_name(p)
    }
  }
}

///|
pub fn validate_function_signature_ext(
  enclosing_strict : Bool,
  name : String?,
  params : Array[@ast.Param],
  rest_param : String?,
  body : Array[@ast.Stmt],
) -> Unit raise Error {
  if is_function_strict(enclosing_strict, body) {
    match name {
      Some(n) => @static_semantics.validate_strict_binding_name(n)
      None => ()
    }
    check_duplicate_params_ext(params, rest_param)
    validate_strict_param_binding_names_ext(params, rest_param)
  }
}

///|
fn should_reconcile_eval_function_decl(
  env : Environment,
  name : String,
) -> Bool {
  env.has_marker(EVAL_FUNCTION_RECONCILE_MARKER) && env.has_var(name)
}

///|
/// Mirror a binding to the global object (globalThis) as an own property.
/// This implements CreateGlobalVarBinding / CreateGlobalFunctionBinding per the
/// ES spec, where global `var` and function declarations become properties of
/// the global object.  `configurable` controls the property descriptor.
fn Interpreter::mirror_to_global(
  self : Interpreter,
  name : String,
  value : Value,
  configurable? : Bool = false,
) -> Unit {
  match self.global_this {
    Object(data) => {
      data.bag.properties[name] = value
      // Only set descriptor if not already set (preserve existing non-configurable)
      match data.bag.descriptors.get(name) {
        Some(existing) =>
          if existing.configurable {
            // Update value via property, descriptor stays
            ()
          }
        None =>
          data.bag.descriptors[name] = {
            writable: true,
            enumerable: true,
            configurable,
            getter: None,
            setter: None,
            is_accessor: false,
          }
      }
    }
    _ => ()
  }
}

///|
pub fn Interpreter::run(
  self : Interpreter,
  stmts : Array[@ast.Stmt],
) -> Value raise Error {
  let strict = @static_semantics.has_use_strict(stmts)
  let ctx : ExecContext = { strict, current_generator: None }
  try {
    let admission = self.classify_activation_dispatch_root_program(stmts)
    let result = match admission {
      ActivationDispatchManagedRoot(root) =>
        activation_dispatch_root_value(
          self.run_activation_dispatch_root_program(stmts, ctx, root),
        )
      ActivationDispatchLegacyRoot =>
        with_cleared_active_callee_realm(self.realm_state, () => {
          self.prepare_root_program_execution(stmts, self.global, strict)
          let mut last : Value = Undefined
          for stmt in stmts {
            match self.exec_stmt(ctx, stmt, self.global) {
              Normal(v) => last = v
              ReturnSignal(_) =>
                raise @errors.SyntaxError(
                  message="return statement outside of function",
                )
              sig => raise_if_break_continue(sig)
            }
          }
          last
        })
    }
    result
  } catch {
    e =>
      if is_js_catchable_error(e) {
        let translated = JsException(
          js_error_to_value_with_env(e, Some(self.global)),
        )
        remap_observed_source_failure(self.realm_state, e, translated)
        raise translated
      } else {
        raise e
      }
  }
}