///|
/// JSON string escaping for stable dependency-free reports.
pub fn json_escape(value : String) -> String {
  let builder = StringBuilder::new()
  for char in value {
    match char {
      '"' => builder.write_string("\\\"")
      '\\' => builder.write_string("\\\\")
      '\n' => builder.write_string("\\n")
      '\r' => builder.write_string("\\r")
      '\t' => builder.write_string("\\t")
      '\b' => builder.write_string("\\b")
      '\u{0C}' => builder.write_string("\\f")
      _ => builder.write_char(char)
    }
  }
  builder.to_string()
}

///|
/// Write a quoted JSON string.
fn write_json_string(builder : StringBuilder, value : String) -> Unit {
  builder.write_char('"')
  builder.write_string(json_escape(value))
  builder.write_char('"')
}

///|
/// Write an optional JSON string.
fn write_optional_string(builder : StringBuilder, value : String?) -> Unit {
  match value {
    Some(text) => write_json_string(builder, text)
    None => builder.write_string("null")
  }
}

///|
/// Write an optional double.
fn write_optional_double(builder : StringBuilder, value : Double?) -> Unit {
  match value {
    Some(number) => builder.write_object(number)
    None => builder.write_string("null")
  }
}

///|
/// Write an optional timestamp in ISO-8601 form.
fn write_optional_timestamp(builder : StringBuilder, value : Int64?) -> Unit {
  match value {
    Some(timestamp) => write_json_string(builder, format_iso8601(timestamp))
    None => builder.write_string("null")
  }
}

///|
/// Serialize a diagnostic as JSON.
fn write_diagnostic_json(
  builder : StringBuilder,
  diagnostic : Diagnostic,
) -> Unit {
  builder.write_string("{\"level\":")
  write_json_string(builder, diagnostic_level_name(diagnostic.level))
  builder.write_string(",\"code\":")
  write_json_string(builder, diagnostic.code)
  builder.write_string(",\"message\":")
  write_json_string(builder, diagnostic.message)
  builder.write_string(",\"line\":")
  match diagnostic.line {
    Some(line) => builder.write_object(line)
    None => builder.write_string("null")
  }
  builder.write_string(",\"sensor_id\":")
  write_optional_string(builder, diagnostic.sensor_id)
  builder.write_char('}')
}

///|
/// Serialize one event as JSON.
fn write_event_json(builder : StringBuilder, event : ExcursionEvent) -> Unit {
  builder.write_string("{\"event_id\":")
  write_json_string(builder, event.event_id)
  builder.write_string(",\"sensor_id\":")
  write_json_string(builder, event.sensor_id)
  builder.write_string(",\"kind\":")
  write_json_string(builder, excursion_kind_name(event.kind))
  builder.write_string(",\"status\":")
  write_json_string(
    builder,
    match event.status {
      Candidate => "candidate"
      Confirmed => "confirmed"
      Suppressed => "suppressed"
      Closed => "closed"
    },
  )
  builder.write_string(",\"started_at\":")
  write_json_string(builder, format_iso8601(event.started_at))
  builder.write_string(",\"ended_at\":")
  write_json_string(builder, format_iso8601(event.ended_at))
  builder.write_string(",\"duration_seconds\":")
  builder.write_object(event.duration_seconds)
  builder.write_string(",\"sample_count\":")
  builder.write_object(event.sample_count)
  builder.write_string(",\"minimum_c\":")
  write_optional_double(builder, event.minimum_c)
  builder.write_string(",\"maximum_c\":")
  write_optional_double(builder, event.maximum_c)
  builder.write_string(",\"mean_c\":")
  write_optional_double(builder, event.mean_c)
  builder.write_string(",\"degree_seconds\":")
  builder.write_object(event.degree_seconds)
  builder.write_string(",\"peak_deviation_c\":")
  builder.write_object(event.peak_deviation_c)
  builder.write_string(",\"reason\":")
  write_json_string(builder, event.reason)
  builder.write_char('}')
}

