// The command line contract of the `mooncheck` executable.
//
// Parsing lives in the library rather than in `cmd/main` on purpose: it is
// pure, so it is unit tested on every backend, while the executable stays a
// thin I/O shell that only reads files, prints the rendered report and sets
// the exit status.

///|
/// Options of the `validate` subcommand.
pub struct ValidateOptions {
  /// Output format for the report.
  format : ReportFormat
  /// Only report failures.
  quiet : Bool
  /// Path of the schema document.
  schema : String
  /// Paths of the data documents, in order.
  data : Array[String]
} derive(Eq, @debug.Debug)

///|
/// Options of the `check-schema` subcommand.
pub struct CheckSchemaOptions {
  /// Path of the schema document.
  path : String
} derive(Eq, @debug.Debug)

///|
/// A parsed command line.
pub enum CliCommand {
  /// `--help`
  Help
  /// `--version`
  Version
  /// Validate one schema against one or more data documents.
  Validate(ValidateOptions)
  /// Check that a schema document is well formed.
  CheckSchema(CheckSchemaOptions)
  /// The command line could not be understood; carries the reason.
  Invalid(String)
} derive(Eq, @debug.Debug)

///|
/// Parse a command line (without the program name).
///
/// `validate` is optional, so `mooncheck validate s.json d.json` and
/// `mooncheck s.json d.json` are equivalent. Data paths may be given in any
/// number; shells expand globs, so `mooncheck s.json cfg/*.json` works without
/// any glob support in the CLI itself.
pub fn parse_args(argv : ArrayView[String]) -> CliCommand {
  if argv.length() == 0 {
    return Invalid("expected a schema file and at least one data file")
  }
  let first = argv[0]
  if first == "--help" || first == "-h" {
    return Help
  }
  if first == "--version" || first == "-V" {
    return Version
  }
  if first == "check-schema" {
    if argv.length() != 2 {
      return Invalid("check-schema takes exactly one schema file")
    }
    return CheckSchema({ path: argv[1], })
  }
  let mut format = Text
  let mut quiet = false
  let paths : Array[String] = []
  let mut i = if first == "validate" { 1 } else { 0 }
  while i < argv.length() {
    let arg = argv[i]
    if arg == "--report" {
      if i + 1 >= argv.length() {
        return Invalid("--report expects a value: text or json")
      }
      let value = argv[i + 1]
      if value == "json" {
        format = Json
      } else if value == "text" {
        format = Text
      } else {
        return Invalid("--report expects text or json, got \"\{value}\"")
      }
      i = i + 2
      continue
    }
    if arg == "--quiet" || arg == "-q" {
      quiet = true
      i = i + 1
      continue
    }
    if arg.has_prefix("-") && arg != "-" {
      return Invalid("unknown option: \{arg}")
    }
    paths.push(arg)
    i = i + 1
  }
  if paths.length() < 2 {
    return Invalid("expected a schema file and at least one data file")
  }
  let data : Array[String] = []
  let mut j = 1
  while j < paths.length() {
    data.push(paths[j])
    j = j + 1
  }
  return Validate({ format, quiet, schema: paths[0], data, })
}

///|
/// Usage text, printed by `--help` and after a usage error.
pub fn usage_text() -> String {
  let text =
    #|MoonCheck - validate JSON documents against a lightweight schema
    #|
    #|Usage:
    #|  mooncheck validate   [more.json ...]
    #|  mooncheck check-schema 
    #|
    #|The `validate` subcommand is optional. Data paths may be repeated, and
    #|shells expand globs, so this works for CI:
    #|  mooncheck validate schema.json configs/*.json
    #|
    #|Options:
    #|  --report text|json   Report format (default: text)
    #|  -q, --quiet          Only report failures
    #|  -h, --help           Show this help
    #|  -V, --version        Show version
    #|
    #|Exit codes:
    #|  0  every document is valid
    #|  1  at least one document is invalid
    #|  2  usage, I/O or schema error
  return text
}

///|
/// Version text, printed by `--version`.
pub fn version_text() -> String {
  return "mooncheck 0.1.0"
}