///|
/// Human-readable description generator.
/// Converts a cron expression into a Chinese or English description.

///|
/// Generate an English description of a cron expression.
pub fn describe_en(fields : Array[CronField]) -> String {
  if fields.length() != 5 && fields.length() != 6 {
    return "Invalid cron expression"
  }

  let offset = if fields.length() == 6 { 1 } else { 0 }
  let second_desc = if offset == 1 {
    describe_field_en(fields[0], Second)
  } else {
    ""
  }
  let minute_desc = describe_field_en(fields[offset], Minute)
  let hour_desc = describe_field_en(fields[offset + 1], Hour)
  let dom_desc = describe_field_en(fields[offset + 2], DayOfMonth)
  let month_desc = describe_field_en(fields[offset + 3], Month)
  let dow_desc = describe_field_en(fields[offset + 4], DayOfWeek)

  build_description_en(
    second_desc, minute_desc, hour_desc, dom_desc, month_desc, dow_desc, fields,
  )
}

///|
/// Generate a Chinese description of a cron expression.
pub fn describe_cn(fields : Array[CronField]) -> String {
  if fields.length() != 5 && fields.length() != 6 {
    return "无效的 cron 表达式"
  }

  let offset = if fields.length() == 6 { 1 } else { 0 }
  let second_desc = if offset == 1 {
    describe_field_cn(fields[0], Second)
  } else {
    ""
  }
  let minute_desc = describe_field_cn(fields[offset], Minute)
  let hour_desc = describe_field_cn(fields[offset + 1], Hour)
  let dom_desc = describe_field_cn(fields[offset + 2], DayOfMonth)
  let month_desc = describe_field_cn(fields[offset + 3], Month)
  let dow_desc = describe_field_cn(fields[offset + 4], DayOfWeek)

  build_description_cn(
    second_desc, minute_desc, hour_desc, dom_desc, month_desc, dow_desc, fields,
  )
}

///|
/// Describe a single field in English.
fn describe_field_en(field : CronField, field_type : FieldType) -> String {
  let raw = field.raw
  let is_all = raw == "*"
  let empty_names : Array[String] = []

  match field_type {
    Second => {
      if is_all {
        return "every second"
      }
      if raw.contains("/") && !raw.contains("-") {
        match get_step(raw) {
          Some(s) => return "every " + s.to_string() + " seconds"
          None => ()
        }
      }
      let vals = describe_values_en(field.values, empty_names)
      if vals != "" {
        "at second " + vals
      } else {
        ""
      }
    }
    Minute => {
      if is_all {
        return "every minute"
      }
      if raw.contains("/") && !raw.contains("-") {
        match get_step(raw) {
          Some(s) => return "every " + s.to_string() + " minutes"
          None => ()
        }
      }
      let vals = describe_values_en(field.values, empty_names)
      if vals != "" {
        "at minute " + vals
      } else {
        ""
      }
    }
    Hour => {
      if is_all {
        return ""
      }
      if raw.contains("/") && !raw.contains("-") {
        match get_step(raw) {
          Some(s) => return "every " + s.to_string() + " hours"
          None => ()
        }
      }
      let vals = describe_values_en(field.values, empty_names)
      if vals != "" {
        "at hour " + vals
      } else {
        ""
      }
    }
    DayOfMonth => {
      if is_all {
        return ""
      }
      let vals = describe_values_en(field.values, empty_names)
      if vals != "" {
        "on day " + vals + " of the month"
      } else {
        ""
      }
    }
    Month => {
      if is_all {
        return ""
      }
      let month_names = [
        "", "January", "February", "March", "April", "May", "June", "July", "August",
        "September", "October", "November", "December",
      ]
      let vals = describe_values_en(field.values, month_names)
      if vals != "" {
        "in " + vals
      } else {
        ""
      }
    }
    DayOfWeek => {
      if is_all {
        return ""
      }
      let dow_names = [
        "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday",
        "Sunday",
      ]
      let vals = describe_values_en(field.values, dow_names)
      if vals != "" {
        "on " + vals
      } else {
        ""
      }
    }
  }
}

///|
/// Describe values as a human-readable string, optionally using name mapping.
fn describe_values_en(values : Array[Int], names : Array[String]) -> String {
  if values.length() == 0 {
    return ""
  }
  // Check if it's a simple range
  if is_consecutive_range(values) {
    let first = format_value(values[0], names)
    let last = format_value(values[values.length() - 1], names)
    return first + " through " + last
  }
  // Check if it's a step
  if values.length() > 1 {
    let step = values[1] - values[0]
    let mut is_step = step > 1
    let mut j = 1
    while j < values.length() - 1 && is_step {
      if values[j + 1] - values[j] != step {
        is_step = false
      }
      j = j + 1
    }
    if is_step && step > 1 {
      let first = format_value(values[0], names)
      let last = format_value(values[values.length() - 1], names)
      return "every " + step.to_string() + " from " + first + " to " + last
    }
  }
  // List of individual values
  let mut result = ""
  for k = 0; k < values.length(); k = k + 1 {
    if k > 0 {
      if k == values.length() - 1 {
        result = result + " and "
      } else {
        result = result + ", "
      }
    }
    result = result + format_value(values[k], names)
  }
  result
}

