///|
/// Cron field representation and expansion.
/// Each of the 5 cron fields (minute, hour, day, month, weekday)
/// is parsed and expanded into a set of matching integer values.

///|
/// Cron field types. Supports both 5-field (standard) and 6-field (with seconds) formats.
pub enum FieldType {
  Second // 0-59 (6-field format only)
  Minute // 0-59
  Hour // 0-23
  DayOfMonth // 1-31
  Month // 1-12
  DayOfWeek // 0-7 (0 and 7 both = Sunday)
}

///|
/// A parsed and expanded cron field.
pub struct CronField {
  field_type : FieldType
  values : Array[Int] // sorted, deduplicated matching values
  raw : String // original field text
}

///|
/// Get the allowed value range for a field type.
pub fn field_range(field_type : FieldType) -> (Int, Int) {
  match field_type {
    Second => (0, 59)
    Minute => (0, 59)
    Hour => (0, 23)
    DayOfMonth => (1, 31)
    Month => (1, 12)
    DayOfWeek => (0, 7)
  }
}

///|
/// Get the display name for a field type.
pub fn field_name(field_type : FieldType) -> String {
  match field_type {
    Second => "second"
    Minute => "minute"
    Hour => "hour"
    DayOfMonth => "day of month"
    Month => "month"
    DayOfWeek => "day of week"
  }
}

///|
/// Get a short key name for a field type (used in error messages).
pub fn field_key(field_type : FieldType) -> String {
  match field_type {
    Second => "second"
    Minute => "minute"
    Hour => "hour"
    DayOfMonth => "day"
    Month => "month"
    DayOfWeek => "weekday"
  }
}

///|
/// Parse a single cron field expression into a CronField.
/// Returns None if the field has invalid syntax or values.
pub fn parse_field(field_type : FieldType, raw : String) -> CronField? {
  let (min, max) = field_range(field_type)
  let expanded = expand_field(raw, min, max)
  match expanded {
    Some(vals) => {
      let sorted = sort_and_dedup(vals)
      Some({ field_type, values: sorted, raw })
    }
    None => None
  }
}

///|
/// Expand a single field string into an array of matching integer values.
/// Supports: *, numbers, lists (1,3,5), ranges (1-5), steps (*/10, 1-10/2).
fn expand_field(raw : String, min : Int, max : Int) -> Array[Int]? {
  if raw == "*" {
    return Some(range(min, max, 1))
  }

  // Handle step syntax: value/step
  if contains_char(raw, '/') {
    return expand_step(raw, min, max)
  }

  // Handle list: a,b,c
  if contains_char(raw, ',') {
    return expand_list(raw, min, max)
  }

  // Handle range: a-b
  if contains_char(raw, '-') {
    return expand_range_item(raw, min, max)
  }

  // Simple number
  match parse_int_safe(raw) {
    Some(value) => Some([value])
    None => None
  }
}

///|
/// Expand a step expression (e.g., */5, 1-30/5, 5/10).
fn expand_step(raw : String, min : Int, max : Int) -> Array[Int]? {
  let parts = split_once(raw, "/")
  let left = parts.0
  let right = parts.1

  // Step must be positive
  let step_opt = parse_int_safe(right)
  match step_opt {
    Some(step) => {
      if step == 0 {
        return None
      }
      if left == "*" {
        return Some(range(min, max, step))
      }
      if contains_char(left, '-') {
        let combined = left + "/" + right
        return expand_range_item(combined, min, max)
      }
      // Single start value with step (e.g., 5/10 = 5,15,25,...)
      match parse_int_safe(left) {
        Some(start) => {
          if start < min || start > max {
            return None
          }
          let result : Array[Int] = []
          let mut value = start
          while value <= max {
            result.push(value)
            value = value + step
          }
          Some(result)
        }
        None => None
      }
    }
    None => None
  }
}

