///|
/// Named scalar metrics exposed to dashboards and CI summaries.
pub struct AnalysisMetric {
  name : String
  signal_id : String?
  value : Double
  count : Int
} derive(Debug, Eq)

///|
/// Calculate quality and activity metrics for one signal.
pub fn signal_analysis_metrics(
  trace : Trace,
  signal_id : String,
) -> Array[AnalysisMetric] {
  let distribution = value_distribution(trace, signal_id)
  let activity_item = activity(trace, signal_id)
  let frequency = frequency_estimate(trace, signal_id)
  let mean_period = match frequency.mean_period {
    Some(value) => value
    None => 0.0
  }
  [
    {
      name: "known_ratio",
      signal_id: Some(signal_id),
      value: known_value_ratio(trace, distribution.signal_id),
      count: distribution.total,
    },
    {
      name: "change_rate",
      signal_id: Some(distribution.signal_id),
      value: change_rate(trace, signal_id),
      count: activity_item.transition_count,
    },
    {
      name: "active_fraction",
      signal_id: Some(signal_id),
      value: if trace.end_time == 0L {
        0.0
      } else {
        activity_item.active_time.to_double() / trace.end_time.to_double()
      },
      count: activity_item.samples,
    },
    {
      name: "mean_period",
      signal_id: Some(signal_id),
      value: mean_period,
      count: frequency.period_count,
    },
    {
      name: "unknown_ratio",
      signal_id: Some(signal_id),
      value: unknown_value_ratio(trace, signal_id),
      count: distribution.unknown + distribution.high_impedance,
    },
  ]
}

///|
/// Calculate metrics for every declared signal in declaration order.
pub fn trace_analysis_metrics(trace : Trace) -> Array[AnalysisMetric] {
  let result : Array[AnalysisMetric] = []
  for signal in trace.signals {
    result.append(signal_analysis_metrics(trace, signal.id))
  }
  result
}

///|
/// Render metrics as a stable CSV document.
pub fn analysis_metrics_csv(metrics : Array[AnalysisMetric]) -> String {
  let out : Array[String] = ["signal_id,metric,value,count"]
  for metric in metrics {
    let signal_id = match metric.signal_id {
      Some(value) => value
      None => ""
    }
    out.push(
      escape_text(signal_id) +
      "," +
      escape_text(metric.name) +
      "," +
      metric.value.to_string() +
      "," +
      metric.count.to_string(),
    )
  }
  out.join("\n")
}

///|
/// Render metrics as a compact JSON array.
pub fn analysis_metrics_json(metrics : Array[AnalysisMetric]) -> String {
  let body = metrics
    .map(fn(metric) {
      let signal_id = match metric.signal_id {
        Some(value) => escape_text(value)
        None => ""
      }
      "{\"signal_id\":\"" +
      signal_id +
      "\",\"metric\":\"" +
      escape_text(metric.name) +
      "\",\"value\":" +
      metric.value.to_string() +
      ",\"count\":" +
      metric.count.to_string() +
      "}"
    })
    .join(",")
  "[" + body + "]"
}

///|
/// Return a bounded quality score in the range [0, 100].
pub fn trace_quality_score(trace : Trace) -> Int {
  if trace.signals.length() == 0 {
    return 0
  }
  let mut total = 0.0
  for signal in trace.signals {
    let known = known_value_ratio(trace, signal.id)
    let validation = if deep_is_valid(trace) { 1.0 } else { 0.5 }
    total += known * validation
  }
  Int::max(
    0,
    Int::min(100, (total / trace.signals.length().to_double() * 100.0).to_int()),
  )
}

///|
/// Produce a compact quality report for verification logs.
pub fn trace_quality_report(trace : Trace) -> String {
  "quality=" +
  trace_quality_score(trace).to_string() +
  "/100 signals=" +
  trace.signals.length().to_string() +
  " valid=" +
  deep_is_valid(trace).to_string() +
  " unknown_changes=" +
  trace.changes.count_if(fn(change) { change.value.is_unknown() }).to_string()
}

///|
/// Return the number of metrics in a catalog.
pub fn analysis_metric_count(metrics : Array[AnalysisMetric]) -> Int {
  metrics.length()
}

///|
/// Find a metric by name and signal identifier.
pub fn find_analysis_metric(
  metrics : Array[AnalysisMetric],
  signal_id : String,
  name : String,
) -> AnalysisMetric? {
  for metric in metrics {
    match metric.signal_id {
      Some(id) if id == signal_id && metric.name == name => return Some(metric)
      _ => ()
    }
  }
  None
}