pub struct InputBuffer {
  text: String,
  cursor: usize,
  selection_anchor: Option<usize>,
}

pub fn new() -> InputBuffer {
  InputBuffer {
    text: String::new(),
    cursor: 0,
    selection_anchor: None,
  }
}

pub fn text(buf: &InputBuffer) -> &str {
  &buf.text
}

pub fn cursor(buf: &InputBuffer) -> usize {
  buf.cursor
}

pub fn selection_range(buf: &InputBuffer) -> Option<(usize, usize)> {
  buf.selection_anchor.map(|anchor| {
    let start = anchor.min(buf.cursor);
    let end = anchor.max(buf.cursor);
    (start, end)
  })
}

pub fn has_selection(buf: &InputBuffer) -> bool {
  buf.selection_anchor.is_some()
}

pub fn insert_char(buf: &mut InputBuffer, c: char) {
  delete_selection_if_active(buf);
  buf.text.insert(byte_offset(&buf.text, buf.cursor), c);
  buf.cursor += 1;
}

pub fn backspace(buf: &mut InputBuffer) {
  if has_selection(buf) {
    delete_selection_if_active(buf);
    return;
  }
  if buf.cursor == 0 {
    return;
  }
  let offset = byte_offset(&buf.text, buf.cursor - 1);
  buf.text.remove(offset);
  buf.cursor -= 1;
}

pub fn delete(buf: &mut InputBuffer) {
  if has_selection(buf) {
    delete_selection_if_active(buf);
    return;
  }
  let len = char_count(&buf.text);
  if buf.cursor >= len {
    return;
  }
  let offset = byte_offset(&buf.text, buf.cursor);
  buf.text.remove(offset);
}

pub fn move_left(buf: &mut InputBuffer) {
  buf.selection_anchor = None;
  if buf.cursor > 0 {
    buf.cursor -= 1;
  }
}

pub fn move_right(buf: &mut InputBuffer) {
  buf.selection_anchor = None;
  let len = char_count(&buf.text);
  if buf.cursor < len {
    buf.cursor += 1;
  }
}

pub fn move_home(buf: &mut InputBuffer) {
  buf.selection_anchor = None;
  buf.cursor = 0;
}

pub fn move_end(buf: &mut InputBuffer) {
  buf.selection_anchor = None;
  buf.cursor = char_count(&buf.text);
}

pub fn select_left(buf: &mut InputBuffer) {
  if buf.cursor == 0 {
    return;
  }
  if buf.selection_anchor.is_none() {
    buf.selection_anchor = Some(buf.cursor);
  }
  buf.cursor -= 1;
  collapse_if_empty(buf);
}

pub fn select_right(buf: &mut InputBuffer) {
  let len = char_count(&buf.text);
  if buf.cursor >= len {
    return;
  }
  if buf.selection_anchor.is_none() {
    buf.selection_anchor = Some(buf.cursor);
  }
  buf.cursor += 1;
  collapse_if_empty(buf);
}

pub fn select_home(buf: &mut InputBuffer) {
  if buf.cursor == 0 {
    return;
  }
  if buf.selection_anchor.is_none() {
    buf.selection_anchor = Some(buf.cursor);
  }
  buf.cursor = 0;
  collapse_if_empty(buf);
}

pub fn select_end(buf: &mut InputBuffer) {
  let len = char_count(&buf.text);
  if buf.cursor >= len {
    return;
  }
  if buf.selection_anchor.is_none() {
    buf.selection_anchor = Some(buf.cursor);
  }
  buf.cursor = len;
  collapse_if_empty(buf);
}

pub fn select_all(buf: &mut InputBuffer) {
  let len = char_count(&buf.text);
  if len == 0 {
    return;
  }
  buf.selection_anchor = Some(0);
  buf.cursor = len;
}

pub fn move_word_left(buf: &mut InputBuffer) {
  buf.selection_anchor = None;
  buf.cursor = prev_word_boundary(&buf.text, buf.cursor);
}

