///|
// Executor-neutral function creation capability.  The runtime owns the
// request shape and callable shell; compiler code may provide a materializer
// without importing compiler representations into this package.
pub(all) enum FunctionMaterializationForm {
  FunctionDeclaration
  FunctionExpression
  NamedFunctionExpression
  MethodFunction
  ComputedMethodFunction
  ArrowFunction
} derive(Eq)

///|
pub extend FunctionMaterializationForm with Eq::{equal, not_equal}

///|
pub(all) enum FunctionMaterializationParameters {
  SimpleFunctionParameters(Array[String])
  ExtendedFunctionParameters(Array[@ast.Param], String?)
}

///|
pub struct FunctionMaterializationRequest {
  form : FunctionMaterializationForm
  // The parser-owned coordinate identifies the exact syntax site being
  // materialized. Candidate routing treats it as a locator only; the
  // immutable candidate source identity remains authoritative.
  site : @token.Loc
  name : String?
  parameters : FunctionMaterializationParameters
  body : Array[@ast.Stmt]
  closure : Environment
  strict : Bool
  source_text : String?
}

///|
// Runtime-owned lifecycle hooks let a compiler-provided capability record
// the actual activation boundary without exposing compiler representations to
// this package.
pub struct FunctionExecutionHooks {
  priv start : () -> Unit raise Error
  priv complete : (Bool) -> Unit raise Error
}

///|
pub fn FunctionExecutionHooks::FunctionExecutionHooks(
  start~ : () -> Unit raise Error,
  complete~ : (Bool) -> Unit raise Error,
) -> FunctionExecutionHooks {
  { start, complete, }
}

///|
pub fn FunctionMaterializationRequest::FunctionMaterializationRequest(
  form~ : FunctionMaterializationForm,
  site~ : @token.Loc,
  name~ : String?,
  parameters~ : FunctionMaterializationParameters,
  body~ : Array[@ast.Stmt],
  closure~ : Environment,
  strict~ : Bool,
  source_text~ : String?,
) -> FunctionMaterializationRequest {
  { form, site, name, parameters, body, closure, strict, source_text, }
}

///|
pub struct FunctionMaterializer {
  priv begin : () -> FunctionMaterializer
  priv materialize : (Interpreter, FunctionMaterializationRequest) -> Value raise Error
  priv hooks : FunctionExecutionHooks?
}

///|
pub fn FunctionMaterializer::FunctionMaterializer(
  begin~ : () -> FunctionMaterializer,
  materialize~ : (Interpreter, FunctionMaterializationRequest) -> Value raise Error,
  hooks? : FunctionExecutionHooks? = None,
) -> FunctionMaterializer {
  { begin, materialize, hooks, }
}

///|
// Retain only source-owned function materialization for deferred syntax such
// as instance field initializers. Lifecycle hooks belong to the activation
// that is currently executing and must not be retained after it completes.
fn Interpreter::capture_deferred_function_materializer(
  self : Interpreter,
) -> FunctionMaterializer? {
  self.active_function_materializer.map(materializer => {
    begin: materializer.begin,
    materialize: materializer.materialize,
    hooks: None,
  })
}

///|
fn function_materialization_name(
  form : FunctionMaterializationForm,
  name : String?,
) -> String {
  match form {
    ArrowFunction => ""
    _ => name.unwrap_or("")
  }
}

///|
fn function_materialization_is_method(
  form : FunctionMaterializationForm,
) -> Bool {
  match form {
    MethodFunction | ComputedMethodFunction => true
    _ => false
  }
}

///|
fn function_materialization_has_name_binding(
  form : FunctionMaterializationForm,
) -> Bool {
  form is NamedFunctionExpression
}

