///|
/// 失败标签的稳定标识(用于 JSON 与报告,不要随展示文案改动)。
pub fn FailReason::label(self : FailReason) -> String {
  match self {
    GroupingMismatch => "grouping-mismatch"
    DigitSystemMismatch => "digit-system-mismatch"
    RoundingMismatch => "rounding-mismatch"
    PluralCategoryMismatch => "plural-category-mismatch"
    BidiControlMissing => "bidi-control-missing"
    CompactFormMismatch => "compact-form-mismatch"
    PatternSyntaxUnsupported => "pattern-syntax-unsupported"
    ValueMismatch => "value-mismatch"
    Other => "other"
  }
}

///|
/// 全部归因类别,顺序即报告中的展示顺序(分组类在前、兜底在后)。
pub fn FailReason::all() -> Array[FailReason] {
  [
    GroupingMismatch,
    DigitSystemMismatch,
    RoundingMismatch,
    PluralCategoryMismatch,
    BidiControlMissing,
    CompactFormMismatch,
    PatternSyntaxUnsupported,
    ValueMismatch,
    Other,
  ]
}

///|
/// 字符级最小差异:首个不同码元的位置与两侧片段。
///
/// 报告要给的是"一眼看出差在哪",而不是让人自己 diff 两串长文本。
pub(all) struct CharDiff {
  /// 首个不同码元的下标(0 起);两串完全相同时为 -1。
  index : Int
  /// 差异位置附近的期望片段(截断处用 `…` 标记)。
  expected_snippet : String
  actual_snippet : String
  expected_length : Int
  actual_length : Int
} derive(Debug, Eq)

///|
/// 逐码元比较,找出首个差异位置,并给出该位置两侧的片段。
///
/// 窗口半径固定为 14 个码元:足够看清差异,又不至于把整串塞进报告。
pub fn char_diff(expected : String, actual : String) -> CharDiff {
  let expected_length = expected.length()
  let actual_length = actual.length()
  let limit = if expected_length < actual_length {
    expected_length
  } else {
    actual_length
  }
  let mut index = 0
  while index < limit {
    if expected[index] != actual[index] {
      break
    }
    index += 1
  }
  if index == expected_length && index == actual_length {
    return {
      index: -1,
      expected_snippet: "",
      actual_snippet: "",
      expected_length,
      actual_length,
    }
  }
  let start = if index > 14 { index - 14 } else { 0 }
  let end = index + 14
  {
    index,
    expected_snippet: snippet(expected, start, end),
    actual_snippet: snippet(actual, start, end),
    expected_length,
    actual_length,
  }
}

///|
fn snippet(text : String, start : Int, end : Int) -> String {
  let prefix = if start > 0 { "…" } else { "" }
  let suffix = if end < text.length() { "…" } else { "" }
  prefix + text.clamped_view(start~, end~).to_owned() + suffix
}

///|
/// 按套件拆分的小计。
pub(all) struct SuiteSummary {
  suite : Suite
  summary : RunSummary
} derive(Debug, Eq)

///|
/// 某个失败原因的计数。
pub(all) struct ReasonCount {
  reason : FailReason
  count : Int
} derive(Debug, Eq)

///|
/// 一条「答错」的明细:报告里最有价值的部分。
pub(all) struct FailureDetail {
  suite : Suite
  source : String
  location : String
  reason : FailReason
  expected : String
  actual : String
  diff : CharDiff
} derive(Debug, Eq)

///|
/// 一条「抛异常」的明细。
///
/// 与答错分列存放:**抛异常与答错是两类缺陷**,混在一起会掩盖实现崩溃的问题。
pub(all) struct ErrorDetail {
  suite : Suite
  source : String
  location : String
  detail : String
} derive(Debug, Eq)

///|
/// 单个适配器的报告。
pub(all) struct AdapterReport {
  adapter_id : String
  summary : RunSummary
  by_suite : Array[SuiteSummary]
  by_reason : Array[ReasonCount]
  /// 失败的**样例**(上限见 `failure_sample_limit`);计数仍是完整的。
  failures : Array[FailureDetail]
  errors : Array[ErrorDetail]
} derive(Debug, Eq)

///|
/// 一次运行的完整报告。
pub(all) struct Report {
  /// CLDR 数据基线(不可变 tag)。报告脱离基线就没有意义。
  baseline : String
  adapters : Array[AdapterReport]
  summary : RunSummary
} derive(Debug, Eq)

///|
/// 每个适配器在报告里最多保留多少条明细。
///
/// 4714 条用例 × N 个适配器全量塞进 JSON 会有数 MB,而人看前几条就能定位问题;
/// 计数与归因分布始终是完整的,不受这个上限影响。
pub fn failure_sample_limit() -> Int {
  20
}

