///|
pub(all) enum SuffixLimit {
  Cut
  Ignore
  Replace(Char)
} derive(Eq, Debug)

///|
pub struct Truncate[M, P] {
  width : M
  suffix : String
  suffix_limit : SuffixLimit
  multiline : Bool
  inherit_suffix_style : Bool
  priority : P
}

///|
pub fn[M] Truncate::new(width : M) -> Truncate[M, PriorityRoundRobin] {
  {
    width,
    suffix: "",
    suffix_limit: Cut,
    multiline: false,
    inherit_suffix_style: false,
    priority: PriorityRoundRobin::new(),
  }
}

///|
pub fn[M, P] Truncate::suffix(
  self : Truncate[M, P],
  suffix : String,
) -> Truncate[M, P] {
  { ..self, suffix, }
}

///|
pub fn[M, P] Truncate::suffix_limit(
  self : Truncate[M, P],
  limit : SuffixLimit,
) -> Truncate[M, P] {
  { ..self, suffix_limit: limit, }
}

///|
/// Let the suffix inherit the last text block's ANSI style when enabled.
pub fn[M, P] Truncate::inherit_suffix_style(
  self : Truncate[M, P],
  enabled : Bool,
) -> Truncate[M, P] {
  { ..self, inherit_suffix_style: enabled, }
}

///|
pub fn[M, P] Truncate::multiline(
  self : Truncate[M, P],
  enabled : Bool,
) -> Truncate[M, P] {
  { ..self, multiline: enabled, }
}

///|
pub fn[M, P, Q] Truncate::priority(
  self : Truncate[M, P],
  priority : Q,
) -> Truncate[M, Q] {
  {
    width: self.width,
    suffix: self.suffix,
    suffix_limit: self.suffix_limit,
    multiline: self.multiline,
    inherit_suffix_style: self.inherit_suffix_style,
    priority,
  }
}

///|
pub fn Truncate::truncate(text : String, width : Int) -> String {
  @papergrid.cut_str(text, width)
}

///|
pub impl[P] WidthOption for Truncate[Int, P] with fn to_mode(self) {
  @papergrid.WidthMode::truncate(self.width, self.suffix)
}

///|
pub impl[M : Measurement, P] CellOption for Truncate[M, P] with fn change(
  self,
  table,
  entity,
) {
  let available = Measurement::measure(self.width, table, Attribute::Width)
  let (suffix, width) = truncation_suffix(
    self.suffix,
    self.suffix_limit,
    available,
  )
  for position in entity.iter(table.count_rows(), table.count_cols()) {
    let (row, col) = position
    if col >= table.rows[row].length() {
      continue
    }
    let text = table.rows[row][col]
    if @papergrid.get_text_dimension(text).1 > available {
      table.rows[row][col] = truncate_content(
        text,
        width,
        available,
        suffix,
        self.inherit_suffix_style,
        self.multiline,
      )
    }
  }
}

///|
pub impl[M : Measurement, P : ResizeSelector] TableOption for Truncate[M, P] with fn change(
  self,
  table,
) {
  if table.is_empty() {
    return
  }
  let target = Measurement::measure(self.width, table, Attribute::Width)
  let widths = table.column_widths()
  let total = table_width(table, widths)
  if total <= target {
    return
  }
  let mins = minimum_widths(table)
  decrease_widths(widths, mins, total - target, self.priority)
  for point in decrease_cells(table, widths, mins) {
    let (row, col, available) = point
    let text = table.rows[row][col]
    let text_width = @papergrid.get_text_dimension(text).1
    if available >= text_width {
      continue
    }
    let (suffix, width) = truncation_suffix(
      self.suffix,
      self.suffix_limit,
      available,
    )
    table.rows[row][col] = truncate_content(
      text,
      width,
      text_width,
      suffix,
      self.inherit_suffix_style,
      self.multiline,
    )
  }
  table.set_column_widths(widths)
  table.dimension.clear_height()
}

///|
pub impl[M : Measurement, P : ResizeSelector] TableOption for Truncate[M, P] with fn hint_change(
  _self,
) {
  None
}

///|
fn truncation_suffix(
  suffix : String,
  limit : SuffixLimit,
  width : Int,
) -> (String, Int) {
  let size = @papergrid.get_line_width(suffix)
  if width > size {
    return (suffix, width - size)
  }
  match limit {
    Ignore => ("", width)
    Cut => (@papergrid.cut_str(suffix, width), 0)
    Replace(ch) => (String::make(width, ch), 0)
  }
}

///|
fn truncate_content(
  text : String,
  width : Int,
  total : Int,
  suffix : String,
  color : Bool,
  lines : Bool,
) -> String {
  fn truncate(line : String) -> String {
    if width == 0 {
      return if total == 0 { "" } else { suffix }
    }
    let ending = if color {
      @papergrid.colorize_suffix(line, suffix)
    } else {
      suffix
    }
    @papergrid.cut_str(line, width) + ending
  }
  if lines {
    @papergrid.get_lines(text).map(truncate).join("\n")
  } else {
    truncate(text)
  }
}

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

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

///|
pub extend Truncate with WidthOption::{to_mode}

///|
pub extend Truncate with CellOption::{
  change as change_cell,
  hint_change as hint_change_cell,
}

///|
pub extend Truncate with TableOption::{change, hint_change}