///|
pub fn help_text() -> String {
  (
    #|MoonChsrc - source switching for developer tools
    #|
    #|Usage:
    #|  moonchsrc list
    #|  moonchsrc list mirror|target|
    #|  moonchsrc plan   [project|user|system]
    #|  moonchsrc set [--dry-run]  [auto|first|] [project|user|system]
    #|  moonchsrc get [--dry-run]  [project|user|system]
    #|  moonchsrc reset [--dry-run]  [project|user|system]
    #|  moonchsrc restore [--dry-run]  [project|user|system]
    #|  moonchsrc measure 
    #|
    #|Use --dry-run to preview the exact command without changing configuration.
    #|
  )
}

///|
pub fn render_error(error : PlanError) -> String {
  match error {
    UnknownTarget(target) => "error: unknown target '\{target}'"
    UnknownMirror(target~, mirror~) =>
      "error: target '\{target}' does not support mirror '\{mirror}'"
    InvalidScope(scope) => "error: invalid scope '\{scope}'"
    UnsupportedScope(target~, scope~) =>
      "error: target '\{target}' does not support scope '\{scope.to_text()}'"
    InvalidSnapshot(target~, expected~, actual~) =>
      "error: snapshot for '\{target}' contains \{actual} values; expected \{expected}"
    SnapshotNotRestorable(target) =>
      "error: snapshot for '\{target}' contains redacted credentials and cannot be restored"
    NoSelectableMirror(target) =>
      "error: target '\{target}' has no selectable mirror"
    ConfigConflict(path~, detail~) =>
      "error: refusing to edit \{path}: \{detail}"
    UnsafeConfigValue(value) =>
      "error: unsafe value cannot be written to configuration: '\{value}'"
  }
}

///|
fn render_plan(
  target : String,
  mirror : String,
  scope_text? : String = "user",
) -> String {
  let scope = match Scope::parse(scope_text) {
    Some(scope) => scope
    None => return render_error(InvalidScope(scope_text))
  }
  try plan_change(target, mirror, scope~) catch {
    error => render_error(error)
  } noraise {
    plan => "Dry-run plan (no changes made):\n\{plan.to_text()}"
  }
}

///|
fn apply_plan(
  target : String,
  mirror : String,
  scope_text? : String = "user",
) -> CliAction {
  let scope = match Scope::parse(scope_text) {
    Some(scope) => scope
    None => return Show(render_error(InvalidScope(scope_text)))
  }
  try plan_change(target, mirror, scope~) catch {
    error => Show(render_error(error))
  } noraise {
    plan => Execute(plan)
  }
}

///|
fn render_first(target : String, scope_text? : String = "user") -> String {
  let scope = match Scope::parse(scope_text) {
    Some(scope) => scope
    None => return render_error(InvalidScope(scope_text))
  }
  try plan_first(target, scope~) catch {
    error => render_error(error)
  } noraise {
    plan => "Dry-run plan (no changes made):\n\{plan.to_text()}"
  }
}

///|
fn apply_first(target : String, scope_text? : String = "user") -> CliAction {
  let scope = match Scope::parse(scope_text) {
    Some(scope) => scope
    None => return Show(render_error(InvalidScope(scope_text)))
  }
  try plan_first(target, scope~) catch {
    error => Show(render_error(error))
  } noraise {
    plan => Execute(plan)
  }
}

///|
fn auto_select_action(
  target : String,
  scope_text? : String = "user",
  dry_run? : Bool = false,
) -> CliAction {
  let scope = match Scope::parse(scope_text) {
    Some(scope) => scope
    None => return Show(render_error(InvalidScope(scope_text)))
  }
  match find_target(target) {
    Some(found) if found.scopes.contains(scope) =>
      AutoSelect(found.id, scope, dry_run)
    Some(found) => Show(render_error(UnsupportedScope(target=found.id, scope~)))
    None => Show(render_error(UnknownTarget(target)))
  }
}

///|
fn render_reset(target : String, scope_text? : String = "user") -> String {
  let scope = match Scope::parse(scope_text) {
    Some(scope) => scope
    None => return render_error(InvalidScope(scope_text))
  }
  try plan_reset(target, scope~) catch {
    error => render_error(error)
  } noraise {
    plan => "Dry-run plan (no changes made):\n\{plan.to_text()}"
  }
}

///|
fn apply_reset(target : String, scope_text? : String = "user") -> CliAction {
  let scope = match Scope::parse(scope_text) {
    Some(scope) => scope
    None => return Show(render_error(InvalidScope(scope_text)))
  }
  try plan_reset(target, scope~) catch {
    error => Show(render_error(error))
  } noraise {
    plan => Execute(plan)
  }
}

