///|
/// ANSI Escape Sequences for terminal output

///|
pub fn ansi_reset() -> String {
  @ansi.ansi_reset()
}

///|
pub fn ansi_bold() -> String {
  @ansi.ansi_bold()
}

///|
pub fn ansi_dim() -> String {
  @ansi.ansi_dim()
}

///|
pub fn ansi_underline() -> String {
  @ansi.ansi_underline()
}

///|
pub fn ansi_reverse() -> String {
  @ansi.ansi_reverse()
}

// --- Basic foreground colors ---

///|
pub fn ansi_fg_black() -> String {
  @ansi.ansi_fg_black()
}

///|
pub fn ansi_fg_red() -> String {
  @ansi.ansi_fg_red()
}

///|
pub fn ansi_fg_green() -> String {
  @ansi.ansi_fg_green()
}

///|
pub fn ansi_fg_yellow() -> String {
  @ansi.ansi_fg_yellow()
}

///|
pub fn ansi_fg_blue() -> String {
  @ansi.ansi_fg_blue()
}

///|
pub fn ansi_fg_magenta() -> String {
  @ansi.ansi_fg_magenta()
}

///|
pub fn ansi_fg_cyan() -> String {
  @ansi.ansi_fg_cyan()
}

///|
pub fn ansi_fg_white() -> String {
  @ansi.ansi_fg_white()
}

///|
/// Dim gray (bright black)
pub fn ansi_fg_gray() -> String {
  @ansi.ansi_fg_gray()
}

///|
pub fn ansi_fg_256(color_idx : Int) -> String {
  @ansi.ansi_fg_256(color_idx)
}

///|
pub fn ansi_bg_256(color_idx : Int) -> String {
  @ansi.ansi_bg_256(color_idx)
}

///|
/// Reset background color to default
pub fn ansi_bg_reset() -> String {
  @ansi.ansi_bg_reset()
}

///|
/// Move cursor up by n lines
pub fn ansi_move_up(n? : Int = 1) -> String {
  @ansi.ansi_move_up(n~)
}

///|
/// Move cursor down by n lines
pub fn ansi_move_down(n? : Int = 1) -> String {
  @ansi.ansi_move_down(n~)
}

///|
/// Move cursor right by n columns
pub fn ansi_move_right(n? : Int = 1) -> String {
  @ansi.ansi_move_right(n~)
}

///|
/// Move cursor left by n columns
pub fn ansi_move_left(n? : Int = 1) -> String {
  @ansi.ansi_move_left(n~)
}

///|
pub fn ansi_move_to(row : Int, col : Int) -> String {
  @ansi.ansi_move_to(row, col)
}

///|
pub fn ansi_clear_screen() -> String {
  @ansi.ansi_clear_screen()
}

///|
pub fn ansi_clear_line() -> String {
  @ansi.ansi_clear_line()
}

///|
pub fn ansi_hide_cursor() -> String {
  @ansi.ansi_hide_cursor()
}

///|
pub fn ansi_show_cursor() -> String {
  @ansi.ansi_show_cursor()
}

///|
pub fn ansi_enter_alt_screen() -> String {
  @ansi.ansi_enter_alt_screen()
}

///|
pub fn ansi_leave_alt_screen() -> String {
  @ansi.ansi_leave_alt_screen()
}

///|
/// Reset all terminal attributes (colors, styles)
pub fn ansi_reset_attrs() -> String {
  @ansi.ansi_reset_attrs()
}

///|
/// Full terminal reset sequence for recovering from corrupted state
pub fn ansi_full_reset() -> String {
  @ansi.ansi_full_reset()
}

///|
/// Convert RGB color to 256-color palette index
pub fn rgb_to_256(r : Int, g : Int, b : Int) -> Int {
  @ansi.rgb_to_256(r, g, b)
}

///|
pub fn color_to_256(color : @core.Color) -> Int {
  rgb_to_256(color.r, color.g, color.b)
}

///|
pub fn ansi_fg_color(color : @core.Color) -> String {
  ansi_fg_256(color_to_256(color))
}

///|
pub fn ansi_bg_color(color : @core.Color) -> String {
  ansi_bg_256(color_to_256(color))
}

