///|
fn main {
  println(sketch_cli_run(sketch_drop_program_name(@env.args())))
}

///|
fn sketch_cli_run(args : Array[String]) -> String {
  if args.length() == 0 {
    return sketch_cli_help()
  }
  match args[0] {
    "help" | "--help" | "-h" => sketch_cli_help()
    "version" | "--version" => "moon-sketch-kit 0.1.0"
    "report" => sketch_cli_report(args)
    "json" => sketch_cli_json(args)
    "topk" => sketch_cli_topk(args)
    "count" => sketch_cli_count(args)
    "sample" => sketch_cli_sample(args)
    "similarity" => sketch_cli_similarity(args)
    "bloom" => sketch_cli_bloom(args)
    "histogram" => sketch_cli_histogram(args)
    "quantile" => sketch_cli_quantile(args)
    "drift" => sketch_cli_drift(args)
    "gate" => sketch_cli_gate(args)
    "weighted" => sketch_cli_weighted(args)
    "windows" => sketch_cli_windows(args)
    "timeline" => sketch_cli_timeline(args)
    "profile" => sketch_cli_profile(args)
    "budget" => sketch_cli_budget(args)
    other => "Unknown command: " + other + "\n\n" + sketch_cli_help()
  }
}

///|
fn sketch_cli_help() -> String {
  (
    #|moon-sketch-kit - streaming sketch utilities for MoonBit
    #|
    #|Usage:
    #|  moon run cmd/main -- report 
    #|  moon run cmd/main -- json 
    #|  moon run cmd/main -- topk  [k]
    #|  moon run cmd/main -- count  
    #|  moon run cmd/main -- sample  [size]
    #|  moon run cmd/main -- similarity  
    #|  moon run cmd/main -- bloom 
    #|  moon run cmd/main -- histogram  [min] [max] [buckets]
    #|  moon run cmd/main -- quantile 
    #|  moon run cmd/main -- drift  
    #|  moon run cmd/main -- gate  
    #|  moon run cmd/main -- weighted 
    #|  moon run cmd/main -- windows 
    #|  moon run cmd/main -- timeline 
    #|  moon run cmd/main -- profile  [prefix-length]
    #|  moon run cmd/main -- budget  [accuracy-level]
    #|
    #|Events can be separated by spaces, commas, tabs, or newlines.
    #|Window commands also accept `|` or `;` as window separators.
  )
}

///|
fn sketch_cli_report(args : Array[String]) -> String {
  match sketch_cli_arg(args, 1) {
    Some(input) => @sketch.sketch_report_markdown(sketch_decode_newlines(input))
    None => "Usage: moon run cmd/main -- report "
  }
}

///|
fn sketch_cli_json(args : Array[String]) -> String {
  match sketch_cli_arg(args, 1) {
    Some(input) => @sketch.sketch_report_json(sketch_decode_newlines(input))
    None => "Usage: moon run cmd/main -- json "
  }
}

///|
fn sketch_cli_topk(args : Array[String]) -> String {
  match sketch_cli_arg(args, 1) {
    Some(input) => {
      let k = sketch_cli_int_arg(args, 2, 5)
      @sketch.topk_markdown(
        @sketch.topk_from_items(
          @sketch.parse_events(sketch_decode_newlines(input)),
          k,
        ),
      )
    }
    None => "Usage: moon run cmd/main -- topk  [k]"
  }
}

///|
fn sketch_cli_count(args : Array[String]) -> String {
  match (sketch_cli_arg(args, 1), sketch_cli_arg(args, 2)) {
    (Some(input), Some(key)) => {
      let sketch = @sketch.count_min_from_items(
        @sketch.parse_events(sketch_decode_newlines(input)),
        64,
        5,
      )
      @sketch.count_min_summary_markdown(sketch, [key])
    }
    _ => "Usage: moon run cmd/main -- count  "
  }
}

///|
fn sketch_cli_sample(args : Array[String]) -> String {
  match sketch_cli_arg(args, 1) {
    Some(input) => {
      let size = sketch_cli_int_arg(args, 2, 5)
      @sketch.reservoir_markdown(
        @sketch.reservoir_from_items(
          @sketch.parse_events(sketch_decode_newlines(input)),
          size,
        ),
      )
    }
    None => "Usage: moon run cmd/main -- sample  [size]"
  }
}

///|
fn sketch_cli_similarity(args : Array[String]) -> String {
  match (sketch_cli_arg(args, 1), sketch_cli_arg(args, 2)) {
    (Some(left), Some(right)) =>
      @sketch.jaccard_report(
        sketch_decode_newlines(left),
        sketch_decode_newlines(right),
      )
    _ => "Usage: moon run cmd/main -- similarity  "
  }
}

///|
fn sketch_cli_bloom(args : Array[String]) -> String {
  match sketch_cli_arg(args, 1) {
    Some(input) => {
      let events = @sketch.parse_events(sketch_decode_newlines(input))
      @sketch.bloom_markdown(@sketch.bloom_from_items(events, 512, 4), events)
    }
    None => "Usage: moon run cmd/main -- bloom "
  }
}

