///|
/// MoonCron CLI tool.
/// Usage:
///   mooncron check ""
///   mooncron explain ""
///   mooncron describe "" [--cn]
///   mooncron next "" [--count N] [--time "YYYY-MM-DD HH:MM[:SS]"]
///   mooncron matches "" --time "YYYY-MM-DD HH:MM[:SS]"
///   mooncron help
fn print_usage() -> Unit {
  println("MoonCron - Cron Expression Parser and Schedule Calculator")
  println("")
  println("Usage:")
  println("  mooncron check          Validate a cron expression")
  println("  mooncron explain        Show English description")
  println("  mooncron describe  --cn Show Chinese description")
  println("  mooncron next           Show next 5 execution times")
  println("           [--count N] [--time \"YYYY-MM-DD HH:MM\"]")
  println(
    "  mooncron matches        Check if expression matches a time",
  )
  println("           --time \"YYYY-MM-DD HH:MM\"")
  println("  mooncron help                       Show this help message")
  println("")
  println("Examples:")
  println("  mooncron check \"*/15 9-18 * * 1-5\"")
  println("  mooncron explain \"0 9 * * 1-5\"")
  println("  mooncron next \"0 8 * * *\" --count 3")
  println("  mooncron matches \"0 9 * * 1-5\" --time \"2026-07-27 09:00\"")
}

///|
fn[T] fail(message : String) -> T {
  println("error: " + message)
  @sys.exit(1)
  abort(message)
}

///|
fn command_check(args : Array[String]) -> Unit {
  let expr = get_expr_arg(args, 2)
  match @lib.validate_expression(expr) {
    Ok(_) => {
      println("Valid cron expression")
      println("")
      println("Field details:")
      match @lib.parse_cron_expression(expr) {
        Ok(fields) =>
          for i = 0; i < fields.length(); i = i + 1 {
            let field = fields[i]
            let name = @lib.field_key(field.field_type)
            let vals = field_values_to_string(field)
            println("  " + name + ": " + vals)
          }
        Err(_) => ()
      }
    }
    Err(err) => println(@lib.cron_error_to_string(err))
  }
}

///|
fn command_explain(args : Array[String]) -> Unit {
  let expr = get_expr_arg(args, 2)
  match @lib.parse_cron_expression(expr) {
    Ok(fields) => {
      let desc = @lib.describe_en(fields)
      println(desc)
    }
    Err(err) => println(@lib.cron_error_to_string(err))
  }
}

///|
fn command_describe(args : Array[String]) -> Unit {
  let expr = get_expr_arg(args, 2)
  let use_cn = has_flag(args, "--cn")
  match @lib.parse_cron_expression(expr) {
    Ok(fields) => {
      let desc = if use_cn {
        @lib.describe_cn(fields)
      } else {
        @lib.describe_en(fields)
      }
      println(desc)
    }
    Err(err) => println(@lib.cron_error_to_string(err))
  }
}

///|
fn command_next(args : Array[String]) -> Unit {
  let expr = get_expr_arg(args, 2)
  let count = get_int_flag(args, "--count", 5)
  let time_str = get_string_flag(args, "--time", "")

  let now = match time_str {
    "" => @lib.DateTime::new(2026, 7, 25, 12, 0)
    s =>
      match @lib.parse_datetime(s) {
        Some(dt) => dt
        None => fail("invalid time format, expected 'YYYY-MM-DD HH:MM[:SS]'")
      }
  }

  match @lib.parse_cron_expression(expr) {
    Ok(fields) => {
      let times = @lib.next_n(fields, now, count)
      let show_seconds = @lib.is_six_field(fields)
      if times.length() == 0 {
        println("No upcoming execution times found")
      } else {
        println("Next " + times.length().to_string() + " execution times:")
        for i = 0; i < times.length(); i = i + 1 {
          let display = if show_seconds {
            times[i].to_string_with_seconds()
          } else {
            times[i].to_string()
          }
          println("  " + display)
        }
      }
    }
    Err(err) => println(@lib.cron_error_to_string(err))
  }
}

///|
fn command_matches(args : Array[String]) -> Unit {
  let expr = get_expr_arg(args, 2)
  let time_str = get_string_flag(args, "--time", "")
  if time_str == "" {
    fail("--time flag is required for matches command")
  }

  let dt = match @lib.parse_datetime(time_str) {
    Some(dt) => dt
    None => fail("invalid time format, expected 'YYYY-MM-DD HH:MM[:SS]'")
  }

  match @lib.parse_cron_expression(expr) {
    Ok(fields) => {
      let matched = @lib.matches_time(fields, dt)
      let display = if @lib.is_six_field(fields) {
        dt.to_string_with_seconds()
      } else {
        dt.to_string()
      }
      if matched {
        println("true - " + display + " matches the cron expression")
      } else {
        println("false - " + display + " does not match the cron expression")
      }
    }
    Err(err) => println(@lib.cron_error_to_string(err))
  }
}

///|
fn get_expr_arg(args : Array[String], index : Int) -> String {
  if index >= args.length() {
    println("error: missing cron expression argument")
    print_usage()
    @sys.exit(1)
  }
  args[index]
}

///|
fn has_flag(args : Array[String], flag : String) -> Bool {
  for i = 0; i < args.length(); i = i + 1 {
    if args[i] == flag {
      return true
    }
  }
  false
}

///|
fn get_int_flag(args : Array[String], flag : String, default : Int) -> Int {
  for i = 0; i < args.length(); i = i + 1 {
    if args[i] == flag && i + 1 < args.length() {
      match parse_int_arg(args[i + 1]) {
        Some(n) => return n
        None => fail("invalid value for " + flag)
      }
    }
  }
  default
}

///|
fn get_string_flag(
  args : Array[String],
  flag : String,
  default : String,
) -> String {
  for i = 0; i < args.length(); i = i + 1 {
    if args[i] == flag && i + 1 < args.length() {
      return args[i + 1]
    }
  }
  default
}

///|
fn parse_int_arg(s : String) -> Int? {
  if s == "" {
    return None
  }
  let mut value = 0
  for i = 0; i < s.length(); i = i + 1 {
    let code = s[i].to_int()
    if code < 48 || code > 57 {
      return None
    }
    value = value * 10 + (code - 48)
  }
  Some(value)
}

///|
fn field_values_to_string(field : @lib.CronField) -> String {
  if field.values.length() == 0 {
    return "(none)"
  }
  let (min, max) = @lib.field_range(field.field_type)
  if field.values.length() == max - min + 1 {
    let mut all = true
    for i = 0; i < field.values.length(); i = i + 1 {
      if field.values[i] != min + i {
        all = false
        break
      }
    }
    if all {
      return "*"
    }
  }
  let mut s = ""
  for i = 0; i < field.values.length(); i = i + 1 {
    if i > 0 {
      s = s + ","
    }
    if i > 0 && i % 20 == 0 {
      s = s + "..."
      break
    }
    s = s + field.values[i].to_string()
  }
  s
}

///|
/// Entry point.
fn main {
  let args = @sys.get_cli_args()
  if args.length() < 2 {
    print_usage()
    @sys.exit(1)
  }

  match args[1] {
    "check" => command_check(args)
    "explain" => command_explain(args)
    "describe" => command_describe(args)
    "next" => command_next(args)
    "matches" => command_matches(args)
    "help" | "--help" | "-h" => print_usage()
    _ => {
      println("error: unknown command '" + args[1] + "'")
      println("")
      print_usage()
      @sys.exit(1)
    }
  }
}