///|
/// Output formats supported by the command-line report adapter.
pub enum CliFormat {
  Text
  Markdown
  Json
  Csv
} derive(Debug, Eq)

///|
/// Parsed command-line options for embedding the library in small tools.
pub struct CliOptions {
  mut input : String?
  mut output : String?
  mut signal_id : String?
  mut start : Int64?
  mut end : Int64?
  mut format : CliFormat
  mut strict : Bool
  mut include_unknown : Bool
  mut benchmark : Bool
  mut help : Bool
} derive(Debug, Eq)

///|
/// Return conservative defaults suitable for a terminal invocation.
pub fn default_cli_options() -> CliOptions {
  {
    input: None,
    output: None,
    signal_id: None,
    start: None,
    end: None,
    format: Text,
    strict: false,
    include_unknown: true,
    benchmark: false,
    help: false,
  }
}

///|
/// Parse options without depending on an operating-system argument parser.
/// Unknown switches are intentionally ignored so applications can add their
/// own flags while reusing this package's report options.
pub fn parse_cli_options(args : Array[String]) -> CliOptions {
  let result = default_cli_options()
  let mut index = 0
  while index < args.length() {
    let arg = args[index]
    if arg == "--help" || arg == "-h" {
      result.help = true
    } else if arg == "--json" {
      result.format = Json
    } else if arg == "--markdown" || arg == "-m" {
      result.format = Markdown
    } else if arg == "--csv" {
      result.format = Csv
    } else if arg == "--strict" {
      result.strict = true
    } else if arg == "--known-only" {
      result.include_unknown = false
    } else if arg == "--benchmark" {
      result.benchmark = true
    } else if arg == "--signal" && index + 1 < args.length() {
      index += 1
      result.signal_id = Some(args[index])
    } else if arg == "--output" && index + 1 < args.length() {
      index += 1
      result.output = Some(args[index])
    } else if arg == "--start" && index + 1 < args.length() {
      index += 1
      result.start = parse_cli_int(args[index])
    } else if arg == "--end" && index + 1 < args.length() {
      index += 1
      result.end = parse_cli_int(args[index])
    } else if arg.has_prefix("--start=") {
      result.start = parse_cli_int(arg.get_view(start=8).unwrap().to_owned())
    } else if arg.has_prefix("--end=") {
      result.end = parse_cli_int(arg.get_view(start=6).unwrap().to_owned())
    } else if arg.has_prefix("-") {
      ()
    } else if result.input is None {
      result.input = Some(arg)
    }
    index += 1
  }
  result
}

///|
fn parse_cli_int(text : String) -> Int64? {
  if text.trim().to_owned() == "" {
    return None
  }
  let mut value : Int64 = 0L
  for char in text.trim() {
    if !char.is_ascii_digit() {
      return None
    }
    value = value * 10L + (char.to_int().to_int64() - 48L)
  }
  Some(value)
}

///|
/// Render concise command help for documentation and shell completion.
pub fn cli_usage() -> String {
  [
    "moonbit-vcd [options] ", "  --signal        restrict reports to one identifier",
    "  --start       inclusive start time", "  --end         inclusive end time",
    "  --json              emit deterministic JSON", "  --markdown          emit Markdown",
    "  --csv               emit change rows", "  --known-only        omit unknown and high-impedance values",
    "  --strict            enable validation diagnostics", "  --benchmark         run the built-in synthetic benchmark",
    "  -h, --help          show this help",
  ].join("\n")
}

///|
/// Select changes according to the parsed time and signal filters.
pub fn apply_cli_window(trace : Trace, options : CliOptions) -> Trace {
  let start = match options.start {
    Some(value) => value
    None => 0L
  }
  let end = match options.end {
    Some(value) => value
    None => trace.end_time
  }
  let changes = trace.changes.filter(fn(change) {
    let selected_signal = match options.signal_id {
      Some(id) => id == change.id
      None => true
    }
    let selected_value = options.include_unknown || !change.value.is_unknown()
    selected_signal &&
    selected_value &&
    change.time >= start &&
    change.time <= end
  })
  { header: trace.header, signals: trace.signals, changes, end_time: end }
}

///|
/// Render a trace according to CLI options.
pub fn cli_report(trace : Trace, options : CliOptions) -> String {
  if options.help {
    return cli_usage()
  }
  if options.benchmark {
    return benchmark_suite_text(benchmark_suite(8, 128, 20))
  }
  let selected = apply_cli_window(trace, options)
  match options.format {
    Text =>
      if options.strict {
        extended_text_report(selected, default_report_options())
      } else {
        text_report(selected)
      }
    Markdown => markdown_report(selected, markdown_report_options())
    Json => trace_json(selected)
    Csv =>
      match options.signal_id {
        Some(id) => changes_csv(selected, signal_id=id)
        None => changes_csv(selected)
      }
  }
}

///|
/// Return the selected time range in a shell-friendly representation.
pub fn cli_range(options : CliOptions) -> String {
  let start = match options.start {
    Some(value) => value
    None => 0L
  }
  let end = match options.end {
    Some(value) => value.to_string()
    None => "*"
  }
  start.to_string() + ".." + end
}

///|
/// Validate the relationship between CLI bounds before a report is run.
pub fn cli_options_valid(options : CliOptions) -> Bool {
  match (options.start, options.end) {
    (Some(start), Some(end)) => start <= end
    _ => true
  }
}

///|
/// Explain an invalid range without throwing from a command-line adapter.
pub fn cli_options_diagnostic(options : CliOptions) -> String {
  if cli_options_valid(options) {
    "ok"
  } else {
    "--start must be less than or equal to --end"
  }
}