//! Tests for input state

use super::*;

#[test]
fn test_new_input_state() {
    let state = InputState::new();
    assert_eq!(state.query(), "");
    assert_eq!(state.editor_mode, EditorMode::Insert);
}

#[test]
fn test_default() {
    let state = InputState::default();
    assert_eq!(state.query(), "");
}

#[test]
fn test_query_after_insert() {
    let mut state = InputState::new();
    state.textarea.insert_str("test query");
    assert_eq!(state.query(), "test query");
}

fn assert_cursor_visible(state: &InputState, viewport_width: usize) {
    let cursor_col = state.textarea.cursor().1;
    let scroll = state.scroll_offset;

    assert!(
        cursor_col >= scroll && cursor_col < scroll + viewport_width,
        "DESYNC: Cursor at {} not visible in viewport [{}, {})",
        cursor_col,
        scroll,
        scroll + viewport_width
    );
}

fn get_visible_text(state: &InputState, viewport_width: usize) -> String {
    let text = state.query();
    let start = state.scroll_offset.min(text.chars().count());
    let end = (start + viewport_width).min(text.chars().count());
    text.chars().skip(start).take(end - start).collect()
}

#[test]
fn test_scroll_offset_deletion_from_end() {
    let mut state = InputState::new();
    let viewport_width = 20;

    state.textarea.insert_str("1234567890123456789012345");
    state.calculate_scroll_offset(viewport_width);

    assert_eq!(state.textarea.cursor().1, 25);
    assert_eq!(state.scroll_offset, 6);

    for _ in 0..10 {
        state.textarea.delete_char();
    }

    assert_eq!(state.textarea.cursor().1, 15);
    assert_eq!(state.query().chars().count(), 15);

    state.calculate_scroll_offset(viewport_width);
    assert_eq!(state.scroll_offset, 0);
}

#[test]
fn test_scroll_offset_deletion_middle() {
    let mut state = InputState::new();
    let viewport_width = 10;

    state.textarea.insert_str("abcdefghijklmnopqrstuvwxyz");

    for _ in 0..11 {
        state.textarea.move_cursor(tui_textarea::CursorMove::Back);
    }
    assert_eq!(state.textarea.cursor().1, 15);

    state.scroll_offset = 10;
    state.calculate_scroll_offset(viewport_width);
    assert_eq!(state.scroll_offset, 10);

    for _ in 0..4 {
        state.textarea.delete_next_char();
    }

    assert_eq!(state.query().chars().count(), 22);
    assert_eq!(state.textarea.cursor().1, 15);

    state.calculate_scroll_offset(viewport_width);
    assert_eq!(state.scroll_offset, 10);

    for _ in 0..4 {
        state.textarea.delete_next_char();
    }

    assert_eq!(state.query().chars().count(), 18);

    state.calculate_scroll_offset(viewport_width);
    assert_eq!(state.scroll_offset, 8);
}

#[test]
fn test_scroll_offset_short_text_no_scroll() {
    let mut state = InputState::new();
    let viewport_width = 20;

    // Insert text shorter than viewport
    state.textarea.insert_str("short");
    state.calculate_scroll_offset(viewport_width);

    assert_eq!(state.scroll_offset, 0);
    assert_eq!(state.textarea.cursor().1, 5);
}

#[test]
fn test_scroll_offset_cursor_visibility_preserved() {
    let mut state = InputState::new();
    let viewport_width = 10;

    state.textarea.insert_str("abcdefghijklmnopqrstuvwxyz");

    for target_pos in [0, 5, 10, 15, 20, 25] {
        while state.textarea.cursor().1 > 0 {
            state.textarea.move_cursor(tui_textarea::CursorMove::Head);
        }

        for _ in 0..target_pos {
            state
                .textarea
                .move_cursor(tui_textarea::CursorMove::Forward);
        }

        let cursor_col = state.textarea.cursor().1;
        state.calculate_scroll_offset(viewport_width);

        assert!(
            cursor_col >= state.scroll_offset && cursor_col < state.scroll_offset + viewport_width,
            "Cursor at {} not visible with scroll_offset {} and viewport_width {}",
            cursor_col,
            state.scroll_offset,
            viewport_width
        );
    }
}