pub fn move_word_right(buf: &mut InputBuffer) {
  buf.selection_anchor = None;
  buf.cursor = next_word_boundary(&buf.text, buf.cursor);
}

pub fn select_word_left(buf: &mut InputBuffer) {
  let target = prev_word_boundary(&buf.text, buf.cursor);
  if target == buf.cursor {
    return;
  }
  if buf.selection_anchor.is_none() {
    buf.selection_anchor = Some(buf.cursor);
  }
  buf.cursor = target;
  collapse_if_empty(buf);
}

pub fn select_word_right(buf: &mut InputBuffer) {
  let target = next_word_boundary(&buf.text, buf.cursor);
  if target == buf.cursor {
    return;
  }
  if buf.selection_anchor.is_none() {
    buf.selection_anchor = Some(buf.cursor);
  }
  buf.cursor = target;
  collapse_if_empty(buf);
}

fn delete_selection_if_active(buf: &mut InputBuffer) {
  let Some(anchor) = buf.selection_anchor.take() else {
    return;
  };
  let start = anchor.min(buf.cursor);
  let end = anchor.max(buf.cursor);

  let start_byte = byte_offset(&buf.text, start);
  let end_byte = byte_offset(&buf.text, end);
  buf.text.replace_range(start_byte..end_byte, "");
  buf.cursor = start;
}

fn collapse_if_empty(buf: &mut InputBuffer) {
  if buf.selection_anchor == Some(buf.cursor) {
    buf.selection_anchor = None;
  }
}

fn byte_offset(text: &str, char_idx: usize) -> usize {
  text
    .char_indices()
    .nth(char_idx)
    .map(|(i, _)| i)
    .unwrap_or(text.len())
}

fn char_count(text: &str) -> usize {
  text.chars().count()
}

fn prev_word_boundary(text: &str, cursor: usize) -> usize {
  if cursor == 0 {
    return 0;
  }
  let chars: Vec<char> = text.chars().collect();
  let mut pos = cursor - 1;

  while pos > 0 && !chars[pos].is_alphanumeric() {
    pos -= 1;
  }
  while pos > 0 && chars[pos - 1].is_alphanumeric() {
    pos -= 1;
  }
  pos
}