///|
/// Serialize one statistics record as JSON.
fn write_statistics_json(
  builder : StringBuilder,
  statistics : SensorStatistics,
) -> Unit {
  builder.write_string("{\"sensor_id\":")
  write_json_string(builder, statistics.sensor_id)
  builder.write_string(",\"sample_count\":")
  builder.write_object(statistics.sample_count)
  builder.write_string(",\"usable_count\":")
  builder.write_object(statistics.usable_count)
  builder.write_string(",\"first_timestamp\":")
  write_optional_timestamp(builder, statistics.first_timestamp)
  builder.write_string(",\"last_timestamp\":")
  write_optional_timestamp(builder, statistics.last_timestamp)
  builder.write_string(",\"minimum_c\":")
  write_optional_double(builder, statistics.minimum_c)
  builder.write_string(",\"maximum_c\":")
  write_optional_double(builder, statistics.maximum_c)
  builder.write_string(",\"mean_c\":")
  write_optional_double(builder, statistics.mean_c)
  builder.write_string(",\"time_weighted_mean_c\":")
  write_optional_double(builder, statistics.time_weighted_mean_c)
  builder.write_string(",\"mean_kinetic_temperature_c\":")
  write_optional_double(builder, statistics.mean_kinetic_temperature_c)
  builder.write_string(",\"standard_deviation_c\":")
  write_optional_double(builder, statistics.standard_deviation_c)
  builder.write_string(",\"median_c\":")
  write_optional_double(builder, statistics.median_c)
  builder.write_string(",\"p05_c\":")
  write_optional_double(builder, statistics.p05_c)
  builder.write_string(",\"p95_c\":")
  write_optional_double(builder, statistics.p95_c)
  builder.write_string(",\"in_range_seconds\":")
  builder.write_object(statistics.in_range_seconds)
  builder.write_string(",\"low_seconds\":")
  builder.write_object(statistics.low_seconds)
  builder.write_string(",\"high_seconds\":")
  builder.write_object(statistics.high_seconds)
  builder.write_string(",\"unknown_seconds\":")
  builder.write_object(statistics.unknown_seconds)
  builder.write_string(",\"low_degree_seconds\":")
  builder.write_object(statistics.low_degree_seconds)
  builder.write_string(",\"high_degree_seconds\":")
  builder.write_object(statistics.high_degree_seconds)
  builder.write_char('}')
}

///|
/// Serialize one sensor health summary as JSON.
fn write_health_json(builder : StringBuilder, health : SensorHealth) -> Unit {
  builder.write_string("{\"sensor_id\":")
  write_json_string(builder, health.sensor_id)
  builder.write_string(",\"score\":")
  builder.write_object(health.score)
  builder.write_string(",\"stuck_runs\":")
  builder.write_object(health.stuck_runs)
  builder.write_string(",\"noisy_steps\":")
  builder.write_object(health.noisy_steps)
  builder.write_string(",\"drift_windows\":")
  builder.write_object(health.drift_windows)
  builder.write_string(",\"gaps\":")
  builder.write_object(health.gaps)
  builder.write_string(",\"conflicts\":")
  builder.write_object(health.conflicts)
  builder.write_string(",\"low_battery_samples\":")
  builder.write_object(health.low_battery_samples)
  builder.write_string(",\"flagged_samples\":")
  builder.write_object(health.flagged_samples)
  builder.write_string(",\"observations\":[")
  for index = 0; index < health.observations.length(); index = index + 1 {
    if index > 0 {
      builder.write_char(',')
    }
    write_json_string(builder, health.observations[index])
  }
  builder.write_string("]}")
}

///|
/// Serialize risk details as JSON.
fn write_risk_json(builder : StringBuilder, risk : RiskAssessment) -> Unit {
  builder.write_string("{\"score\":")
  builder.write_object(risk.score)
  builder.write_string(",\"band\":")
  write_json_string(builder, risk_band_name(risk.band))
  builder.write_string(",\"confidence_percent\":")
  builder.write_object(risk.confidence_percent)
  builder.write_string(",\"temperature_component\":")
  builder.write_object(risk.temperature_component)
  builder.write_string(",\"duration_component\":")
  builder.write_object(risk.duration_component)
  builder.write_string(",\"data_quality_component\":")
  builder.write_object(risk.data_quality_component)
  builder.write_string(",\"sensor_health_component\":")
  builder.write_object(risk.sensor_health_component)
  builder.write_string(",\"reasons\":[")
  for index = 0; index < risk.reasons.length(); index = index + 1 {
    if index > 0 {
      builder.write_char(',')
    }
    write_json_string(builder, risk.reasons[index])
  }
  builder.write_string("]}")
}

