///|
/// 字符格物理尺寸(逻辑像素)。投影器的坐标系只有这一个原点:
/// 布局盒用像素表达,cell 栅格用它换算。
pub(all) struct CellBox {
  width : Double
  height : Double
}

///|
/// 宿主按自己终端的实际字宽构造。cell 尺寸是投影唯一的标定入口:
/// 它同时决定栅格量化和等宽文本系统的度量,标定错了分栏边界就会错格。
pub fn CellBox::new(width~ : Double, height~ : Double) -> CellBox {
  { width, height, }
}

///|
pub fn CellBox::default() -> CellBox {
  { width: 8.0, height: 16.0, }
}

///|
fn cell_count(px : Double, unit : Double) -> Int {
  if unit <= 0.0 {
    return 1
  }
  let n = (px / unit).floor().to_int()
  if n < 1 {
    1
  } else {
    n
  }
}

///|
/// 可视区列数:向下取整,至少 1 列。
pub fn CellBox::cols_of(self : CellBox, metrics : @core.SurfaceMetrics) -> Int {
  cell_count(metrics.logical_size.width, self.width)
}

///|
/// 可视区行数:向下取整,至少 1 行。
pub fn CellBox::rows_of(self : CellBox, metrics : @core.SurfaceMetrics) -> Int {
  cell_count(metrics.logical_size.height, self.height)
}

///|
/// 文本占用的 cell 数(换行符不占格,CJK/emoji 按双宽计)。
pub fn cell_units(text : String) -> Int {
  let mut units = 0
  for ch in text {
    if ch != '\n' {
      units = units + @tui.unicode_width(ch.to_string())
    }
  }
  units
}

///|
/// 按可用列数做贪心折行,返回占用行数。
fn wrapped_line_count(text : String, cols : Int) -> Int {
  let cols = if cols < 1 { 1 } else { cols }
  let mut lines = 1
  let mut used = 0
  for ch in text {
    if ch == '\n' {
      lines = lines + 1
      used = 0
    } else {
      let w = @tui.unicode_width(ch.to_string())
      if used + w > cols {
        lines = lines + 1
        used = w
      } else {
        used = used + w
      }
    }
  }
  lines
}

///|
/// 逐字符的插入点横偏移(含原点那一项,所以空文本也有一格)。
///
/// 等宽网格上这件事是免费的:第 i 个插入点就是前缀占了几个格宽。不报它
/// (曾经报 `[]`)有两个后果 —— 文本框的绘制覆盖层按下标取几何会越界,
/// 而点击定位没有可依据的偏移,只能永远退回第 0 个插入点。
/// 超出可用宽度的那些点按同一规则钉在边界上,和 `moui/core` 的
/// `clamp_text_layout_result` 一致:折出视口的文本,其尾部插入点留在边缘,
/// 不跑到框外去。
fn cell_caret_positions(
  text : String,
  cell : CellBox,
  max_width : Double?,
) -> Array[Double] {
  let positions : Array[Double] = [0.0]
  let mut cursor = 0.0
  for ch in text {
    if ch != '\n' {
      cursor = cursor +
        @tui.unicode_width(ch.to_string()).to_double() * cell.width
    }
    let clamped = match max_width {
      Some(width) => if cursor > width { width } else { cursor }
      None => cursor
    }
    positions.push(clamped)
  }
  positions
}

///|
/// 等宽 cell 文本系统:让既有 `layout()` 在不接 GPU/字体栈时也能算出盒高。
/// 度量单位就是 `CellBox`,所以布局结果与投影栅格共用一套网格。
pub fn cell_text_system(cell : CellBox) -> @core.TextSystem {
  @core.TextSystem::new(id="tui-cell", measure=input => {
    let cols = match input.max_width {
      Some(width) => cell_count(width, cell.width)
      None => cell_units(input.text)
    }
    let used = cell_units(input.text)
    let wide = if used > cols { cols } else { used }
    {
      size: @core.Size::new(
        width=wide.to_double() * cell.width,
        height=wrapped_line_count(input.text, cols).to_double() * cell.height,
      ),
      baseline: cell.height,
      caret_positions: cell_caret_positions(input.text, cell, input.max_width),
    }
  })
}