fn next_word_boundary(text: &str, cursor: usize) -> usize {
  let chars: Vec<char> = text.chars().collect();
  let len = chars.len();
  if cursor >= len {
    return len;
  }
  let mut pos = cursor;

  while pos < len && !chars[pos].is_alphanumeric() {
    pos += 1;
  }
  while pos < len && chars[pos].is_alphanumeric() {
    pos += 1;
  }
  pos
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn insert_appends_at_end() {
    let mut buf = new();
    insert_char(&mut buf, 'h');
    insert_char(&mut buf, 'i');

    assert_eq!(text(&buf), "hi");
    assert_eq!(cursor(&buf), 2);
  }

  #[test]
  fn insert_at_cursor_position() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    insert_char(&mut buf, 'c');
    move_left(&mut buf);
    insert_char(&mut buf, 'b');

    assert_eq!(text(&buf), "abc");
    assert_eq!(cursor(&buf), 2);
  }

  #[test]
  fn backspace_removes_before_cursor() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    insert_char(&mut buf, 'b');
    insert_char(&mut buf, 'c');
    move_left(&mut buf);
    backspace(&mut buf);

    assert_eq!(text(&buf), "ac");
    assert_eq!(cursor(&buf), 1);
  }

  #[test]
  fn backspace_at_start_does_nothing() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    move_home(&mut buf);
    backspace(&mut buf);

    assert_eq!(text(&buf), "a");
    assert_eq!(cursor(&buf), 0);
  }

  #[test]
  fn delete_removes_at_cursor() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    insert_char(&mut buf, 'b');
    insert_char(&mut buf, 'c');
    move_home(&mut buf);
    delete(&mut buf);

    assert_eq!(text(&buf), "bc");
    assert_eq!(cursor(&buf), 0);
  }

  #[test]
  fn delete_at_end_does_nothing() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    delete(&mut buf);

    assert_eq!(text(&buf), "a");
  }

  #[test]
  fn move_left_right_navigates() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    insert_char(&mut buf, 'b');
    insert_char(&mut buf, 'c');

    move_left(&mut buf);
    assert_eq!(cursor(&buf), 2);
    move_left(&mut buf);
    assert_eq!(cursor(&buf), 1);
    move_right(&mut buf);
    assert_eq!(cursor(&buf), 2);
  }

  #[test]
  fn move_left_clamps_at_zero() {
    let mut buf = new();
    move_left(&mut buf);
    assert_eq!(cursor(&buf), 0);
  }

  #[test]
  fn move_right_clamps_at_end() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    move_right(&mut buf);
    assert_eq!(cursor(&buf), 1);
  }

  #[test]
  fn home_and_end() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    insert_char(&mut buf, 'b');
    insert_char(&mut buf, 'c');
    move_home(&mut buf);
    assert_eq!(cursor(&buf), 0);
    move_end(&mut buf);
    assert_eq!(cursor(&buf), 3);
  }

  #[test]
  fn select_left_creates_selection() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    insert_char(&mut buf, 'b');
    insert_char(&mut buf, 'c');
    select_left(&mut buf);
    select_left(&mut buf);

    assert_eq!(selection_range(&buf), Some((1, 3)));
    assert_eq!(cursor(&buf), 1);
  }

  #[test]
  fn select_right_creates_selection() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    insert_char(&mut buf, 'b');
    insert_char(&mut buf, 'c');
    move_home(&mut buf);
    select_right(&mut buf);

    assert_eq!(selection_range(&buf), Some((0, 1)));
    assert_eq!(cursor(&buf), 1);
  }

  #[test]
  fn select_all_selects_entire_text() {
    let mut buf = new();
    insert_char(&mut buf, 'h');
    insert_char(&mut buf, 'i');
    select_all(&mut buf);

    assert_eq!(selection_range(&buf), Some((0, 2)));
  }

  #[test]
  fn typing_replaces_selection() {
    let mut buf = new();
    insert_char(&mut buf, 'h');
    insert_char(&mut buf, 'e');
    insert_char(&mut buf, 'l');
    insert_char(&mut buf, 'l');
    insert_char(&mut buf, 'o');
    select_all(&mut buf);
    insert_char(&mut buf, 'x');

    assert_eq!(text(&buf), "x");
    assert_eq!(cursor(&buf), 1);
    assert!(!has_selection(&buf));
  }

  #[test]
  fn backspace_deletes_selection() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    insert_char(&mut buf, 'b');
    insert_char(&mut buf, 'c');
    select_left(&mut buf);
    select_left(&mut buf);
    backspace(&mut buf);

    assert_eq!(text(&buf), "a");
    assert_eq!(cursor(&buf), 1);
  }

  #[test]
  fn move_clears_selection() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    insert_char(&mut buf, 'b');
    select_left(&mut buf);
    move_right(&mut buf);

    assert!(!has_selection(&buf));
  }

  #[test]
  fn select_home_from_end() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    insert_char(&mut buf, 'b');
    insert_char(&mut buf, 'c');
    select_home(&mut buf);

    assert_eq!(selection_range(&buf), Some((0, 3)));
    assert_eq!(cursor(&buf), 0);
  }

  #[test]
  fn select_end_from_start() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    insert_char(&mut buf, 'b');
    move_home(&mut buf);
    select_end(&mut buf);

    assert_eq!(selection_range(&buf), Some((0, 2)));
    assert_eq!(cursor(&buf), 2);
  }

  #[test]
  fn word_boundaries_navigate_words() {
    let mut buf = new();
    for c in "hello world foo".chars() {
      insert_char(&mut buf, c);
    }
    move_home(&mut buf);

    move_word_right(&mut buf);
    assert_eq!(cursor(&buf), 5);
    move_word_right(&mut buf);
    assert_eq!(cursor(&buf), 11);
    move_word_left(&mut buf);
    assert_eq!(cursor(&buf), 6);
    move_word_left(&mut buf);
    assert_eq!(cursor(&buf), 0);
  }

  #[test]
  fn select_word_left_selects_word() {
    let mut buf = new();
    for c in "hello world".chars() {
      insert_char(&mut buf, c);
    }
    select_word_left(&mut buf);

    assert_eq!(selection_range(&buf), Some((6, 11)));
  }

  #[test]
  fn select_word_right_selects_word() {
    let mut buf = new();
    for c in "hello world".chars() {
      insert_char(&mut buf, c);
    }
    move_home(&mut buf);
    select_word_right(&mut buf);

    assert_eq!(selection_range(&buf), Some((0, 5)));
  }

  #[test]
  fn multibyte_chars_handled_correctly() {
    let mut buf = new();
    for c in "café".chars() {
      insert_char(&mut buf, c);
    }
    assert_eq!(cursor(&buf), 4);
    move_left(&mut buf);
    insert_char(&mut buf, 'x');

    assert_eq!(text(&buf), "cafxé");
    assert_eq!(cursor(&buf), 4);
  }

  #[test]
  fn selection_collapes_when_cursor_meets_anchor() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    insert_char(&mut buf, 'b');
    select_left(&mut buf);
    select_right(&mut buf);

    assert!(!has_selection(&buf));
  }

  // ── Edge-case coverage for early returns ──────────────────

  #[test]
  fn delete_with_selection_removes_selected() {
    let mut buf = new();
    for c in "abc".chars() {
      insert_char(&mut buf, c);
    }
    move_home(&mut buf);
    select_right(&mut buf);
    select_right(&mut buf);
    delete(&mut buf);
    assert_eq!(text(&buf), "c");
  }

  #[test]
  fn select_left_at_zero_is_noop() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    move_home(&mut buf);
    select_left(&mut buf);
    assert_eq!(cursor(&buf), 0);
    assert!(!has_selection(&buf));
  }

  #[test]
  fn select_right_at_end_is_noop() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    select_right(&mut buf);
    assert_eq!(cursor(&buf), 1);
    assert!(!has_selection(&buf));
  }

  #[test]
  fn select_home_at_zero_is_noop() {
    let mut buf = new();
    select_home(&mut buf);
    assert_eq!(cursor(&buf), 0);
    assert!(!has_selection(&buf));
  }

  #[test]
  fn select_end_at_end_is_noop() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    select_end(&mut buf);
    assert_eq!(cursor(&buf), 1);
    assert!(!has_selection(&buf));
  }

  #[test]
  fn select_all_on_empty_is_noop() {
    let mut buf = new();
    select_all(&mut buf);
    assert!(!has_selection(&buf));
  }

  #[test]
  fn select_word_left_at_boundary_is_noop() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    move_home(&mut buf);
    select_word_left(&mut buf);
    assert_eq!(cursor(&buf), 0);
    assert!(!has_selection(&buf));
  }

  #[test]
  fn select_word_right_at_boundary_is_noop() {
    let mut buf = new();
    insert_char(&mut buf, 'a');
    select_word_right(&mut buf);
    assert_eq!(cursor(&buf), 1);
    assert!(!has_selection(&buf));
  }

  #[test]
  fn prev_word_boundary_at_zero_returns_zero() {
    assert_eq!(prev_word_boundary("hello", 0), 0);
  }

  #[test]
  fn next_word_boundary_at_end_returns_len() {
    assert_eq!(next_word_boundary("hello", 5), 5);
  }
}
