///|
/// A host object, not the representation of MoonBit Json, Option, Map or Array.
#cfg(target="js")
#external
pub type NativeReportObject

///|
#cfg(target="js")
extern "js" fn native_report_metadata(text : String) -> NativeReportObject =
  #| (text) => JSON.parse(text)

///|
/// Each optional field has a primitive presence flag. Even Some("") and Some(0)
/// are distinct from None, without exposing the Option ABI to JavaScript.
#cfg(target="js")
extern "js" fn native_report_append_edge(
  report : NativeReportObject,
  from : String,
  to : String,
  kind : String,
  has_key : Bool,
  key : String,
  has_from_op : Bool,
  from_op : Int32,
  has_to_op : Bool,
  to_op : Int32,
  has_from_version : Bool,
  from_version : String,
  has_to_version : Bool,
  to_version : String,
  reason : String,
) -> Unit =
  #| (report, from, to, kind, hasKey, key, hasFromOp, fromOp, hasToOp, toOp, hasFromVersion, fromVersion, hasToVersion, toVersion, reason) => {
  #|   report.edges.push({from, to, kind, key: hasKey ? key : null,
  #|     fromOp: hasFromOp ? fromOp : null, toOp: hasToOp ? toOp : null,
  #|     fromVersion: hasFromVersion ? fromVersion : null,
  #|     toVersion: hasToVersion ? toVersion : null, reason});
  #| }

///|
#cfg(target="js")
fn native_report_object(
  report : Report,
  extras~ : Map[String, Json] = Map([]),
) -> NativeReportObject {
  // This newly created metadata JSON contains complete findings, coverage,
  // version orders and witness. Only the largest top-level edge array is typed.
  let metadata = report_json({ ..report, edges: [], })
  match metadata {
    Object(fields) => {
      for name, value in extras {
        fields[name] = value
      }
      match fields.at("stats") {
        Object(stats) => stats["dependencies"] = Json(report.edges.length())
        _ => abort("Report stats")
      }
    }
    _ => abort("Report object")
  }
  let result = native_report_metadata(metadata.stringify())
  for edge in report.edges {
    native_report_append_edge(
      result,
      edge.from,
      edge.to,
      dependency_label(edge.kind),
      edge.key is Some(_),
      edge.key.unwrap_or(""),
      edge.from_op is Some(_),
      edge.from_op.unwrap_or(0),
      edge.to_op is Some(_),
      edge.to_op.unwrap_or(0),
      edge.from_version is Some(_),
      edge.from_version.unwrap_or(""),
      edge.to_version is Some(_),
      edge.to_version.unwrap_or(""),
      edge.reason,
    )
  }
  result
}

///|
/// Experimental JS transport. Input validation and analysis are unchanged.
#cfg(target="js")
pub fn analyze_native_js_v1(text : String) -> NativeReportObject {
  match parse_history(text) {
    Ok(history) => native_report_object(analyze(history))
    Err(message) => native_report_object(input_error_report(message))
  }
}

///|
/// The existing options parser and all effective budgets are reused exactly.
#cfg(target="js")
pub fn analyze_options_native_js_v1(
  text : String,
  options_text : String,
) -> NativeReportObject {
  let result = options_input(text, options_text)
  native_report_object(result.report, extras=analysis_extras(result))
}