///|
/// query --json 的写出层(jumbit 扩展,上游无此语义):键序固定、单行数组,
/// 字节级可测;不引 @json 依赖(写出侧只需一个固定形状)。

///|
/// RFC 8259 字符串转义:引号/反斜杠/控制字符(< 0x20),其余 Unicode 原样透传
/// (输出为 UTF-8,合法 JSON 不必 \u 化非 ASCII)
fn json_escape(s : String) -> String {
  let out : StringBuilder = StringBuilder()
  for ch in s {
    match ch {
      '"' => out.write_string("\\\"")
      '\\' => out.write_string("\\\\")
      '\n' => out.write_string("\\n")
      '\r' => out.write_string("\\r")
      '\t' => out.write_string("\\t")
      '\u{0008}' => out.write_string("\\b")
      '\u{000C}' => out.write_string("\\f")
      c =>
        if c < '\u{0020}' {
          let hex = c.to_int().to_string(radix=16).pad_start(4, '0')
          out.write_string("\\u\{hex}")
        } else {
          out.write_char(c)
        }
    }
  }
  out.to_string()
}

///|
/// JSON 数字:NaN/±Infinity 不是合法 JSON 数值(import 可带进异常 rank),出 null;
/// 有限数走 double_short 最短表示(与 --tsv 的 score 列共用)
fn json_number(v : Double) -> String {
  if v.is_nan() || v.is_inf() {
    "null"
  } else {
    double_short(v)
  }
}

///|
/// 有限浮点最短表示(4.0→"4"、0.75→"0.75"):--json 与 --tsv 共用的数字形态
fn double_short(v : Double) -> String {
  v.to_string()
}

///|
/// 单条 JSON 对象({path, score, last_accessed, matched_by})
fn json_row(d : @core.Dir, now : Int64, matched_by : String) -> String {
  let out : StringBuilder = StringBuilder()
  out.write_string("{\"path\":\"")
  out.write_string(json_escape(d.path))
  out.write_string("\",\"score\":")
  out.write_string(json_number(d.score(now)))
  out.write_string(",\"last_accessed\":")
  out.write_string(d.last_accessed.to_string())
  out.write_string(",\"matched_by\":\"")
  out.write_string(matched_by)
  out.write_string("\"}")
  out.to_string()
}

///|
/// 单行 JSON 数组:每条 {path, score, last_accessed, matched_by}。
/// matched_by ∈ exact/fuzzy(fuzzy 由模糊兜底路接通,提交前恒 exact)。
/// 保持整体物化:单行契约(无内嵌换行)使流式冲刷必须用无尾换行写,
/// 与 moon test 的 stdout 事件协议冲突(见 docs/perf-import-optimization.md)
fn render_json(
  dirs : Array[@core.Dir],
  now : Int64,
  matched_by : String,
) -> String {
  let out : StringBuilder = StringBuilder()
  out.write_string("[")
  for i, d in dirs {
    if i > 0 {
      out.write_string(",")
    }
    out.write_string(json_row(d, now, matched_by))
  }
  out.write_string("]")
  out.to_string()
}