#[test]
fn test_scroll_offset_unicode_chars() {
    let mut state = InputState::new();
    let viewport_width = 10;

    state.textarea.insert_str("Hello👋World🌍Test");

    let text_len = state.query().chars().count();
    assert_eq!(text_len, 16);

    state.calculate_scroll_offset(viewport_width);

    assert_eq!(state.scroll_offset, 7);

    for _ in 0..5 {
        state.textarea.delete_char();
    }

    state.calculate_scroll_offset(viewport_width);
    let cursor = state.textarea.cursor().1;

    assert!(cursor >= state.scroll_offset);
    assert!(cursor < state.scroll_offset + viewport_width);
}

#[test]
fn test_scroll_offset_empty_input() {
    let mut state = InputState::new();
    let viewport_width = 10;

    assert_eq!(state.query(), "");
    state.calculate_scroll_offset(viewport_width);

    assert_eq!(state.scroll_offset, 0);
    assert_eq!(state.textarea.cursor().1, 0);
}

#[test]
fn test_scroll_offset_exactly_fits_viewport() {
    let mut state = InputState::new();
    let viewport_width = 10;

    state.textarea.insert_str("1234567890");
    state.calculate_scroll_offset(viewport_width);

    assert_eq!(state.textarea.cursor().1, 10);
    assert_eq!(state.scroll_offset, 1);

    state.textarea.insert_char('x');
    state.calculate_scroll_offset(viewport_width);

    assert_eq!(state.textarea.cursor().1, 11);
    assert_eq!(state.scroll_offset, 2);

    state.textarea.delete_char();
    state.calculate_scroll_offset(viewport_width);

    assert_eq!(state.textarea.cursor().1, 10);
    assert_eq!(state.scroll_offset, 1);
}

#[test]
fn test_sync_delete_from_end_realistic_scenario() {
    let mut state = InputState::new();
    let viewport_width = 20;

    state.textarea.insert_str(
        ".services | select(.[].capacityProviderStrategy | length > 0) | .[0].capacity",
    );
    state.calculate_scroll_offset(viewport_width);

    assert_cursor_visible(&state, viewport_width);

    for i in 0..30 {
        state.textarea.delete_char();
        state.calculate_scroll_offset(viewport_width);

        assert_cursor_visible(&state, viewport_width);

        let visible = get_visible_text(&state, viewport_width);
        let cursor = state.textarea.cursor().1;
        let scroll = state.scroll_offset;

        assert!(
            cursor - scroll <= visible.chars().count(),
            "Iteration {}: Cursor at {} with scroll {} points beyond visible text '{}'",
            i,
            cursor,
            scroll,
            visible
        );
    }
}

#[test]
fn test_sync_after_every_operation() {
    let mut state = InputState::new();
    let viewport_width = 15;

    state
        .textarea
        .insert_str("abcdefghijklmnopqrstuvwxyz0123456789");
    state.calculate_scroll_offset(viewport_width);
    assert_cursor_visible(&state, viewport_width);

    for _ in 0..20 {
        state.textarea.move_cursor(tui_textarea::CursorMove::Back);
        state.calculate_scroll_offset(viewport_width);
        assert_cursor_visible(&state, viewport_width);
    }

    for _ in 0..10 {
        state.textarea.delete_next_char();
        state.calculate_scroll_offset(viewport_width);
        assert_cursor_visible(&state, viewport_width);
    }

    for _ in 0..10 {
        state.textarea.delete_char();
        state.calculate_scroll_offset(viewport_width);
        assert_cursor_visible(&state, viewport_width);
    }

    for _ in 0..5 {
        state
            .textarea
            .move_cursor(tui_textarea::CursorMove::Forward);
        state.calculate_scroll_offset(viewport_width);
        assert_cursor_visible(&state, viewport_width);
    }
}

