///|
pub(all) struct TextInputState {
  focused : Bool
  element_id : ElementId?
  text : String
  caret : Int
  selection : TextRange?
  composition : String?
  composition_cursor : TextRange?
  frame : Rect
  caret_rect : Rect
} derive(Eq, Debug, ToJson)

///|
pub fn TextInputState::none() -> TextInputState {
  {
    focused: false,
    element_id: None,
    text: "",
    caret: 0,
    selection: None,
    composition: None,
    composition_cursor: None,
    frame: Rect::new(x=0.0, y=0.0, width=0.0, height=0.0),
    caret_rect: Rect::new(x=0.0, y=0.0, width=0.0, height=0.0),
  }
}

///|
fn TextInputState::for_text_field(
  element : ElementNode,
  data : TextFieldViewSpec,
  render : RenderNode,
  text_system : TextSystem,
) -> TextInputState {
  let text = data.value.get()
  let display_text = text_with_composition(text, element, true)
  let text_frame = render.frame.inset(
    Insets::symmetric(horizontal=10.0, vertical=4.0),
  )
  let layout = text_system.measure_text(
    TextLayoutInput::new(
      text=display_text,
      font=data.font,
      max_width=text_frame.size.width,
    ),
  )
  let caret = clamp_int(element.control.caret, 0, text_length(text))
  let visual_caret = clamp_int(
    text_field_visual_caret(text, element, true),
    0,
    layout.caret_positions.length() - 1,
  )
  let cursor_x = text_frame.origin.x + layout.caret_positions[visual_caret]
  let caret_rect = Rect::new(
    x=cursor_x,
    y=text_frame.origin.y + 6.0,
    width=1.5,
    height=max_double(8.0, text_frame.size.height - 12.0),
  )
  {
    focused: true,
    element_id: Some(element.id),
    text,
    caret,
    selection: element.control.selection,
    composition: element.control.composition,
    composition_cursor: element.control.composition_cursor,
    frame: render.frame,
    caret_rect,
  }
}

///|
fn TextInputState::for_rich_text_editor(
  element : ElementNode,
  data : RichTextEditorViewSpec,
  render : RenderNode,
  text_system : TextSystem,
) -> TextInputState {
  let text = data.value.get()
  let display_text = text_with_composition(text, element, true)
  let content = render.frame.inset(
    Insets::symmetric(horizontal=14.0, vertical=12.0),
  )
  let caret = clamp_int(element.control.caret, 0, text_length(text))
  let visual_caret = clamp_int(
    text_field_visual_caret(text, element, true),
    0,
    text_length(display_text),
  )
  let document = (data.format)(display_text)
  let caret_rect = rich_text_document_caret_rect(
    document,
    visual_caret,
    content,
    data.font,
    text_system,
  ).unwrap_or(
    rich_text_caret_rect(
      display_text,
      visual_caret,
      content,
      data.font,
      text_system,
    ),
  )
  {
    focused: true,
    element_id: Some(element.id),
    text,
    caret,
    selection: element.control.selection,
    composition: element.control.composition,
    composition_cursor: element.control.composition_cursor,
    frame: render.frame,
    caret_rect,
  }
}

///|
fn ElementNode::focused_text_input(
  self : ElementNode,
  target : ElementId,
  render : RenderNode,
  text_system? : TextSystem = TextSystem::fallback(),
) -> TextInputState? {
  if self.id == target {
    match self.spec {
      TextFieldSpec(data) =>
        Some(TextInputState::for_text_field(self, data, render, text_system))
      RichTextEditorSpec(data) =>
        Some(
          TextInputState::for_rich_text_editor(self, data, render, text_system),
        )
      _ => None
    }
  } else {
    for index, child in self.children {
      if index < render.children.length() {
        match
          child.focused_text_input(target, render.children[index], text_system~) {
          Some(state) => return Some(state)
          None => ()
        }
      }
    }
    None
  }
}