///|
/// Bounded encoding cache: values are official JSON strings, never conclusions.
/// Saturation only disables insertion and does not affect output or analysis.
priv struct QuotedStrings {
  values : Map[String, String]
  mut units : Int
}

///|
let quoted_strings : QuotedStrings = { values: Map([]), units: 0, }

///|
fn cached_quote(value : String) -> String {
  match quoted_strings.values.get(value) {
    Some(text) => text
    None => {
      let text = value.to_json().stringify()
      if quoted_strings.values.length() < 2048 &&
        quoted_strings.units + value.length() + text.length() <= 131072 {
        quoted_strings.values[value] = text
        quoted_strings.units += value.length() + text.length()
      }
      text
    }
  }
}

///|
fn scalar_string(value : Json, strings : Map[String, String]) -> String {
  match value {
    String(s) => quoted_cached(strings, s)
    _ => value.stringify()
  }
}

///|
/// Stream the largest array instead of materializing its JSON object tree.
/// Public report_json remains available for structured MoonBit callers.
fn report_string(r : Report, extras? : Map[String, Json] = Map([])) -> String {
  let small = report_json({
    ..r,
    edges: [],
    coverage: [],
    inferred_orders: Map([]),
    order_sources: Map([]),
  })
  let fields = match small {
    Object(m) => m
    _ => abort("Report object")
  }
  for name, value in extras {
    fields[name] = value
  }
  match fields.at("stats") {
    Object(m) => m["dependencies"] = r.edges.length().to_json()
    _ => abort("Report stats")
  }
  let b = json_text()
  let strings : Map[String, String] = Map([])
  b.write_char('{')
  let mut first = true
  for name, value in fields {
    if !first {
      b.write_char(',')
    }
    first = false
    b.write_string(quoted_cached(strings, name))
    b.write_char(':')
    if name == "coverage" {
      write_coverage(b, r, strings)
    } else if name == "versionOrderSources" {
      b.write_char('{')
      let mut first_key = true
      for key, source in r.order_sources {
        if !first_key {
          b.write_char(',')
        }
        first_key = false
        b.write_string(quoted_cached(strings, key))
        b.write_char(':')
        b.write_string(quoted_cached(strings, source))
      }
      b.write_char('}')
    } else if name == "versionOrders" {
      b.write_char('{')
      let mut first_key = true
      for key, versions in r.inferred_orders {
        if !first_key {
          b.write_char(',')
        }
        first_key = false
        b.write_string(quoted_cached(strings, key))
        b.write_string(":[")
        for i, version in versions {
          if i > 0 {
            b.write_char(',')
          }
          b.write_string(quoted_cached(strings, version))
        }
        b.write_char(']')
      }
      b.write_char('}')
    } else if name == "edges" {
      b.write_char('[')
      for i, e in r.edges {
        if i > 0 {
          b.write_char(',')
        }
        write_edge(b, e, strings)
      }
      b.write_char(']')
    } else {
      b.write_string(scalar_string(value, strings))
    }
  }
  b.write_char('}')
  b.to_string()
}

///|
fn write_coverage(
  b : JsonText,
  r : Report,
  strings : Map[String, String],
) -> Unit {
  b.write_char('[')
  for i, row in r.coverage {
    if i > 0 {
      b.write_char(',')
    }
    let source = r.order_sources.at(row.key)
    b.write_string("{\"key\":")
    b.write_string(quoted_cached(strings, row.key))
    b.write_string(",\"committedReads\":")
    b.write_string(row.committed_reads.to_string())
    b.write_string(",\"committedFinalWrites\":")
    b.write_string(row.committed_final_writes.to_string())
    b.write_string(",\"unresolvedReads\":")
    b.write_string(row.unresolved_reads.to_string())
    b.write_string(",\"versionOrderSource\":")
    b.write_string(quoted_cached(strings, source))
    b.write_string(",\"sourceLabel\":")
    b.write_string(
      quoted_cached(
        strings,
        order_source_cn(source, row.committed_final_writes),
      ),
    )
    b.write_char('}')
  }
  b.write_char(']')
}

///|
fn quoted_cached(strings : Map[String, String], value : String) -> String {
  match strings.get(value) {
    Some(text) => text
    None => {
      let text = cached_quote(value)
      strings[value] = text
      text
    }
  }
}

///|
fn optional_quoted(strings : Map[String, String], value : String?) -> String {
  match value {
    Some(s) => quoted_cached(strings, s)
    None => "null"
  }
}

///|
fn optional_integer(value : Int?) -> String {
  match value {
    Some(i) => i.to_string()
    None => "null"
  }
}

///|
fn write_edge(b : JsonText, e : Edge, strings : Map[String, String]) -> Unit {
  b.write_string("{\"from\":")
  b.write_string(quoted_cached(strings, e.from))
  b.write_string(",\"to\":")
  b.write_string(quoted_cached(strings, e.to))
  b.write_string(",\"kind\":")
  b.write_string(quoted_cached(strings, dependency_label(e.kind)))
  b.write_string(",\"key\":")
  b.write_string(optional_quoted(strings, e.key))
  b.write_string(",\"fromOp\":")
  b.write_string(optional_integer(e.from_op))
  b.write_string(",\"toOp\":")
  b.write_string(optional_integer(e.to_op))
  b.write_string(",\"fromVersion\":")
  b.write_string(optional_quoted(strings, e.from_version))
  b.write_string(",\"toVersion\":")
  b.write_string(optional_quoted(strings, e.to_version))
  b.write_string(",\"reason\":")
  b.write_string(quoted_cached(strings, e.reason))
  b.write_char('}')
}