///|
/// Stable complete JSON report.
pub fn report_to_json(report : AnalysisReport) -> String {
  let builder = StringBuilder::new()
  builder.write_string("{\"schema\":")
  write_json_string(builder, report.schema)
  builder.write_string(",\"shipment_id\":")
  write_json_string(builder, report.shipment_id)
  builder.write_string(",\"source_name\":")
  write_json_string(builder, report.source_name)
  builder.write_string(",\"generated_at\":")
  write_json_string(builder, format_iso8601(report.generated_at))
  builder.write_string(",\"sensor_ids\":[")
  for index = 0; index < report.sensor_ids.length(); index = index + 1 {
    if index > 0 {
      builder.write_char(',')
    }
    write_json_string(builder, report.sensor_ids[index])
  }
  builder.write_string("],\"summary\":{\"reading_count\":")
  builder.write_object(report.readings.length())
  builder.write_string(",\"gap_count\":")
  builder.write_object(report.gaps.length())
  builder.write_string(",\"event_count\":")
  builder.write_object(report.events.length())
  builder.write_string("},\"risk\":")
  write_risk_json(builder, report.risk)
  builder.write_string(",\"statistics\":[")
  for index = 0; index < report.statistics.length(); index = index + 1 {
    if index > 0 {
      builder.write_char(',')
    }
    write_statistics_json(builder, report.statistics[index])
  }
  builder.write_string("],\"health\":[")
  for index = 0; index < report.health.length(); index = index + 1 {
    if index > 0 {
      builder.write_char(',')
    }
    write_health_json(builder, report.health[index])
  }
  builder.write_string("],\"events\":[")
  for index = 0; index < report.events.length(); index = index + 1 {
    if index > 0 {
      builder.write_char(',')
    }
    write_event_json(builder, report.events[index])
  }
  builder.write_string("],\"diagnostics\":[")
  for index = 0; index < report.diagnostics.length(); index = index + 1 {
    if index > 0 {
      builder.write_char(',')
    }
    write_diagnostic_json(builder, report.diagnostics[index])
  }
  builder.write_string("]}")
  builder.to_string()
}

///|
/// Compact terminal summary.
pub fn report_summary(report : AnalysisReport) -> String {
  let builder = StringBuilder::new()
  builder.write_string("ThermoTrail analysis for ")
  builder.write_string(report.shipment_id)
  builder.write_char('\n')
  builder.write_string("Risk: ")
  builder.write_string(risk_band_name(report.risk.band))
  builder.write_string(" (")
  builder.write_object(report.risk.score)
  builder.write_string("/100, confidence ")
  builder.write_object(report.risk.confidence_percent)
  builder.write_string("%)\nSensors: ")
  builder.write_object(report.sensor_ids.length())
  builder.write_string(", readings: ")
  builder.write_object(report.readings.length())
  builder.write_string(", events: ")
  builder.write_object(report.events.length())
  builder.write_string(", gaps: ")
  builder.write_object(report.gaps.length())
  builder.write_char('\n')
  for reason in report.risk.reasons {
    builder.write_string("- ")
    builder.write_string(reason)
    builder.write_char('\n')
  }
  builder.to_string()
}