///|
/// Convert CharBuffer to ANSI-encoded string
pub fn CharBuffer::to_ansi(self : CharBuffer) -> String {
  let sb = StringBuilder::new()
  let mut last_fg = -1
  let mut last_bg = -1
  let mut last_bold = false
  let mut last_underline = false
  for row = 0; row < self.height; row = row + 1 {
    sb.write_string(ansi_move_to(row, 0))
    let mut expected_col = 0
    for col = 0; col < self.width; col = col + 1 {
      let cell = self.get_cell(col, row)
      if cell.char == '\u0000' {
        // Skip wide char placeholder, but track that cursor should not advance
        continue
      }
      // If cursor position doesn't match expected, reposition
      if col != expected_col {
        sb.write_string(ansi_move_to(row, col))
      }
      expected_col = col + @core.char_display_width(cell.char)
      let fg_idx = if cell.fg.is_transparent() {
        7
      } else {
        color_to_256(cell.fg)
      }
      let bg_idx = if cell.bg.is_transparent() {
        -1
      } else {
        color_to_256(cell.bg)
      }
      let need_reset = (last_bold && !cell.bold) ||
        (last_underline && !cell.underline)
      if need_reset {
        sb.write_string(ansi_reset())
        last_fg = -1
        last_bg = -1
        last_bold = false
        last_underline = false
      }
      if cell.bold && !last_bold {
        sb.write_string(ansi_bold())
        last_bold = true
      }
      if cell.underline && !last_underline {
        sb.write_string(ansi_underline())
        last_underline = true
      }
      if fg_idx != last_fg {
        sb.write_string(ansi_fg_256(fg_idx))
        last_fg = fg_idx
      }
      if bg_idx != last_bg {
        if bg_idx >= 0 {
          sb.write_string(ansi_bg_256(bg_idx))
        } else {
          sb.write_string(ansi_bg_reset())
        }
        last_bg = bg_idx
      }
      sb.write_char(cell.char)
    }
  }
  sb.write_string(ansi_reset())
  sb.to_string()
}

///|
/// Convert CharBuffer to ANSI-encoded string with an offset
pub fn CharBuffer::to_ansi_offset(
  self : CharBuffer,
  row_offset : Int,
  col_offset : Int,
) -> String {
  let sb = StringBuilder::new()
  let mut last_fg = -1
  let mut last_bg = -1
  let mut last_bold = false
  let mut last_underline = false
  for row = 0; row < self.height; row = row + 1 {
    sb.write_string(ansi_move_to(row + row_offset, col_offset))
    let mut expected_col = 0
    for col = 0; col < self.width; col = col + 1 {
      let cell = self.get_cell(col, row)
      if cell.char == '\u0000' {
        continue
      }
      // If cursor position doesn't match expected, reposition
      if col != expected_col {
        sb.write_string(ansi_move_to(row + row_offset, col + col_offset))
      }
      expected_col = col + @core.char_display_width(cell.char)
      let fg_idx = if cell.fg.is_transparent() {
        7
      } else {
        color_to_256(cell.fg)
      }
      let bg_idx = if cell.bg.is_transparent() {
        -1
      } else {
        color_to_256(cell.bg)
      }
      let need_reset = (last_bold && !cell.bold) ||
        (last_underline && !cell.underline)
      if need_reset {
        sb.write_string(ansi_reset())
        last_fg = -1
        last_bg = -1
        last_bold = false
        last_underline = false
      }
      if cell.bold && !last_bold {
        sb.write_string(ansi_bold())
        last_bold = true
      }
      if cell.underline && !last_underline {
        sb.write_string(ansi_underline())
        last_underline = true
      }
      if fg_idx != last_fg {
        sb.write_string(ansi_fg_256(fg_idx))
        last_fg = fg_idx
      }
      if bg_idx != last_bg {
        if bg_idx >= 0 {
          sb.write_string(ansi_bg_256(bg_idx))
        } else {
          sb.write_string(ansi_bg_reset())
        }
        last_bg = bg_idx
      }
      sb.write_char(cell.char)
    }
  }
  sb.write_string(ansi_reset())
  sb.to_string()
}