///|
// Materialize a normal tree-walker callable from an executor-neutral request.
// The optional child capability is retained by the resulting function value,
// so later activations do not consult an Engine-global candidate program.
pub fn make_materialized_tree_function(
  request : FunctionMaterializationRequest,
  child_materializer : FunctionMaterializer?,
) -> Value {
  let name = function_materialization_name(request.form, request.name)
  let is_method = function_materialization_is_method(request.form)
  let has_name_binding = function_materialization_has_name_binding(request.form)
  match request.parameters {
    SimpleFunctionParameters(params) => {
      let data : FuncData = {
        name: request.name,
        params,
        body: request.body,
        closure: request.closure,
        strict: request.strict,
        has_name_binding,
        is_method,
        source_text: request.source_text,
        materializer: child_materializer,
      }
      match request.form {
        ArrowFunction =>
          build_func_object(
            name,
            data.params.length(),
            ArrowFunc(data),
            realm_state=request.closure.realm_state,
          )
        _ => make_func(data)
      }
    }
    ExtendedFunctionParameters(params, rest_param) => {
      let data : FuncDataExt = {
        name: request.name,
        params,
        rest_param,
        body: request.body,
        closure: request.closure,
        strict: request.strict,
        has_name_binding,
        is_method,
        source_text: request.source_text,
        materializer: child_materializer,
      }
      match request.form {
        ArrowFunction =>
          build_func_object(
            name,
            expected_argument_count_ext(data.params),
            ArrowFuncExt(data),
            realm_state=request.closure.realm_state,
          )
        _ => make_func_ext(data)
      }
    }
  }
}

///|
// Select the candidate capability for a newly evaluated function definition.
// With no active capability this is exactly the legacy tree-walker factory.
fn Interpreter::materialize_function(
  self : Interpreter,
  request : FunctionMaterializationRequest,
) -> Value raise Error {
  match self.active_function_materializer {
    Some(materializer) => (materializer.materialize)(self, request)
    None => make_materialized_tree_function(request, None)
  }
}

///|
// Install a capability only for the dynamic extent of one root/function
// activation. Restoration is performed on both normal and abrupt exits.
pub fn[T] Interpreter::with_function_materializer(
  self : Interpreter,
  materializer : FunctionMaterializer?,
  thunk : () -> T raise Error,
) -> T raise Error {
  let previous = self.active_function_materializer
  self.active_function_materializer = materializer
  let result : T = {
    errdefer {
      self.active_function_materializer = previous
    }
    thunk()
  }
  self.active_function_materializer = previous
  result
}

///|
// Run one UserFunc/ArrowFunc activation with its own immutable materializer
// capability. The capability's lifecycle hooks mark the actual
// start/completion boundary; no caller can transition a started activation to
// another executor.
pub fn[T] Interpreter::run_materialized_function(
  self : Interpreter,
  materializer : FunctionMaterializer?,
  thunk : () -> T raise Error,
) -> T raise Error {
  let active = materializer.map(capability => (capability.begin)())
  let hooks : FunctionExecutionHooks? = match active {
    Some(capability) => capability.hooks
    None => None
  }
  match hooks {
    Some(h) => (h.start)()
    None => ()
  }
  let previous = self.active_function_materializer
  self.active_function_materializer = active
  let result : T = {
    errdefer {
      self.active_function_materializer = previous
      match hooks {
        Some(h) => (h.complete)(false)
        None => ()
      }
    }
    thunk()
  }
  self.active_function_materializer = previous
  match hooks {
    Some(h) => (h.complete)(true)
    None => ()
  }
  result
}

///|
fn callable_has_function_materializer(value : Value) -> Bool {
  retained_function_materializer(value) is Some(_)
}

///|
fn retained_function_materializer(value : Value) -> FunctionMaterializer? {
  match value {
    Object(data) =>
      match data.callable {
        Some(UserFunc(func_data)) => func_data.materializer
        Some(UserFuncExt(func_data)) => func_data.materializer
        Some(ArrowFunc(func_data)) => func_data.materializer
        Some(ArrowFuncExt(func_data)) => func_data.materializer
        _ => None
      }
    _ => None
  }
}

///|
// Candidate tree roots intentionally bypass the legacy activation-dispatch
// admission gate. Their selected tree route is already settled by the
// compiler, and this method only supplies the ordinary root shell plus the
// capability that materializes independent child functions.
pub fn Interpreter::run_with_function_materializer(
  self : Interpreter,
  stmts : Array[@ast.Stmt],
  materializer : FunctionMaterializer,
) -> Value raise Error {
  let strict = @static_semantics.has_use_strict(stmts)
  let ctx : ExecContext = { strict, current_generator: None, }
  self.run_materialized_function(Some(materializer), fn() raise {
    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
    })
  }) catch {
    error =>
      if is_js_catchable_error(error) {
        let translated = JsException(
          js_error_to_value_with_env(error, Some(self.global)),
        )
        remap_observed_source_failure(self.realm_state, error, translated)
        raise translated
      } else {
        raise error
      }
  }
}