///|
/// Format a value, using a name from the mapping if available.
fn format_value(value : Int, names : Array[String]) -> String {
  if value >= 0 && value < names.length() && names[value] != "" {
    names[value]
  } else {
    value.to_string()
  }
}

///|
/// Check if values form a consecutive range.
fn is_consecutive_range(values : Array[Int]) -> Bool {
  if values.length() <= 1 {
    return false
  }
  let mut i = 1
  while i < values.length() {
    if values[i] != values[i - 1] + 1 {
      return false
    }
    i = i + 1
  }
  true
}

///|
/// Extract step value from a "*/N" or "N/N" expression.
fn get_step(raw : String) -> Int? {
  let pos = find_slash_pos(raw)
  if pos >= 0 && pos + 1 < raw.length() {
    return parse_int_desc(raw[pos + 1:raw.length()].to_owned())
  }
  None
}

///|
/// Build the final English description from field descriptions.
fn build_description_en(
  second_desc : String,
  minute_desc : String,
  hour_desc : String,
  dom_desc : String,
  month_desc : String,
  dow_desc : String,
  fields : Array[CronField],
) -> String {
  let parts : Array[String] = []

  let offset = if fields.length() == 6 { 1 } else { 0 }
  let minute_raw = fields[offset].raw
  let hour_raw = fields[offset + 1].raw

  // Time description
  if minute_raw != "*" && hour_raw != "*" {
    if minute_raw.contains("/") &&
      !minute_raw.contains("-") &&
      !minute_raw.contains(",") {
      match get_step(minute_raw) {
        Some(s) =>
          if hour_raw.contains("-") &&
            !hour_raw.contains(",") &&
            !hour_raw.contains("/") {
            let hour_vals = fields[offset + 1].values
            if hour_vals.length() > 0 {
              let desc = "every " +
                s.to_string() +
                " minutes between " +
                hour_vals[0].to_string() +
                ":00 and " +
                hour_vals[hour_vals.length() - 1].to_string() +
                ":59"
              parts.push(desc)
            } else {
              parts.push("every " + s.to_string() + " minutes")
            }
          } else {
            parts.push("every " + s.to_string() + " minutes")
          }
        None => {
          if hour_desc != "" {
            parts.push(hour_desc)
          }
          if minute_desc != "" {
            parts.push(minute_desc)
          }
        }
      }
    } else {
      if hour_desc != "" {
        parts.push(hour_desc)
      }
      if minute_desc != "" {
        parts.push(minute_desc)
      }
    }
  } else if minute_raw != "*" {
    if minute_desc != "" {
      parts.push(minute_desc)
    }
  } else if hour_raw != "*" {
    if hour_desc != "" {
      parts.push(hour_desc)
    }
  } else if second_desc != "every second" {
    parts.push("every minute")
  }

  if second_desc != "" {
    parts.push(second_desc)
  }
  if dom_desc != "" {
    parts.push(dom_desc)
  }
  if dow_desc != "" {
    parts.push(dow_desc)
  }
  if month_desc != "" {
    parts.push(month_desc)
  }

  if parts.length() == 0 {
    return "every minute"
  }
  join_strings(parts, ", ")
}

///|
/// Build a Chinese description from field descriptions.
fn build_description_cn(
  second_desc : String,
  minute_desc : String,
  hour_desc : String,
  dom_desc : String,
  month_desc : String,
  dow_desc : String,
  fields : Array[CronField],
) -> String {
  let parts : Array[String] = []

  let offset = if fields.length() == 6 { 1 } else { 0 }
  let minute_raw = fields[offset].raw
  let hour_raw = fields[offset + 1].raw

  // Time description
  if minute_raw != "*" && hour_raw != "*" {
    if minute_raw.contains("/") &&
      !minute_raw.contains("-") &&
      !minute_raw.contains(",") {
      match get_step(minute_raw) {
        Some(s) =>
          if hour_raw.contains("-") &&
            !hour_raw.contains(",") &&
            !hour_raw.contains("/") {
            let hour_vals = fields[offset + 1].values
            if hour_vals.length() > 0 {
              parts.push(
                "在" +
                hour_vals[0].to_string() +
                "点到" +
                hour_vals[hour_vals.length() - 1].to_string() +
                "点之间,每" +
                s.to_string() +
                "分钟",
              )
            } else {
              parts.push("每" + s.to_string() + "分钟")
            }
          } else {
            parts.push("每" + s.to_string() + "分钟")
          }
        None => {
          if hour_desc != "" {
            parts.push(hour_desc)
          }
          if minute_desc != "" {
            parts.push(minute_desc)
          }
        }
      }
    } else {
      if hour_desc != "" {
        parts.push(hour_desc)
      }
      if minute_desc != "" {
        parts.push(minute_desc)
      }
    }
  } else if minute_raw != "*" {
    if minute_desc != "" {
      parts.push(minute_desc)
    }
  } else if hour_raw != "*" {
    if hour_desc != "" {
      parts.push(hour_desc)
    }
  } else if second_desc != "每秒" {
    parts.push("每分钟")
  }

  if second_desc != "" {
    parts.push(second_desc)
  }
  if dom_desc != "" {
    parts.push(dom_desc)
  }
  if dow_desc != "" {
    parts.push(dow_desc)
  }
  if month_desc != "" {
    parts.push(month_desc)
  }

  if parts.length() == 0 {
    return "每分钟"
  }
  join_strings(parts, ",")
}