///|
/// 统计各原因的失败数(原因顺序稳定,只列入出现过的)。
fn reason_counts(results : Array[CaseResult]) -> Array[ReasonCount] {
  let counts : Array[ReasonCount] = []
  for target in FailReason::all() {
    let mut count = 0
    for result in results {
      match result.outcome {
        Fail(reason~, ..) => if reason == target { count += 1 }
        _ => ()
      }
    }
    if count > 0 {
      counts.push({ reason: target, count, })
    }
  }
  counts
}

///|
/// 按套件拆分统计(只列入出现过的套件)。
fn suite_summaries(results : Array[CaseResult]) -> Array[SuiteSummary] {
  let summaries : Array[SuiteSummary] = []
  for suite in Suite::all() {
    let mine = results.filter(result => result.suite == suite)
    if mine.length() > 0 {
      summaries.push({ suite, summary: RunSummary::from_results(mine), })
    }
  }
  summaries
}

///|
/// 从执行结果构建单个适配器的报告。
///
/// `limit` 为负时采用 `failure_sample_limit()`;需要看全部样例时传一个很大的数
/// (CLI 的 `--all-samples` 就是这么用的)。
pub fn AdapterReport::from_results(
  adapter_id : String,
  results : Array[CaseResult],
  limit? : Int = -1,
) -> AdapterReport {
  let cap = if limit < 0 { failure_sample_limit() } else { limit }
  let mine = results.filter(result => result.adapter_id == adapter_id)
  let failures : Array[FailureDetail] = []
  let errors : Array[ErrorDetail] = []
  for result in mine {
    match result.outcome {
      Fail(reason~, expected~, actual~) =>
        if failures.length() < cap {
          failures.push({
            suite: result.suite,
            source: result.source,
            location: result.location,
            reason,
            expected,
            actual,
            diff: char_diff(expected, actual),
          })
        }
      Errored(detail~) =>
        if errors.length() < cap {
          errors.push({
            suite: result.suite,
            source: result.source,
            location: result.location,
            detail,
          })
        }
      _ => ()
    }
  }
  {
    adapter_id,
    summary: RunSummary::from_results(mine),
    by_suite: suite_summaries(mine),
    by_reason: reason_counts(mine),
    failures,
    errors,
  }
}

///|
/// 从一次运行的全部结果构建报告。`adapter_ids` 决定顺序(即报告中的展示顺序)。
pub fn Report::from_results(
  baseline : String,
  adapter_ids : Array[String],
  results : Array[CaseResult],
  limit? : Int = -1,
) -> Report {
  let adapters : Array[AdapterReport] = []
  for adapter_id in adapter_ids {
    adapters.push(AdapterReport::from_results(adapter_id, results, limit~))
  }
  { baseline, adapters, summary: RunSummary::from_results(results), }
}

///|
fn json_summary(summary : RunSummary) -> Json {
  let fields : Map[String, Json] = Map([])
  fields["total"] = Json::number(summary.total.to_double())
  fields["passed"] = Json::number(summary.passed.to_double())
  fields["failed"] = Json::number(summary.failed.to_double())
  fields["errored"] = Json::number(summary.errored.to_double())
  fields["unsupported"] = Json::number(summary.unsupported.to_double())
  fields["not_installed"] = Json::number(summary.not_installed.to_double())
  fields["pass_rate"] = Json::string(summary.pass_rate_text())
  Json::object(fields)
}

///|
fn json_adapter(adapter : AdapterReport) -> Json {
  let fields : Map[String, Json] = Map([])
  fields["adapter_id"] = Json::string(adapter.adapter_id)
  fields["summary"] = json_summary(adapter.summary)
  let suites : Array[Json] = []
  for entry in adapter.by_suite {
    let item : Map[String, Json] = Map([])
    item["suite"] = Json::string(entry.suite.label())
    item["summary"] = json_summary(entry.summary)
    suites.push(Json::object(item))
  }
  fields["by_suite"] = Json::array(suites)
  let reasons : Array[Json] = []
  for entry in adapter.by_reason {
    let item : Map[String, Json] = Map([])
    item["reason"] = Json::string(entry.reason.label())
    item["count"] = Json::number(entry.count.to_double())
    reasons.push(Json::object(item))
  }
  fields["by_reason"] = Json::array(reasons)
  let failures : Array[Json] = []
  for failure in adapter.failures {
    let item : Map[String, Json] = Map([])
    item["suite"] = Json::string(failure.suite.label())
    item["source"] = Json::string(failure.source)
    item["location"] = Json::string(failure.location)
    item["reason"] = Json::string(failure.reason.label())
    item["expected"] = Json::string(failure.expected)
    item["actual"] = Json::string(failure.actual)
    let diff : Map[String, Json] = Map([])
    diff["index"] = Json::number(failure.diff.index.to_double())
    diff["expected_snippet"] = Json::string(failure.diff.expected_snippet)
    diff["actual_snippet"] = Json::string(failure.diff.actual_snippet)
    item["diff"] = Json::object(diff)
    failures.push(Json::object(item))
  }
  fields["failures"] = Json::array(failures)
  let errors : Array[Json] = []
  for error in adapter.errors {
    let item : Map[String, Json] = Map([])
    item["suite"] = Json::string(error.suite.label())
    item["source"] = Json::string(error.source)
    item["location"] = Json::string(error.location)
    item["detail"] = Json::string(error.detail)
    errors.push(Json::object(item))
  }
  fields["errors"] = Json::array(errors)
  Json::object(fields)
}

