///|
pub struct AnalyzeCliSpec {
  show_help : Bool
  workflow_path : String
  changed_paths : Array[String]
  errors : Array[String]
}

///|
fn drop_program_name(raw_args : Array[String]) -> Array[String] {
  if raw_args.length() <= 1 {
    return []
  }
  let args : Array[String] = []
  for i in 1.. AnalyzeCliSpec {
  let args = drop_program_name(raw_args)
  let errors : Array[String] = []
  let mut show_help = false
  let mut workflow_path = ""
  let changed_paths : Array[String] = []
  for arg in args {
    if arg == "-h" || arg == "--help" {
      show_help = true
      continue
    }
    if arg.has_prefix("-") {
      errors.push("unknown option: " + arg)
      continue
    }
    if workflow_path.length() == 0 {
      workflow_path = arg
    } else {
      changed_paths.push(arg)
    }
  }
  if !show_help {
    if workflow_path.length() == 0 {
      errors.push("workflow path is required")
    }
    if changed_paths.length() == 0 {
      errors.push("at least one changed path is required")
    }
  }
  { show_help, workflow_path, changed_paths, errors, }
}

///|
fn host_fs_adapter() -> @wf.FsAdapter {
  @wf.FsAdapter::new(
    fn(path : String) {
      try @xfs.read_file_to_string(path) catch {
        _ => None
      } noraise {
        text => Some(text)
      }
    },
    fn(path : String, content : String) {
      try @xfs.write_string_to_file(path, content) catch {
        _ => false
      } noraise {
        _ => true
      }
    },
    fn(path : String) { @xfs.path_exists(path) },
    fn() -> Array[String] { [] },
  )
}

///|
fn print_usage() -> Unit {
  println("Usage: moon run src/examples --  ...")
}

///|
fn print_parse_errors(errors : Array[String]) -> Unit {
  for err in errors {
    println("error: " + err)
  }
}

///|
fn run_success_with_log(task : @wf.FlowTask) -> (Bool, String) {
  println("run: \{task.id} (\{task.cmd})")
  (true, "ok")
}

///|
fn main {
  let raw_args = cli_args()
  let spec = parse_analyze_cli_args(raw_args)
  if spec.show_help {
    print_usage()
    return
  }
  if spec.errors.length() > 0 {
    print_parse_errors(spec.errors)
    print_usage()
    @xsys.exit(2)
  }

  let adapter = @wf.WorkflowAdapter::new(
    host_fs_adapter(),
    @wf.CommandAdapter::none(),
  )
  let parsed = @wf.parse_from_fs(spec.workflow_path, adapter)
  if parsed.errors.length() > 0 {
    print_parse_errors(parsed.errors)
    @xsys.exit(1)
  }

  let targets = @wf.entry_targets_for_changed_paths(
    parsed.ir,
    spec.changed_paths,
  )
  let changed_text = spec.changed_paths.join(",")
  let targets_text = targets.join(",")
  println("workflow=\{parsed.ir.name}")
  println("changed=\{changed_text}")
  println("targets=\{targets_text}")
  if targets.length() == 0 {
    println("no affected auto targets")
    return
  }

  let result = @wf.execute_ir_for_changed_paths(
    parsed.ir,
    spec.changed_paths,
    run_success_with_log,
  )
  let order_text = result.order.join(",")
  let issues_text = result.issues.join(" | ")
  println("state=\{result.state}")
  println("order=\{order_text}")
  if result.issues.length() > 0 {
    println("issues=\{issues_text}")
  }
}

///|
test "examples cli: parses workflow and changed paths" {
  let spec = parse_analyze_cli_args([
    "bitflow-examples", "workflow.star", "packages/lib-a/src/index.ts", "packages/app/src/main.ts",
  ])
  assert_eq(spec.show_help, false)
  assert_eq(spec.errors.length(), 0)
  assert_eq(spec.workflow_path, "workflow.star")
  assert_eq(spec.changed_paths.length(), 2)
}

///|
test "examples cli: reports missing required args" {
  let spec = parse_analyze_cli_args(["bitflow-examples"])
  assert_eq(spec.show_help, false)
  let text = spec.errors.join("\n")
  assert_true(text.contains("workflow path is required"))
  assert_true(text.contains("at least one changed path is required"))
}

///|
test "examples cli: accepts help" {
  let spec = parse_analyze_cli_args(["bitflow-examples", "--help"])
  assert_eq(spec.show_help, true)
  assert_eq(spec.errors.length(), 0)
}

///|
#cfg(target="js")
fn cli_args() -> Array[String] {
  // core/env includes the JS runtime; retain the existing program-name contract.
  @env.args()[1:].to_owned()
}

///|
#cfg(not(target="js"))
fn cli_args() -> Array[String] {
  @env.args()
}