///|
/// Describe a single field in Chinese.
fn describe_field_cn(field : CronField, field_type : FieldType) -> String {
  let raw = field.raw
  let is_all = raw == "*"

  match field_type {
    Second => {
      if is_all {
        return "每秒"
      }
      if raw.contains("/") && !raw.contains("-") {
        match get_step(raw) {
          Some(s) => return "每" + s.to_string() + "秒"
          None => ()
        }
      }
      let vals = describe_values_cn(field.values)
      if vals != "" {
        "第" + vals + "秒"
      } else {
        ""
      }
    }
    Minute => {
      if is_all {
        return "每分钟"
      }
      if raw.contains("/") && !raw.contains("-") {
        match get_step(raw) {
          Some(s) => return "每" + s.to_string() + "分钟"
          None => ()
        }
      }
      let vals = describe_values_cn(field.values)
      if vals != "" {
        "第" + vals + "分钟"
      } else {
        ""
      }
    }
    Hour => {
      if is_all {
        return ""
      }
      let vals = describe_values_cn(field.values)
      if vals != "" {
        vals + "点"
      } else {
        ""
      }
    }
    DayOfMonth => {
      if is_all {
        return ""
      }
      let vals = describe_values_cn(field.values)
      if vals != "" {
        "每月" + vals + "日"
      } else {
        ""
      }
    }
    Month => {
      if is_all {
        return ""
      }
      let vals = describe_values_cn(field.values)
      if vals != "" {
        vals + "月"
      } else {
        ""
      }
    }
    DayOfWeek => {
      if is_all {
        return ""
      }
      let dow_names = [
        "周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日",
      ]
      let vals = describe_values_cn_names(field.values, dow_names)
      if vals != "" {
        "每" + vals
      } else {
        ""
      }
    }
  }
}

///|
/// Describe values in Chinese.
fn describe_values_cn(values : Array[Int]) -> String {
  if values.length() == 0 {
    return ""
  }
  if is_consecutive_range(values) {
    if values.length() >= 2 {
      return values[0].to_string() +
        "到" +
        values[values.length() - 1].to_string()
    }
  }
  if values.length() > 1 {
    let step = values[1] - values[0]
    let mut is_step = step > 1
    let mut i = 1
    while i < values.length() - 1 && is_step {
      if values[i + 1] - values[i] != step {
        is_step = false
      }
      i = i + 1
    }
    if is_step && step > 1 {
      return "每" +
        step.to_string() +
        "个从" +
        values[0].to_string() +
        "到" +
        values[values.length() - 1].to_string()
    }
  }
  let mut result = ""
  for j = 0; j < values.length(); j = j + 1 {
    if j > 0 {
      result = result + "、"
    }
    result = result + values[j].to_string()
  }
  result
}

///|
/// Describe values in Chinese, using name mappings where available.
fn describe_values_cn_names(
  values : Array[Int],
  names : Array[String],
) -> String {
  if values.length() == 0 {
    return ""
  }
  if is_consecutive_range(values) {
    if values.length() >= 2 {
      let first = format_value_cn(values[0], names)
      let last = format_value_cn(values[values.length() - 1], names)
      return first + "到" + last
    }
  }
  let mut result = ""
  for j = 0; j < values.length(); j = j + 1 {
    if j > 0 {
      result = result + "、"
    }
    result = result + format_value_cn(values[j], names)
  }
  result
}

///|
/// Format a value for Chinese display, using name mapping if available.
fn format_value_cn(value : Int, names : Array[String]) -> String {
  if value >= 0 && value < names.length() && names[value] != "" {
    names[value]
  } else {
    value.to_string()
  }
}

///|
/// Join an array of strings with a separator.
fn join_strings(parts : Array[String], sep : String) -> String {
  let mut result = ""
  for i = 0; i < parts.length(); i = i + 1 {
    if i > 0 {
      result = result + sep
    }
    result = result + parts[i]
  }
  result
}

///|
/// Find slash position in string.
fn find_slash_pos(s : String) -> Int {
  for i = 0; i < s.length(); i = i + 1 {
    if s[i].to_int() == 47 {
      return i
    }
  }
  -1
}

///|
/// Parse a strict integer (empty string returns None).
fn parse_int_desc(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)
}