#[test]
fn test_sync_visible_text_contains_cursor() {
    let mut state = InputState::new();
    let viewport_width = 10;

    state.textarea.insert_str("0123456789ABCDEFGHIJKLMNOP");
    state.calculate_scroll_offset(viewport_width);

    let test_positions = vec![0, 5, 10, 15, 20, 25];

    for &target_pos in &test_positions {
        while state.textarea.cursor().1 > 0 {
            state.textarea.move_cursor(tui_textarea::CursorMove::Head);
        }
        for _ in 0..target_pos {
            state
                .textarea
                .move_cursor(tui_textarea::CursorMove::Forward);
        }

        state.calculate_scroll_offset(viewport_width);

        let cursor = state.textarea.cursor().1;
        let scroll = state.scroll_offset;
        let visible = get_visible_text(&state, viewport_width);
        let full_text = state.query();

        let expected_start = scroll;
        let expected_end = (scroll + viewport_width).min(full_text.chars().count());
        let expected_visible: String = full_text
            .chars()
            .skip(expected_start)
            .take(expected_end - expected_start)
            .collect();

        assert_eq!(
            visible, expected_visible,
            "At cursor {}: visible text mismatch (scroll={})",
            cursor, scroll
        );

        let cursor_in_viewport = cursor - scroll;
        assert!(
            cursor_in_viewport <= visible.chars().count(),
            "At cursor {}: cursor_in_viewport {} > visible.len() {}",
            cursor,
            cursor_in_viewport,
            visible.chars().count()
        );
    }
}

#[test]
fn test_sync_no_empty_space_with_short_text() {
    let mut state = InputState::new();
    let viewport_width = 20;

    state.textarea.insert_str("12345678901234567890ABCDE");
    state.calculate_scroll_offset(viewport_width);
    assert_cursor_visible(&state, viewport_width);

    for _ in 0..15 {
        state.textarea.delete_char();
        state.calculate_scroll_offset(viewport_width);
        assert_cursor_visible(&state, viewport_width);
    }

    let text_len = state.query().chars().count();
    if text_len < viewport_width {
        let max_expected_scroll = text_len.saturating_sub(viewport_width - 1);
        assert!(
            state.scroll_offset <= max_expected_scroll,
            "Text length {}, viewport {}: scroll_offset {} creates empty space",
            text_len,
            viewport_width,
            state.scroll_offset
        );
    }
}

#[test]
fn test_sync_deletion_middle_maintains_visibility() {
    let mut state = InputState::new();
    let viewport_width = 10;

    state.textarea.insert_str("ABCDEFGHIJKLMNOPQRSTUVWXYZ");

    for _ in 0..11 {
        state.textarea.move_cursor(tui_textarea::CursorMove::Back);
    }
    state.scroll_offset = 10;
    state.calculate_scroll_offset(viewport_width);

    for i in 0..8 {
        let cursor_before = state.textarea.cursor().1;
        state.textarea.delete_next_char();
        state.calculate_scroll_offset(viewport_width);

        assert_cursor_visible(&state, viewport_width);

        let cursor_after = state.textarea.cursor().1;

        assert_eq!(
            cursor_after, cursor_before,
            "Iteration {}: delete_next_char moved cursor from {} to {}",
            i, cursor_before, cursor_after
        );
    }
}

#[test]
fn test_sync_textarea_actually_scrolls() {
    let mut state = InputState::new();
    let viewport_width = 20;

    state
        .textarea
        .insert_str("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
    state.calculate_scroll_offset(viewport_width);

    let initial_scroll = state.scroll_offset;

    for _ in 0..20 {
        state.textarea.delete_char();
    }
    state.calculate_scroll_offset(viewport_width);

    assert!(
        state.scroll_offset < initial_scroll,
        "Expected scroll to reduce from {} after deletions, but got {}",
        initial_scroll,
        state.scroll_offset
    );

    let scroll_before_move = state.scroll_offset;
    let cursor_before_move = state.textarea.cursor().1;

    state.textarea.move_cursor(tui_textarea::CursorMove::Back);
    state.calculate_scroll_offset(viewport_width);

    let scroll_after_move = state.scroll_offset;
    let cursor_after_move = state.textarea.cursor().1;

    assert_eq!(
        cursor_after_move,
        cursor_before_move - 1,
        "Cursor should move one position back"
    );

    let scroll_jump = (scroll_after_move as isize - scroll_before_move as isize).abs();
    assert!(
        scroll_jump <= 1,
        "Scroll jumped by {} after small cursor movement (from {} to {}). \
         This indicates desync between our scroll_offset and textarea's internal scroll.",
        scroll_jump,
        scroll_before_move,
        scroll_after_move
    );
}