///|
/// Expand a list expression (e.g., 1,3,5 or 1-3,7-9).
fn expand_list(raw : String, min : Int, max : Int) -> Array[Int]? {
  let items = split(raw, ",")
  let result : Array[Int] = []
  for i = 0; i < items.length(); i = i + 1 {
    let item = items[i]
    if item == "" {
      return None
    }
    match expand_field(item, min, max) {
      Some(expanded) =>
        for j = 0; j < expanded.length(); j = j + 1 {
          result.push(expanded[j])
        }
      None => return None
    }
  }
  Some(result)
}

///|
/// Expand a range expression with optional step (e.g., 1-5, 1-30/5).
fn expand_range_item(raw : String, min : Int, max : Int) -> Array[Int]? {
  let step = if contains_char(raw, '/') {
    let parts = split_once(raw, "/")
    match parse_int_safe(parts.1) {
      Some(s) => {
        if s == 0 {
          return None
        }
        s
      }
      None => return None
    }
  } else {
    1
  }

  let range_part = if contains_char(raw, '/') {
    split_once(raw, "/").0
  } else {
    raw
  }

  let parts = split_once(range_part, "-")
  if parts.0 == "" || parts.1 == "" {
    return None
  }

  let start_opt = parse_int_safe(parts.0)
  let end_opt = parse_int_safe(parts.1)

  match (start_opt, end_opt) {
    (Some(start), Some(end)) => {
      if start < min || end > max || start > end {
        return None
      }
      let result : Array[Int] = []
      let mut value = start
      while value <= end {
        result.push(value)
        value = value + step
      }
      Some(result)
    }
    _ => None
  }
}

///|
/// Generate a range of values [min, max] with step.
fn range(min : Int, max : Int, step : Int) -> Array[Int] {
  let result : Array[Int] = []
  let mut value = min
  while value <= max {
    result.push(value)
    value = value + step
  }
  result
}

///|
/// Sort and deduplicate an array of integers.
fn sort_and_dedup(arr : Array[Int]) -> Array[Int] {
  if arr.length() <= 1 {
    return arr
  }
  let sorted = sort_ints(arr)
  let result : Array[Int] = []
  let mut prev : Int? = None
  for i = 0; i < sorted.length(); i = i + 1 {
    let curr = sorted[i]
    match prev {
      Some(p) =>
        if curr != p {
          result.push(curr)
          prev = Some(curr)
        } else {
          ()
        }
      None => {
        result.push(curr)
        prev = Some(curr)
      }
    }
  }
  result
}

///|
/// Simple insertion sort for integers.
fn sort_ints(arr : Array[Int]) -> Array[Int] {
  let result : Array[Int] = []
  for i = 0; i < arr.length(); i = i + 1 {
    result.push(arr[i])
  }
  for i = 1; i < result.length(); i = i + 1 {
    let key = result[i]
    let mut j = i
    while j > 0 && result[j - 1] > key {
      result[j] = result[j - 1]
      j = j - 1
    }
    result[j] = key
  }
  result
}

///|
/// Split a string by a delimiter character.
fn split(s : String, delim : String) -> Array[String] {
  let result : Array[String] = []
  let mut start = 0
  let mut i = 0
  let delim_code = delim[0].to_int()
  while i < s.length() {
    if s[i].to_int() == delim_code {
      result.push(s[start:i].to_owned())
      start = i + 1
    }
    i = i + 1
  }
  result.push(s[start:s.length()].to_owned())
  result
}

///|
/// Split a string on the first occurrence of a delimiter.
fn split_once(s : String, delim : String) -> (String, String) {
  let delim_code = delim[0].to_int()
  let mut i = 0
  while i < s.length() {
    if s[i].to_int() == delim_code {
      return (s[0:i].to_owned(), s[i + 1:s.length()].to_owned())
    }
    i = i + 1
  }
  (s, "")
}

///|
/// Check if a string contains a specific character.
fn contains_char(s : String, ch : Char) -> Bool {
  let code = ch.to_int()
  for i = 0; i < s.length(); i = i + 1 {
    if s[i].to_int() == code {
      return true
    }
  }
  false
}

///|
/// Parse a string into an integer, returning None on failure.
fn parse_int_safe(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)
}