///|
/// Rendering-only whitespace and multiline alignment policy.
pub(all) struct Formatting {
  trim_horizontal : Bool
  trim_vertical : Bool
  align_lines : Bool
} derive(Eq, Debug)

///|
pub fn Formatting::new(
  trim_horizontal~ : Bool,
  trim_vertical~ : Bool,
  align_lines~ : Bool,
) -> Formatting {
  { trim_horizontal, trim_vertical, align_lines, }
}

///|
pub fn Formatting::default() -> Formatting {
  Formatting::new(trim_horizontal=false, trim_vertical=false, align_lines=false)
}

///|
/// An ANSI prefix and suffix, kept separate from the visible cell text.
pub(all) struct ANSIBuf {
  prefix : String
  suffix : String
} derive(Eq, Debug)

///|
pub fn ANSIBuf::new(prefix : String, suffix : String) -> ANSIBuf {
  { prefix, suffix, }
}

///|
pub fn ANSIBuf::empty() -> ANSIBuf {
  ANSIBuf::new("", "")
}

///|
pub fn ANSIBuf::is_empty(self : ANSIBuf) -> Bool {
  self.prefix.is_empty() && self.suffix.is_empty()
}

///|
pub fn ANSIBuf::get_prefix(self : ANSIBuf) -> String {
  self.prefix
}

///|
pub fn ANSIBuf::get_suffix(self : ANSIBuf) -> String {
  self.suffix
}

///|
priv enum FormatChange {
  HorizontalTrim(Bool)
  VerticalTrim(Bool)
  LineAlignment(Bool)
  AlignmentFill(Char, ANSIBuf?)
  TextColor(ANSIBuf?)
}

///|
priv struct TextFormat {
  mut formatting : Formatting
  mut justification : Char
  mut justification_color : ANSIBuf?
  mut color : ANSIBuf?
}

///|
fn entity_scope(entity : Entity) -> Scope {
  match entity {
    Global => All
    Row(row) => RowRange(row, if row == 2147483647 { row } else { row + 1 })
    Col(col) => ColRange(col, if col == 2147483647 { col } else { col + 1 })
    Cell(row, col) => Cell(row, col)
  }
}

///|
pub fn SpannedConfig::set_trim_horizontal(
  self : SpannedConfig,
  entity : Entity,
  enabled : Bool,
) -> Unit {
  self.formatting_rules.push((entity_scope(entity), HorizontalTrim(enabled)))
}

///|
pub fn SpannedConfig::set_trim_vertical(
  self : SpannedConfig,
  entity : Entity,
  enabled : Bool,
) -> Unit {
  self.formatting_rules.push((entity_scope(entity), VerticalTrim(enabled)))
}

///|
pub fn SpannedConfig::set_line_alignment(
  self : SpannedConfig,
  entity : Entity,
  enabled : Bool,
) -> Unit {
  self.formatting_rules.push((entity_scope(entity), LineAlignment(enabled)))
}

///|
pub fn SpannedConfig::set_formatting(
  self : SpannedConfig,
  entity : Entity,
  formatting : Formatting,
) -> Unit {
  self.set_trim_horizontal(entity, formatting.trim_horizontal)
  self.set_trim_vertical(entity, formatting.trim_vertical)
  self.set_line_alignment(entity, formatting.align_lines)
}

///|
pub fn SpannedConfig::set_alignment_fill(
  self : SpannedConfig,
  entity : Entity,
  fill : Char,
  color? : ANSIBuf,
) -> Unit {
  self.formatting_rules.push((entity_scope(entity), AlignmentFill(fill, color)))
}

///|
pub fn SpannedConfig::set_color(
  self : SpannedConfig,
  entity : Entity,
  color : ANSIBuf?,
) -> Unit {
  self.formatting_rules.push((entity_scope(entity), TextColor(color)))
}

///|
fn SpannedConfig::text_format_at(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> TextFormat {
  let result = TextFormat::{
    formatting: Formatting::default(),
    justification: ' ',
    justification_color: None,
    color: None,
  }
  for rule in self.formatting_rules {
    if scope_matches(rule.0, row, col) {
      match rule.1 {
        HorizontalTrim(enabled) =>
          result.formatting = { ..result.formatting, trim_horizontal: enabled, }
        VerticalTrim(enabled) =>
          result.formatting = { ..result.formatting, trim_vertical: enabled, }
        LineAlignment(enabled) =>
          result.formatting = { ..result.formatting, align_lines: enabled, }
        AlignmentFill(fill, color) => {
          result.justification = fill
          result.justification_color = color
        }
        TextColor(color) => result.color = color
      }
    }
  }
  result
}

///|
pub fn SpannedConfig::get_formatting(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> Formatting {
  self.text_format_at(row, col).formatting
}

///|
pub fn SpannedConfig::get_alignment_fill(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> Char {
  self.text_format_at(row, col).justification
}

///|
pub fn SpannedConfig::get_alignment_fill_color(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> ANSIBuf? {
  self.text_format_at(row, col).justification_color
}

///|
pub fn SpannedConfig::get_color(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> ANSIBuf? {
  self.text_format_at(row, col).color
}

///|
fn SpannedConfig::remap_formatting(
  self : SpannedConfig,
  row_map : Array[Int],
  col_map : Array[Int],
) -> Unit {
  let rules = []
  for rule in self.formatting_rules {
    match rule.0 {
      RowRange(start, end_) =>
        mapped_runs(row_map, start, end_).each(run => {
          rules.push((RowRange(run.0, run.1), rule.1))
        })
      ColRange(start, end_) =>
        mapped_runs(col_map, start, end_).each(run => {
          rules.push((ColRange(run.0, run.1), rule.1))
        })
      All => rules.push(rule)
      Cell(row, col) => {
        let row = map_index(row_map, row)
        let col = map_index(col_map, col)
        if row >= 0 && col >= 0 {
          rules.push((Cell(row, col), rule.1))
        }
      }
    }
  }
  self.formatting_rules.clear()
  self.formatting_rules.append(rules)
}

///|
pub extend Formatting with Eq::{not_equal, equal}

///|
pub extend Formatting with @debug.Debug::{to_repr}

///|
pub extend ANSIBuf with Eq::{not_equal, equal}

///|
pub extend ANSIBuf with @debug.Debug::{to_repr}