///|
fn sketch_cli_histogram(args : Array[String]) -> String {
  match sketch_cli_arg(args, 1) {
    Some(input) =>
      @sketch.histogram_markdown(
        @sketch.histogram_from_text(
          sketch_decode_newlines(input),
          sketch_cli_int_arg(args, 2, 0),
          sketch_cli_int_arg(args, 3, 100),
          sketch_cli_int_arg(args, 4, 10),
        ),
      )
    None =>
      "Usage: moon run cmd/main -- histogram  [min] [max] [buckets]"
  }
}

///|
fn sketch_cli_quantile(args : Array[String]) -> String {
  match sketch_cli_arg(args, 1) {
    Some(input) =>
      @sketch.numeric_summary_markdown(
        @sketch.quantile_from_text(sketch_decode_newlines(input)),
      )
    None => "Usage: moon run cmd/main -- quantile "
  }
}

///|
fn sketch_cli_drift(args : Array[String]) -> String {
  match (sketch_cli_arg(args, 1), sketch_cli_arg(args, 2)) {
    (Some(baseline), Some(candidate)) =>
      @sketch.stream_drift_markdown(
        "baseline",
        sketch_decode_newlines(baseline),
        "candidate",
        sketch_decode_newlines(candidate),
      )
    _ =>
      "Usage: moon run cmd/main -- drift  "
  }
}

///|
fn sketch_cli_gate(args : Array[String]) -> String {
  match (sketch_cli_arg(args, 1), sketch_cli_arg(args, 2)) {
    (Some(baseline), Some(candidate)) => {
      let report = @sketch.evaluate_stream_gate_default(
        sketch_decode_newlines(baseline),
        sketch_decode_newlines(candidate),
      )
      @sketch.sketch_gate_markdown(report) +
      "\n" +
      @sketch.recommendations_markdown(
        @sketch.recommendations_from_gate(report),
      )
    }
    _ => "Usage: moon run cmd/main -- gate  "
  }
}

///|
fn sketch_cli_weighted(args : Array[String]) -> String {
  match sketch_cli_arg(args, 1) {
    Some(input) => {
      let summary = @sketch.weighted_summary_from_text(
        sketch_decode_newlines(input),
      )
      @sketch.weighted_summary_markdown(summary) +
      "\n" +
      @sketch.recommendations_markdown(
        @sketch.recommendations_from_weighted(summary),
      )
    }
    None => "Usage: moon run cmd/main -- weighted "
  }
}

///|
fn sketch_cli_windows(args : Array[String]) -> String {
  match sketch_cli_arg(args, 1) {
    Some(input) => {
      let report = @sketch.windowed_report_from_text(
        sketch_decode_newlines(input),
      )
      @sketch.windowed_report_markdown(report) +
      "\n" +
      @sketch.recommendations_markdown(
        @sketch.recommendations_from_windowed(report),
      )
    }
    None => "Usage: moon run cmd/main -- windows "
  }
}

///|
fn sketch_cli_timeline(args : Array[String]) -> String {
  match sketch_cli_arg(args, 1) {
    Some(input) =>
      @sketch.windowed_topk_timeline_markdown(
        @sketch.windowed_report_from_text(sketch_decode_newlines(input)),
      )
    None => "Usage: moon run cmd/main -- timeline "
  }
}

///|
fn sketch_cli_profile(args : Array[String]) -> String {
  match sketch_cli_arg(args, 1) {
    Some(input) => {
      let profile = @sketch.token_profile_from_text(
        sketch_decode_newlines(input),
        sketch_cli_int_arg(args, 2, 3),
      )
      @sketch.token_profile_markdown(profile) +
      "\n" +
      @sketch.recommendations_markdown(
        @sketch.token_profile_recommendations(profile),
      )
    }
    None => "Usage: moon run cmd/main -- profile  [prefix-length]"
  }
}

///|
fn sketch_cli_budget(args : Array[String]) -> String {
  match sketch_cli_arg(args, 1) {
    Some(input) => {
      let plan = @sketch.sketch_budget_from_text(
        sketch_decode_newlines(input),
        sketch_cli_int_arg(args, 2, 5),
      )
      @sketch.sketch_budget_markdown(plan) +
      "\n" +
      @sketch.recommendations_markdown(
        @sketch.sketch_budget_recommendations(plan),
      )
    }
    None => "Usage: moon run cmd/main -- budget  [accuracy-level]"
  }
}

///|
fn sketch_cli_arg(args : Array[String], index : Int) -> String? {
  if args.length() > index {
    Some(args[index])
  } else {
    None
  }
}

///|
fn sketch_cli_int_arg(args : Array[String], index : Int, fallback : Int) -> Int {
  match sketch_cli_arg(args, index) {
    Some(value) =>
      try @string.parse_int(value) catch {
        _ => fallback
      } noraise {
        parsed => parsed
      }
    None => fallback
  }
}

///|
fn sketch_decode_newlines(value : String) -> String {
  let out = StringBuilder()
  let mut slash = false
  for ch in value.iter() {
    if slash {
      if ch == 'n' {
        out.write_char('\n')
      } else if ch == 't' {
        out.write_char('\t')
      } else {
        out.write_char('\\')
        out.write_char(ch)
      }
      slash = false
    } else if ch == '\\' {
      slash = true
    } else {
      out.write_char(ch)
    }
  }
  if slash {
    out.write_char('\\')
  }
  out.to_string()
}

///|
fn sketch_drop_program_name(args : Array[String]) -> Array[String] {
  if args.length() <= 1 {
    []
  } else {
    let rest : Array[String] = Array::new()
    for i in 1..