///|
/// Evaluate FEEL against a JSON object. The optional clock supplies now/today.
pub fn feel_eval(
  source : String,
  context~ : String = "{}",
  host~ : Map[String, @types.Value] = Map([]),
  clock~ : (() -> @types.DateTime)? = None,
) -> String raise @types.EngineError {
  @eval.feel_eval(
    source,
    ctx=@eval.with_clock(host_scope(@types.json_context(context), host), clock),
  )
  .json()
  .stringify()
}

///|
/// Parse DMN 1.3/1.4/1.5 XML; failures carry code, line and column.
pub fn parse_model(
  xml : String,
  resolver~ : ((String, String) -> String raise @types.EngineError)? = None,
) -> @model.Model raise @types.EngineError {
  @model.parse_model(xml, resolver~)
}

///|
/// Evaluate a named decision with exact JSON numbers and an optional clock.
pub fn evaluate_decision(
  model : @model.Model,
  name : String,
  input : String,
  host~ : Map[String, @types.Value] = Map([]),
  clock~ : (() -> @types.DateTime)? = None,
) -> String raise @types.EngineError {
  @model.evaluate_decision(
    model,
    name,
    @eval.with_clock(host_scope(@model.json_inputs(model, input), host), clock),
  )
  .json()
  .stringify()
}

///|
/// Compile FEEL once; include input names containing spaces in names.
pub fn compile(
  source : String,
  names~ : Array[String] = [],
) -> @parser.Expr raise @types.EngineError {
  let scope = @builtins.lexer_names()
  scope.append(names)
  @parser.compile(source, names=scope)
}

///|
/// Reuse a compiled expression with fresh JSON input and optional clock.
pub fn evaluate_compiled(
  expression : @parser.Expr,
  input : String,
  clock~ : (() -> @types.DateTime)? = None,
) -> String raise @types.EngineError {
  @eval.evaluate(
    expression,
    @eval.with_clock(@types.json_context(input), clock),
  )
  .json()
  .stringify()
}

///|
/// Register a callback in a host scope passed to feel_eval/evaluate_decision.
pub fn register_external(
  host : Map[String, @types.Value],
  class_name : String,
  signature : String,
  callback : (Array[@types.Value]) -> @types.Value raise @types.EngineError,
) -> Unit {
  @eval.register_external(host, class_name, signature, callback)
}

///|
fn host_scope(
  inputs : Map[String, @types.Value],
  host : Map[String, @types.Value],
) -> Map[String, @types.Value] {
  if host.get("$external") is Some(v) {
    inputs["$external"] = v
  }
  inputs
}