///|
pub(all) struct InvocationEntry {
  fqname : String
  generated_name : String
  inputs : Array[@execution.ValueType]
  output : @execution.ValueType
  input_codecs : Array[String]
  output_codec : String
}

///|
fn invocation_entry(ctx : Lowering, d : Declaration) -> InvocationEntry? {
  if !d.public {
    return None
  }
  let inputs = []
  for (_, t) in d.inputs {
    if !boundary_supported(ctx, t, Map([])) {
      return None
    }
    guard boundary_type(t) is Some(v) else { return None }
    inputs.push(v)
  }
  if !boundary_supported(ctx, d.output, Map([])) {
    return None
  }
  guard boundary_type(d.output) is Some(output) else { return None }
  Some({
    fqname: d.fqname.to_canonical(),
    generated_name: ctx.names.names[value_node(d.fqname)],
    inputs,
    output,
    input_codecs: d.inputs.map(p => decoder(p.1)),
    output_codec: encoder(d.output),
  })
}

///|
/// A separate consumer package. Only entry selection affects its source.
pub fn invocation_driver(
  output : Output,
  entries : Array[String],
) -> Array[Artifact] raise GenerationError {
  let names = entries.copy()
  names.sort()
  let cases = []
  let seen = Map([])
  for name in names {
    if seen.contains(name) {
      continue
    }
    seen[name] = true
    guard output.invocations.filter(e => e.fqname == name).get(0) is Some(entry) else {
      raise Invalid(
        "moonbit.unsupported_entry", name, "A public entry with supported boundary types is required",
      )
    }
    let arguments = []
    for i = 0; i < entry.inputs.length(); i = i + 1 {
      arguments.push(
        entry.input_codecs[i] + "(call.arguments[" + i.to_string() + "])",
      )
    }
    let mut call = "@model." + entry.generated_name + "()"
    for argument in arguments {
      call = "{ let function = " + call + "; function(" + argument + ") }"
    }
    let wrapped = entry.output_codec + "(" + call + ")"
    cases.push(
      quote(name) +
      " => { if call.arguments.length() != " +
      entry.inputs.length().to_string() +
      " { raise @execution.Invalid(\"execution.wrong_arity\") }; " +
      wrapped +
      " }",
    )
  }
  let source = output.invocation_codecs +
    (
      #|///|
      #|fn record_field(items : Array[(String, @execution.Value)], name : String) -> @execution.Value raise {
      #|  let fields = items.filter(p => p.0 == name)
      #|  if fields.length() != 1 { raise @execution.Invalid("execution.record_field") }
      #|  fields[0].1
      #|}
      #|///|
      #|fn invoke_value(call : @execution.Call) -> @execution.Value raise {
      #|  match call.entry {
      #|
    )
  let ending =
    #|    _ => raise @execution.Invalid("execution.unknown_entry")
    #|  }
    #|}
    #|///|
    #|pub fn invoke(call : @execution.Call) -> @execution.Value raise {
    #|  invoke_value(call) catch {
    #|    @sdk.DivisionByZero => @execution.ModelError("sdk.division_by_zero")
    #|    @sdk.InvalidNumber(_) => @execution.ModelError("sdk.invalid_number")
    #|    @sdk.OutOfRange(_) => @execution.ModelError("sdk.out_of_range")
    #|    @sdk.InvalidComparison(_) => @execution.ModelError("sdk.invalid_comparison")
    #|    @sdk.UnrepresentableText(_) => @execution.ModelError("sdk.unrepresentable_text")
  let decimal_errors = if output.project.dependencies.any(d => {
      d.api_profile.contains("+decimal-checked-v1")
    }) {
    "\n    @decimal.DivisionByZero => @execution.ModelError(\"sdk.decimal.division_by_zero\")\n" +
    "    @decimal.Underflow => @execution.ModelError(\"sdk.decimal.underflow\")\n" +
    "    @decimal.PrecisionLoss => @execution.ModelError(\"sdk.decimal.precision_loss\")\n" +
    "    @decimal.InvalidLiteral => @execution.ModelError(\"sdk.decimal.invalid_literal\")\n"
  } else {
    "\n"
  }
  let ending = ending +
    decimal_errors +
    (
      #|    error => raise error
      #|  }
      #|}
      #|
    )
  let float_import = if output.invocation_codecs.contains("@source_float.") {
    "\"finos/morphir-sdk/source_float\" @source_float,\n"
  } else {
    ""
  }
  [
    {
      path: "moon.mod",
      role: ModuleManifest,
      content: Text(
        "name = \"morphir-generated/driver\"\nversion = \"0.0.0\"\nimport {\n" +
        quote(output.project.module_name + "@0.0.0") +
        ",\n\"finos/morphir-sdk@0.1.0\",\n\"finos/morphir-execution@0.1.0\",\n\"moonrockz/ion@0.6.0\",\n\"moonbitlang/x@0.5.6\",\n}\n",
      ),
    },
    {
      path: "moon.pkg",
      role: PackageManifest,
      content: Text(
        "import {\n" +
        quote(output.project.module_name) +
        " @model,\n\"finos/morphir-sdk\" @sdk,\n\"finos/morphir-sdk/int\",\n\"finos/morphir-sdk/integer\",\n\"finos/morphir-sdk/decimal\",\n\"finos/morphir-sdk/list\",\n" +
        float_import +
        "\"finos/morphir-execution\" @execution,\n\"moonbitlang/core/bigint\",\n}\nwarnings = \"-16-29\"\n",
      ),
    },
    {
      path: "driver.mbt",
      role: Source,
      content: Text(source + cases.join("\n") + "\n" + ending),
    },
  ]
}