///|
fn render_get(target : String, scope_text? : String = "user") -> String {
  let scope = match Scope::parse(scope_text) {
    Some(scope) => scope
    None => return render_error(InvalidScope(scope_text))
  }
  try plan_get(target, scope~) catch {
    error => render_error(error)
  } noraise {
    plan => "Read-only plan:\n\{plan.to_text()}"
  }
}

///|
fn execute_get(target : String, scope_text? : String = "user") -> CliAction {
  let scope = match Scope::parse(scope_text) {
    Some(scope) => scope
    None => return Show(render_error(InvalidScope(scope_text)))
  }
  try plan_get(target, scope~) catch {
    error => Show(render_error(error))
  } noraise {
    plan => Execute(plan)
  }
}

///|
fn restore_latest_action(
  target : String,
  scope_text? : String = "user",
  dry_run? : Bool = false,
) -> CliAction {
  match Scope::parse(scope_text) {
    Some(scope) => RestoreLatest(target, scope, dry_run)
    None => Show(render_error(InvalidScope(scope_text)))
  }
}

///|
/// Parse CLI arguments into either display output or an execution plan.
pub fn dispatch_cli(args : ArrayView[String]) -> CliAction {
  match args {
    [] | ["help"] | ["-h"] | ["--help"] => Show(help_text())
    ["version"] | ["-V"] | ["--version"] => Show(version())
    ["list"] | ["ls"] | ["l"] => Show(render_targets())
    ["list", "mirror"] | ["list", "mirrors"] => Show(render_mirrors())
    ["list", "target"] | ["list", "targets"] => Show(render_targets())
    ["list", target] =>
      try render_target(target) catch {
        error => Show(render_error(error))
      } noraise {
        output => Show(output)
      }
    ["plan", target, mirror] => Show(render_plan(target, mirror))
    ["plan", target, mirror, scope] =>
      Show(render_plan(target, mirror, scope_text=scope))
    ["set", "--dry-run", target] => auto_select_action(target, dry_run=true)
    ["set", "--dry-run", target, "auto"] =>
      auto_select_action(target, dry_run=true)
    ["set", "--dry-run", target, "auto", scope] =>
      auto_select_action(target, scope_text=scope, dry_run=true)
    ["set", "--dry-run", target, "first"] => Show(render_first(target))
    ["set", "--dry-run", target, "first", scope] =>
      Show(render_first(target, scope_text=scope))
    ["set", "--dry-run", target, mirror] => Show(render_plan(target, mirror))
    ["set", "--dry-run", target, mirror, scope] =>
      Show(render_plan(target, mirror, scope_text=scope))
    ["set", target] => auto_select_action(target)
    ["set", target, "auto"] => auto_select_action(target)
    ["set", target, "auto", scope] =>
      auto_select_action(target, scope_text=scope)
    ["set", target, "first"] => apply_first(target)
    ["set", target, "first", scope] => apply_first(target, scope_text=scope)
    ["set", target, mirror] => apply_plan(target, mirror)
    ["set", target, mirror, scope] =>
      apply_plan(target, mirror, scope_text=scope)
    ["get", "--dry-run", target] => Show(render_get(target))
    ["get", "--dry-run", target, scope] =>
      Show(render_get(target, scope_text=scope))
    ["get", target] => execute_get(target)
    ["get", target, scope] => execute_get(target, scope_text=scope)
    ["reset", "--dry-run", target] => Show(render_reset(target))
    ["reset", "--dry-run", target, scope] =>
      Show(render_reset(target, scope_text=scope))
    ["reset", target] => apply_reset(target)
    ["reset", target, scope] => apply_reset(target, scope_text=scope)
    ["restore", "--dry-run", target] =>
      restore_latest_action(target, dry_run=true)
    ["restore", "--dry-run", target, scope] =>
      restore_latest_action(target, scope_text=scope, dry_run=true)
    ["restore", target] => restore_latest_action(target)
    ["restore", target, scope] =>
      restore_latest_action(target, scope_text=scope)
    ["measure", target] =>
      match find_target(target) {
        Some(found) => MeasureTarget(found.id)
        None => Show(render_error(UnknownTarget(target)))
      }
    _ => Show("error: invalid arguments\n\n\{help_text()}")
  }
}

///|
/// Run the pure display form of the CLI. Execution requests are rendered but
/// never launched; the native executable handles Execute explicitly.
pub fn run_cli(args : ArrayView[String]) -> String {
  match dispatch_cli(args) {
    Show(output) => output
    Execute(plan) => "Execution requested:\n\{plan.to_text()}"
    RestoreLatest(target, scope, dry_run) =>
      "Restore requested for \{target} at \{scope.to_text()} scope (dry-run: \{dry_run})"
    MeasureTarget(target) => "Measurement requested for \{target}"
    AutoSelect(target, scope, dry_run) =>
      "Automatic selection requested for \{target} at \{scope.to_text()} scope (dry-run: \{dry_run})"
  }
}