///|
pub(all) enum AnsiColor {
  Default
  Black
  Red
  Green
  Yellow
  Blue
  Magenta
  Cyan
  White
  BrightBlack
  BrightRed
  BrightGreen
  BrightYellow
  BrightBlue
  BrightMagenta
  BrightCyan
  BrightWhite
  Color256(Int)
  Rgb(Int, Int, Int)
} derive(Eq)

///|
pub struct ConsoleFormat {
  foreground : AnsiColor
  background : AnsiColor
  bold : Bool
  italic : Bool
  underline : Bool
} derive(Eq)

///|
pub fn ConsoleFormat::with_background(
  self : ConsoleFormat,
  background : AnsiColor,
) -> ConsoleFormat {
  { ..self, background, }
}

///|
pub impl Default for ConsoleFormat with default() {
  {
    foreground: Default,
    background: Default,
    bold: false,
    italic: false,
    underline: false,
  }
}

///|
pub fn ConsoleFormat::new(
  foreground? : AnsiColor = Default,
  background? : AnsiColor = Default,
  bold? : Bool = false,
  italic? : Bool = false,
  underline? : Bool = false,
) -> ConsoleFormat {
  { foreground, background, bold, italic, underline }
}

///|
pub struct FormatSpan {
  start : Int // Start index in characters
  length : Int // Length in characters
  format : ConsoleFormat
}

///|
pub fn FormatSpan::new(
  start : Int,
  length : Int,
  format : ConsoleFormat,
) -> FormatSpan {
  { start, length, format }
}

///|
fn AnsiColor::to_escape_code(self : AnsiColor, is_background : Bool) -> String {
  let prefix = if is_background { "4" } else { "3" }
  let bright_prefix = if is_background { "10" } else { "9" }
  match self {
    Default => if is_background { "49" } else { "39" }
    Black => "\{prefix}0"
    Red => "\{prefix}1"
    Green => "\{prefix}2"
    Yellow => "\{prefix}3"
    Blue => "\{prefix}4"
    Magenta => "\{prefix}5"
    Cyan => "\{prefix}6"
    White => "\{prefix}7"
    BrightBlack => "\{bright_prefix}0"
    BrightRed => "\{bright_prefix}1"
    BrightGreen => "\{bright_prefix}2"
    BrightYellow => "\{bright_prefix}3"
    BrightBlue => "\{bright_prefix}4"
    BrightMagenta => "\{bright_prefix}5"
    BrightCyan => "\{bright_prefix}6"
    BrightWhite => "\{bright_prefix}7"
    Color256(code) =>
      (if is_background { "48;5;" } else { "38;5;" }) + code.to_string()
    Rgb(r, g, b) =>
      (if is_background { "48;2;" } else { "38;2;" }) + "\{r};\{g};\{b}"
  }
}

///|
fn ConsoleFormat::to_escape_sequence(self : ConsoleFormat) -> String {
  let parts = []
  if self.bold {
    parts.push("1")
  }
  if self.italic {
    parts.push("3")
  }
  if self.underline {
    parts.push("4")
  }

  if !(self.foreground is Default) {
    parts.push(self.foreground.to_escape_code(false))
  }
  if !(self.background is Default) {
    parts.push(self.background.to_escape_code(true))
  }

  if parts is [] {
    ""
  } else {
    "\u{1b}[" + parts.join(";") + "m"
  }
}

///|
let reset_sequence : String = "\u{1b}[0m"

///|
pub fn apply_highlights(text : String, spans : Array[FormatSpan]) -> String {
  if spans.length() == 0 {
    return text
  }

  // Sort spans by start position
  let sorted_spans = spans.copy()
  sorted_spans.sort_by_key(span => span.start)

  let result = StringBuilder::new()
  let chars = text.to_array()
  let mut current_pos = 0

  for span in sorted_spans {
    if span.start < current_pos {
      continue // Overlapping spans not fully supported in this simple version
    }

    // Text before span
    if span.start > current_pos {
      for i in current_pos..