///|
/// Output format for machine and human reports.
pub enum ReportFormat {
  Text
  Markdown
  Json
  Csv
} derive(Debug, Eq)

///|
/// Options controlling report content.
pub struct ReportOptions {
  format : ReportFormat
  include_changes : Bool
  include_validation : Bool
  signal_pattern : String?
} derive(Debug, Eq)

///|
/// Create a complete text-report configuration.
pub fn default_report_options() -> ReportOptions {
  {
    format: Text,
    include_changes: false,
    include_validation: true,
    signal_pattern: None,
  }
}

///|
/// Create Markdown report options.
pub fn markdown_report_options() -> ReportOptions {
  {
    format: Markdown,
    include_changes: false,
    include_validation: true,
    signal_pattern: None,
  }
}

///|
/// Create JSON report options.
pub fn json_report_options() -> ReportOptions {
  {
    format: Json,
    include_changes: true,
    include_validation: true,
    signal_pattern: None,
  }
}

///|
/// Create CSV report options.
pub fn csv_report_options() -> ReportOptions {
  {
    format: Csv,
    include_changes: true,
    include_validation: false,
    signal_pattern: None,
  }
}

///|
/// Render a trace using the requested report format.
pub fn render_report(trace : Trace, options : ReportOptions) -> String {
  let selected = match options.signal_pattern {
    Some(pattern) => filter_trace_signals(trace, pattern)
    None => trace
  }
  match options.format {
    Text => extended_text_report(selected, options)
    Markdown => markdown_report(selected, options)
    Json => extended_json_report(selected, options)
    Csv => changes_csv(selected)
  }
}

///|
/// Render a Markdown summary with tables for all declared signals.
pub fn markdown_report(trace : Trace, options : ReportOptions) -> String {
  let out : Array[String] = [
    "# MoonBit VCD report",
    "",
    "| Metric | Value |",
    "| --- | ---: |",
    "| Signals | " + trace.signals.length().to_string() + " |",
    "| Changes | " + trace.changes.length().to_string() + " |",
    "| End time | " + trace.end_time.to_string() + " |",
    "",
    "| Signal | Changes | Rising | Falling | Unknown |",
    "| --- | ---: | ---: | ---: | ---: |",
  ]
  for signal in trace.signals {
    let item = stats(trace, signal.id)
    out.push(
      "| " +
      signal_path(signal) +
      " | " +
      item.changes.to_string() +
      " | " +
      item.rising_edges.to_string() +
      " | " +
      item.falling_edges.to_string() +
      " | " +
      item.unknown_values.to_string() +
      " |",
    )
  }
  if options.include_validation {
    out.push("")
    out.push("## Validation")
    out.push("")
    out.push(diagnostics_report(deep_validate(trace)))
  }
  if options.include_changes {
    out.push("")
    out.push("## Changes")
    out.push("")
    out.push("```")
    out.push(changes_csv(trace).trim_end().to_owned())
    out.push("```")
  }
  out.join("\n")
}

///|
/// Render a text report with analysis highlights.
pub fn extended_text_report(trace : Trace, options : ReportOptions) -> String {
  let out : Array[String] = [
    text_report(trace),
    "timescale: " + trace.time_scale().display(),
    "scope depth: " + maximum_scope_depth(trace).to_string(),
  ]
  for signal in trace.signals {
    let frequency = frequency_estimate(trace, signal.id)
    let activity_item = activity(trace, signal.id)
    out.push(
      "  " +
      signal_path(signal) +
      " active=" +
      activity_item.active_time.to_string() +
      " transitions=" +
      activity_item.transition_count.to_string() +
      " periods=" +
      frequency.period_count.to_string(),
    )
  }
  if options.include_validation {
    out.push("validation: " + diagnostics_report(deep_validate(trace)))
  }
  out.join("\n")
}

///|
/// Render a report-oriented JSON object.
pub fn extended_json_report(trace : Trace, options : ReportOptions) -> String {
  let signals = trace.signals
    .map(fn(signal) {
      let item = stats(trace, signal.id)
      "{\"path\":\"" +
      escape_text(signal_path(signal)) +
      "\",\"id\":\"" +
      escape_text(signal.id) +
      "\",\"changes\":" +
      item.changes.to_string() +
      ",\"rising\":" +
      item.rising_edges.to_string() +
      ",\"falling\":" +
      item.falling_edges.to_string() +
      ",\"unknown\":" +
      item.unknown_values.to_string() +
      "}"
    })
    .join(",")
  let validation = if options.include_validation {
    let (errors, warnings) = diagnostic_counts(deep_validate(trace))
    ",\"errors\":" +
    errors.to_string() +
    ",\"warnings\":" +
    warnings.to_string()
  } else {
    ""
  }
  "{\"signals\":[" +
  signals +
  "],\"changes\":" +
  trace.changes.length().to_string() +
  ",\"end_time\":" +
  trace.end_time.to_string() +
  validation +
  "}"
}

///|
/// Render each signal's statistics as CSV.
pub fn stats_csv(trace : Trace) -> String {
  let out : Array[String] = ["path,id,changes,rising,falling,unknown"]
  for signal in trace.signals {
    let item = stats(trace, signal.id)
    out.push(
      escape_text(signal_path(signal)) +
      "," +
      escape_text(signal.id) +
      "," +
      item.changes.to_string() +
      "," +
      item.rising_edges.to_string() +
      "," +
      item.falling_edges.to_string() +
      "," +
      item.unknown_values.to_string(),
    )
  }
  out.join("\n")
}

///|
/// Return a one-line summary intended for command-line tools.
pub fn summary_line(trace : Trace) -> String {
  "signals=" +
  trace.signals.length().to_string() +
  " changes=" +
  trace.changes.length().to_string() +
  " end=" +
  trace.end_time.to_string() +
  " valid=" +
  deep_is_valid(trace).to_string()
}