///|
/// Human-reviewable Markdown report.
pub fn report_to_markdown(report : AnalysisReport) -> String {
  let builder = StringBuilder::new()
  builder.write_string("# ThermoTrail 冷链分析报告\n\n")
  builder.write_string("- 运输编号:")
  builder.write_string(report.shipment_id)
  builder.write_string("\n- 数据来源:")
  builder.write_string(report.source_name)
  builder.write_string("\n- 生成时间:")
  builder.write_string(format_iso8601(report.generated_at))
  builder.write_string("\n- 报告格式:")
  builder.write_string(report.schema)
  builder.write_string("\n\n## 风险结论\n\n")
  builder.write_string("- 风险等级:**")
  builder.write_string(risk_band_name(report.risk.band))
  builder.write_string("**\n- 风险分数:")
  builder.write_object(report.risk.score)
  builder.write_string("/100\n- 数据置信度:")
  builder.write_object(report.risk.confidence_percent)
  builder.write_string("%\n\n")
  for reason in report.risk.reasons {
    builder.write_string("- ")
    builder.write_string(reason)
    builder.write_char('\n')
  }
  builder.write_string("\n## 传感器统计\n\n")
  builder.write_string(
    "| 传感器 | 样本 | 最低温 | 最高温 | 平均温 | MKT | 范围内时长 |\n",
  )
  builder.write_string("|---|---:|---:|---:|---:|---:|---:|\n")
  for item in report.statistics {
    builder.write_string("| ")
    builder.write_string(item.sensor_id)
    builder.write_string(" | ")
    builder.write_object(item.sample_count)
    builder.write_string(" | ")
    match item.minimum_c {
      Some(value) => builder.write_object(value)
      None => builder.write_string("—")
    }
    builder.write_string(" | ")
    match item.maximum_c {
      Some(value) => builder.write_object(value)
      None => builder.write_string("—")
    }
    builder.write_string(" | ")
    match item.mean_c {
      Some(value) => builder.write_object(value)
      None => builder.write_string("—")
    }
    builder.write_string(" | ")
    match item.mean_kinetic_temperature_c {
      Some(value) => builder.write_object(value)
      None => builder.write_string("—")
    }
    builder.write_string(" | ")
    builder.write_string(format_duration(item.in_range_seconds))
    builder.write_string(" |\n")
  }
  builder.write_string("\n## 事件时间线\n\n")
  if report.events.length() == 0 {
    builder.write_string("未检测到温度偏差或数据缺口事件。\n")
  } else {
    builder.write_string(
      "| 开始 | 传感器 | 类型 | 状态 | 时长 | 峰值偏差 |\n",
    )
    builder.write_string("|---|---|---|---|---:|---:|\n")
    for event in report.events {
      builder.write_string("| ")
      builder.write_string(format_iso8601(event.started_at))
      builder.write_string(" | ")
      builder.write_string(event.sensor_id)
      builder.write_string(" | ")
      builder.write_string(excursion_kind_name(event.kind))
      builder.write_string(" | ")
      builder.write_string(
        match event.status {
          Candidate => "candidate"
          Confirmed => "confirmed"
          Suppressed => "suppressed"
          Closed => "closed"
        },
      )
      builder.write_string(" | ")
      builder.write_string(format_duration(event.duration_seconds))
      builder.write_string(" | ")
      builder.write_object(event.peak_deviation_c)
      builder.write_string(" |\n")
    }
  }
  builder.write_string("\n## 传感器健康\n\n")
  for health in report.health {
    builder.write_string("### ")
    builder.write_string(health.sensor_id)
    builder.write_string("(")
    builder.write_object(health.score)
    builder.write_string("/100)\n\n")
    for observation in health.observations {
      builder.write_string("- ")
      builder.write_string(observation)
      builder.write_char('\n')
    }
    builder.write_char('\n')
  }
  builder.write_string("## 使用限制\n\n")
  builder.write_string(
    "本报告用于离线风险筛查和技术分析,不替代医疗、食品或监管机构的最终合规判断。\n",
  )
  builder.to_string()
}

///|
/// Machine-friendly event CSV.
pub fn events_to_csv(events : Array[ExcursionEvent]) -> String {
  let builder = StringBuilder::new()
  builder.write_string(
    "event_id,sensor_id,kind,status,started_at,ended_at,duration_seconds,sample_count,minimum_c,maximum_c,mean_c,degree_seconds,peak_deviation_c,reason\n",
  )
  for event in events {
    builder.write_string(csv_escape(event.event_id))
    builder.write_char(',')
    builder.write_string(csv_escape(event.sensor_id))
    builder.write_char(',')
    builder.write_string(excursion_kind_name(event.kind))
    builder.write_char(',')
    builder.write_string(
      match event.status {
        Candidate => "candidate"
        Confirmed => "confirmed"
        Suppressed => "suppressed"
        Closed => "closed"
      },
    )
    builder.write_char(',')
    builder.write_string(format_iso8601(event.started_at))
    builder.write_char(',')
    builder.write_string(format_iso8601(event.ended_at))
    builder.write_char(',')
    builder.write_object(event.duration_seconds)
    builder.write_char(',')
    builder.write_object(event.sample_count)
    builder.write_char(',')
    match event.minimum_c {
      Some(value) => builder.write_object(value)
      None => ()
    }
    builder.write_char(',')
    match event.maximum_c {
      Some(value) => builder.write_object(value)
      None => ()
    }
    builder.write_char(',')
    match event.mean_c {
      Some(value) => builder.write_object(value)
      None => ()
    }
    builder.write_char(',')
    builder.write_object(event.degree_seconds)
    builder.write_char(',')
    builder.write_object(event.peak_deviation_c)
    builder.write_char(',')
    builder.write_string(csv_escape(event.reason))
    builder.write_char('\n')
  }
  builder.to_string()
}