///|
/// Optimized ANSI output with diff (only output changed cells)
pub fn CharBuffer::diff_to_ansi(self : CharBuffer, prev : CharBuffer) -> String {
  let sb = StringBuilder::new()
  let mut last_row = -1
  let mut expected_col = -1 // Track expected cursor position (considering char width)
  let mut last_fg = -1
  let mut last_bg = -1
  let mut last_bold = false
  let mut last_underline = false
  for row = 0; row < self.height; row = row + 1 {
    for col = 0; col < self.width; col = col + 1 {
      let cell = self.get_cell(col, row)
      let prev_cell = prev.get_cell(col, row)
      if cell == prev_cell {
        continue
      }
      let ch = cell.char
      // Skip wide char placeholder - the wide character before it already covers this column
      if ch == '\u0000' {
        continue
      }
      if row != last_row || col != expected_col {
        sb.write_string(ansi_move_to(row, col))
      }
      let fg_idx = if cell.fg.is_transparent() {
        7
      } else {
        color_to_256(cell.fg)
      }
      let bg_idx = if cell.bg.is_transparent() {
        -1
      } else {
        color_to_256(cell.bg)
      }
      let need_reset = (last_bold && !cell.bold) ||
        (last_underline && !cell.underline)
      if need_reset {
        sb.write_string(ansi_reset())
        last_fg = -1
        last_bg = -1
        last_bold = false
        last_underline = false
      }
      if cell.bold && !last_bold {
        sb.write_string(ansi_bold())
        last_bold = true
      }
      if cell.underline && !last_underline {
        sb.write_string(ansi_underline())
        last_underline = true
      }
      if fg_idx != last_fg {
        sb.write_string(ansi_fg_256(fg_idx))
        last_fg = fg_idx
      }
      if bg_idx != last_bg {
        if bg_idx >= 0 {
          sb.write_string(ansi_bg_256(bg_idx))
        } else {
          sb.write_string(ansi_bg_reset())
        }
        last_bg = bg_idx
      }
      sb.write_char(ch)
      last_row = row
      // Wide characters advance cursor by 2, narrow by 1
      expected_col = col + @core.char_display_width(ch)
    }
  }
  sb.write_string(ansi_reset())
  sb.to_string()
}

///|
/// Optimized ANSI output with diff and an offset
pub fn CharBuffer::diff_to_ansi_offset(
  self : CharBuffer,
  prev : CharBuffer,
  row_offset : Int,
  col_offset : Int,
) -> String {
  let sb = StringBuilder::new()
  let mut last_row = -1
  let mut expected_col = -1 // Track expected cursor position (considering char width)
  let mut last_fg = -1
  let mut last_bg = -1
  let mut last_bold = false
  let mut last_underline = false
  for row = 0; row < self.height; row = row + 1 {
    for col = 0; col < self.width; col = col + 1 {
      let cell = self.get_cell(col, row)
      let prev_cell = prev.get_cell(col, row)
      if cell == prev_cell {
        continue
      }
      let ch = cell.char
      // Skip wide char placeholder - the wide character before it already covers this column
      if ch == '\u0000' {
        continue
      }
      let out_row = row + row_offset
      let out_col = col + col_offset
      if out_row != last_row || out_col != expected_col {
        sb.write_string(ansi_move_to(out_row, out_col))
      }
      let fg_idx = if cell.fg.is_transparent() {
        7
      } else {
        color_to_256(cell.fg)
      }
      let bg_idx = if cell.bg.is_transparent() {
        -1
      } else {
        color_to_256(cell.bg)
      }
      let need_reset = (last_bold && !cell.bold) ||
        (last_underline && !cell.underline)
      if need_reset {
        sb.write_string(ansi_reset())
        last_fg = -1
        last_bg = -1
        last_bold = false
        last_underline = false
      }
      if cell.bold && !last_bold {
        sb.write_string(ansi_bold())
        last_bold = true
      }
      if cell.underline && !last_underline {
        sb.write_string(ansi_underline())
        last_underline = true
      }
      if fg_idx != last_fg {
        sb.write_string(ansi_fg_256(fg_idx))
        last_fg = fg_idx
      }
      if bg_idx != last_bg {
        if bg_idx >= 0 {
          sb.write_string(ansi_bg_256(bg_idx))
        } else {
          sb.write_string(ansi_bg_reset())
        }
        last_bg = bg_idx
      }
      sb.write_char(ch)
      last_row = out_row
      // Wide characters advance cursor by 2, narrow by 1
      expected_col = out_col + @core.char_display_width(ch)
    }
  }
  sb.write_string(ansi_reset())
  sb.to_string()
}

///|
pub fn enable_mouse() -> String {
  @ansi.enable_mouse()
}

///|
pub fn disable_mouse() -> String {
  @ansi.disable_mouse()
}

///|
/// Enable mouse with motion tracking (reports move events even without button press)
pub fn enable_mouse_all() -> String {
  @ansi.enable_mouse_all()
}

///|
/// Disable mouse motion tracking
pub fn disable_mouse_all() -> String {
  @ansi.disable_mouse_all()
}