///|
pub(all) struct TextLayoutInput {
  text : String
  font : FontSpec
  max_width : Double?
} derive(Eq, Debug, ToJson)

///|
pub(all) struct TextLayoutResult {
  size : Size
  baseline : Double
  caret_positions : Array[Double]
} derive(Eq, Debug, ToJson)

///|
pub struct TextSystem {
  id : String
  measure : (TextLayoutInput) -> TextLayoutResult
  register_font_data : (FontFamily, Bytes) -> Unit
}

///|
pub fn TextLayoutInput::new(
  text~ : String,
  font~ : FontSpec,
  max_width? : Double,
) -> TextLayoutInput {
  { text, font, max_width }
}

///|
pub fn TextSystem::new(
  id~ : String,
  measure~ : (TextLayoutInput) -> TextLayoutResult,
  register_font_data? : (FontFamily, Bytes) -> Unit = (_family, _data) => (),
) -> TextSystem {
  { id, measure, register_font_data }
}

///|
pub fn TextSystem::fallback() -> TextSystem {
  TextSystem::new(
    id="core-fallback-text-system",
    measure=fallback_measure_text_layout,
  )
}

///|
pub fn TextSystem::id(self : TextSystem) -> String {
  self.id
}

///|
pub fn TextSystem::measure_text(
  self : TextSystem,
  input : TextLayoutInput,
) -> TextLayoutResult {
  (self.measure)(input)
}

///|
pub fn TextSystem::register_font_data(
  self : TextSystem,
  family : FontFamily,
  data : Bytes,
) -> Unit {
  (self.register_font_data)(family, data)
}

///|
fn fallback_measure_text_layout(input : TextLayoutInput) -> TextLayoutResult {
  let raw = fallback_text_layout_result(input)
  match input.max_width {
    Some(max_width) => clamp_text_layout_result(raw, max_width)
    None => raw
  }
}

///|
fn clamp_text_layout_result(
  result : TextLayoutResult,
  max_width : Double,
) -> TextLayoutResult {
  let carets : Array[Double] = []
  for caret in result.caret_positions {
    carets.push(min_double(caret, max_width))
  }
  {
    size: Size::new(
      width=min_double(result.size.width, max_width),
      height=result.size.height,
    ),
    baseline: result.baseline,
    caret_positions: carets,
  }
}

///|
fn fallback_text_layout_result(input : TextLayoutInput) -> TextLayoutResult {
  let width = input.font.size * input.text.to_array().length().to_double() * 0.5
  let carets : Array[Double] = []
  carets.push(0.0)
  let mut cursor = 0.0
  for _ in input.text {
    cursor = cursor + input.font.size * 0.5
    carets.push(cursor)
  }
  {
    size: Size::new(width~, height=input.font.size * 1.25),
    baseline: input.font.size * 0.8,
    caret_positions: carets,
  }
}