///|
/// 渲染为 JSON(缩进 2 空格,键名稳定)。
///
/// schema 顶层:`baseline` / `summary` / `adapters[]`,
/// 每个适配器含 `adapter_id` / `summary` / `by_suite[]` / `by_reason[]` /
/// `failures[]`(样例)/ `errors[]`(样例)。
pub fn Report::to_json(self : Report) -> String {
  let root : Map[String, Json] = Map([])
  root["baseline"] = Json::string(self.baseline)
  root["summary"] = json_summary(self.summary)
  let adapters : Array[Json] = []
  for adapter in self.adapters {
    adapters.push(json_adapter(adapter))
  }
  root["adapters"] = Json::array(adapters)
  Json::object(root).stringify(indent=2)
}

///|
/// 渲染为 Markdown:矩阵 + 归因分布 + 失败样例(带字符级差异)。
pub fn Report::to_markdown(self : Report) -> String {
  let out = StringBuilder()
  out.write_string("# CLDR 一致性报告\n\n")
  out.write_string(
    "数据基线 `\{self.baseline}`;用例总数 \{self.summary.total}。\n\n",
  )
  out.write_string("## 一致率矩阵\n\n")
  out.write_string(
    "| 适配器 | 套件 | 用例 | 一致率 | pass | fail | errored | unsupported | not-installed |\n",
  )
  out.write_string("|---|---|---|---|---|---|---|---|---|\n")
  for adapter in self.adapters {
    for entry in adapter.by_suite {
      let summary = entry.summary
      out.write_string(
        "| `\{adapter.adapter_id}` | \{entry.suite.label()} | \{summary.total} | \{summary.pass_rate_text()} | \{summary.passed} | \{summary.failed} | \{summary.errored} | \{summary.unsupported} | \{summary.not_installed} |\n",
      )
    }
    let summary = adapter.summary
    out.write_string(
      "| **`\{adapter.adapter_id}`** | **合计** | **\{summary.total}** | **\{summary.pass_rate_text()}** | **\{summary.passed}** | **\{summary.failed}** | **\{summary.errored}** | **\{summary.unsupported}** | **\{summary.not_installed}** |\n",
    )
  }
  out.write_string("\n## 失败归因分布\n\n")
  for adapter in self.adapters {
    out.write_string("### `\{adapter.adapter_id}`\n\n")
    if adapter.by_reason.length() == 0 {
      out.write_string("无失败。\n\n")
      continue
    }
    out.write_string("| 原因 | 次数 |\n|---|---|\n")
    for entry in adapter.by_reason {
      out.write_string("| \{entry.reason.label()} | \{entry.count} |\n")
    }
    out.write_string("\n")
  }
  out.write_string("## 失败样例\n\n")
  out.write_string(
    "> 每个适配器最多展示 \{failure_sample_limit()} 条;计数与分布始终是完整的。\n\n",
  )
  for adapter in self.adapters {
    out.write_string("### `\{adapter.adapter_id}`\n\n")
    if adapter.failures.length() == 0 && adapter.errors.length() == 0 {
      out.write_string("无失败样例。\n\n")
      continue
    }
    for failure in adapter.failures {
      out.write_string(
        "- `\{failure.source}` \{failure.location} · **\{failure.reason.label()}**\n",
      )
      out.write_string("  - 期望 `\{failure.expected}`\n")
      out.write_string("  - 实际 `\{failure.actual}`\n")
      out.write_string(
        "  - 首个差异在第 \{failure.diff.index} 个码元:`\{failure.diff.expected_snippet}` → `\{failure.diff.actual_snippet}`\n",
      )
    }
    for error in adapter.errors {
      out.write_string(
        "- `\{error.source}` \{error.location} · **errored**:\{error.detail}\n",
      )
    }
    out.write_string("\n")
  }
  out.to_string()
}