use crate::model::app_state::AppState;
use crate::model::shared::focused_pane::FocusedPane;
use crate::model::shared::key_sequence::KeySequenceState;
use crate::model::shared::ui_state::{scroll_max_offset, text_display_width};
use crate::update::action::{
    Action, CursorPosition, ScrollAmount, ScrollDirection, ScrollTarget, ScrollToCursorTarget,
    SelectMotion,
};
use crate::update::dispatch_result::DispatchResult;

use super::explorer_item_count;

pub fn reduce_explorer(state: &mut AppState, action: &Action) -> DispatchResult {
    match action {
        Action::Select(SelectMotion::Next) => {
            if state.ui.focused_pane == FocusedPane::Explorer {
                let len = state.tables().len();
                if len > 0 && state.ui.explorer_selected < len - 1 {
                    state
                        .ui
                        .set_explorer_selection(Some(state.ui.explorer_selected + 1));
                }
            }
            DispatchResult::handled()
        }
        Action::Select(SelectMotion::Previous) => {
            if state.ui.focused_pane == FocusedPane::Explorer && !state.tables().is_empty() {
                let new_idx = state.ui.explorer_selected.saturating_sub(1);
                state.ui.set_explorer_selection(Some(new_idx));
            }
            DispatchResult::handled()
        }
        Action::Select(SelectMotion::First) => {
            if state.ui.focused_pane == FocusedPane::Explorer && !state.tables().is_empty() {
                state.ui.set_explorer_selection(Some(0));
            }
            DispatchResult::handled()
        }
        Action::Select(SelectMotion::Last) => {
            if state.ui.focused_pane == FocusedPane::Explorer {
                let len = state.tables().len();
                if len > 0 {
                    state.ui.set_explorer_selection(Some(len - 1));
                }
            }
            DispatchResult::handled()
        }
        Action::Select(SelectMotion::ViewportMiddle) => {
            if state.ui.focused_pane == FocusedPane::Explorer {
                let len = explorer_item_count(state);
                let visible = state.ui.explorer_visible_items();
                if len > 0 && visible > 0 {
                    let displayed =
                        visible.min(len.saturating_sub(state.ui.explorer_scroll_offset));
                    let target = state.ui.explorer_scroll_offset + displayed / 2;
                    state.ui.set_explorer_selection(Some(target));
                }
            }
            DispatchResult::handled()
        }
        Action::Select(SelectMotion::ViewportTop) => {
            if state.ui.focused_pane == FocusedPane::Explorer {
                let len = explorer_item_count(state);
                if len > 0 {
                    let target = state.ui.explorer_scroll_offset.min(len.saturating_sub(1));
                    state.ui.set_explorer_selection(Some(target));
                }
            }
            DispatchResult::handled()
        }
        Action::Select(SelectMotion::ViewportBottom) => {
            if state.ui.focused_pane == FocusedPane::Explorer {
                let len = explorer_item_count(state);
                let visible = state.ui.explorer_visible_items();
                if len > 0 && visible > 0 {
                    let displayed =
                        visible.min(len.saturating_sub(state.ui.explorer_scroll_offset));
                    let target = state.ui.explorer_scroll_offset + displayed.saturating_sub(1);
                    state.ui.set_explorer_selection(Some(target));
                }
            }
            DispatchResult::handled()
        }

        Action::ScrollToCursor {
            target: ScrollToCursorTarget::Explorer,
            position: CursorPosition::Center,
        } => {
            state.ui.key_sequence = KeySequenceState::Idle;
            let len = explorer_item_count(state);
            let visible = state.ui.explorer_visible_items();
            if len > 0 && visible > 0 {
                let selected = state.ui.explorer_selected;
                let max_offset = len.saturating_sub(visible);
                state.ui.explorer_scroll_offset =
                    selected.saturating_sub(visible / 2).min(max_offset);
            }
            DispatchResult::handled()
        }
        Action::ScrollToCursor {
            target: ScrollToCursorTarget::Explorer,
            position: CursorPosition::Top,
        } => {
            state.ui.key_sequence = KeySequenceState::Idle;
            let len = explorer_item_count(state);
            let visible = state.ui.explorer_visible_items();
            if len > 0 && visible > 0 {
                let selected = state.ui.explorer_selected;
                let max_offset = len.saturating_sub(visible);
                state.ui.explorer_scroll_offset = selected.min(max_offset);
            }
            DispatchResult::handled()
        }
        Action::ScrollToCursor {
            target: ScrollToCursorTarget::Explorer,
            position: CursorPosition::Bottom,
        } => {
            state.ui.key_sequence = KeySequenceState::Idle;
            let len = explorer_item_count(state);
            let visible = state.ui.explorer_visible_items();
            if len > 0 && visible > 0 {
                let selected = state.ui.explorer_selected;
                let max_offset = len.saturating_sub(visible);
                state.ui.explorer_scroll_offset = selected
                    .saturating_sub(visible.saturating_sub(1))
                    .min(max_offset);
            }
            DispatchResult::handled()
        }

        Action::Select(motion @ (SelectMotion::HalfPageDown | SelectMotion::FullPageDown)) => {
            let len = explorer_item_count(state);
            if len == 0 {
                return DispatchResult::handled();
            }
            let visible = state.ui.explorer_visible_items();
            if visible == 0 {
                return DispatchResult::handled();
            }
            let delta = match motion {
                SelectMotion::HalfPageDown => (visible / 2).max(1),
                _ => visible.max(1),
            };
            let max_idx = len.saturating_sub(1);
            let max_offset = len.saturating_sub(visible);
            state.ui.explorer_selected = (state.ui.explorer_selected + delta).min(max_idx);
            state.ui.explorer_scroll_offset =
                (state.ui.explorer_scroll_offset + delta).min(max_offset);
            DispatchResult::handled()
        }
        Action::Select(motion @ (SelectMotion::HalfPageUp | SelectMotion::FullPageUp)) => {
            let len = explorer_item_count(state);
            if len == 0 {
                return DispatchResult::handled();
            }
            let visible = state.ui.explorer_visible_items();
            if visible == 0 {
                return DispatchResult::handled();
            }
            let delta = match motion {
                SelectMotion::HalfPageUp => (visible / 2).max(1),
                _ => visible.max(1),
            };
            state.ui.explorer_selected = state.ui.explorer_selected.saturating_sub(delta);
            state.ui.explorer_scroll_offset = state.ui.explorer_scroll_offset.saturating_sub(delta);
            DispatchResult::handled()
        }

        Action::Scroll {
            target: ScrollTarget::Explorer,
            direction: ScrollDirection::Left,
            amount: ScrollAmount::Line,
        } => {
            state.ui.explorer_horizontal_offset =
                state.ui.explorer_horizontal_offset.saturating_sub(1);
            DispatchResult::handled()
        }
        Action::Scroll {
            target: ScrollTarget::Explorer,
            direction: ScrollDirection::Right,
            amount: ScrollAmount::Line,
        } => {
            let max_name_width = state
                .tables()
                .iter()
                .map(|t| text_display_width(&t.qualified_name()))
                .max()
                .unwrap_or(0);
            let max_offset = scroll_max_offset(max_name_width, state.ui.explorer_content_width);
            if state.ui.explorer_horizontal_offset < max_offset {
                state.ui.explorer_horizontal_offset += 1;
            }
            DispatchResult::handled()
        }

        _ => DispatchResult::pass(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::domain::{DatabaseMetadata, TableSummary};
    use crate::model::shared::key_sequence::Prefix;
    use crate::services::AppServices;
    use crate::update::browse::navigation::dispatch_navigation;
    use rstest::rstest;
    use std::sync::Arc;
    use std::time::Instant;

    fn state_with_tables(count: usize, pane_height: u16) -> AppState {
        let mut state = AppState::new("test".to_string());
        state.ui.explorer_pane_height = pane_height;
        state.ui.focused_pane = FocusedPane::Explorer;
        let tables: Vec<TableSummary> = (0..count)
            .map(|i| TableSummary::new("public".to_string(), format!("table_{i}"), Some(0), false))
            .collect();
        state.session.set_metadata(Some(Arc::new(DatabaseMetadata {
            database_name: "test".to_string(),
            schemas: vec![],
            table_summaries: tables,
        })));
        state.ui.set_explorer_selection(Some(0));
        state
    }

    fn state_with_named_tables(names: &[&str], content_width: usize) -> AppState {
        let mut state = AppState::new("test".to_string());
        state.ui.focused_pane = FocusedPane::Explorer;
        state.ui.explorer_content_width = content_width;
        let tables: Vec<TableSummary> = names
            .iter()
            .map(|name| {
                TableSummary::new("public".to_string(), (*name).to_string(), Some(0), false)
            })
            .collect();
        state.session.set_metadata(Some(Arc::new(DatabaseMetadata {
            database_name: "test".to_string(),
            schemas: vec![],
            table_summaries: tables,
        })));
        state
    }

    mod page_scroll {
        use super::*;

        #[test]
        fn half_page_down_jumps_by_correct_delta() {
            let mut state = state_with_tables(50, 23);
            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::HalfPageDown),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 10);
        }

        #[test]
        fn half_page_down_clamped_at_last() {
            let mut state = state_with_tables(50, 23);
            state.ui.set_explorer_selection(Some(45));

            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::HalfPageDown),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 49);
        }

        #[test]
        fn half_page_up_clamped_at_zero() {
            let mut state = state_with_tables(50, 23);
            state.ui.set_explorer_selection(Some(3));

            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::HalfPageUp),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 0);
        }

        #[test]
        fn full_page_down_jumps_by_visible() {
            let mut state = state_with_tables(50, 23);
            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::FullPageDown),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 20);
        }

        #[test]
        fn empty_list_does_nothing() {
            let mut state = AppState::new("test".to_string());
            state.ui.explorer_pane_height = 23;

            let effects = dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::HalfPageDown),
                &AppServices::stub(),
                Instant::now(),
            );

            assert!(effects.is_handled());
            assert_eq!(state.ui.explorer_selected, 0);
        }

        #[test]
        fn zero_height_pane_is_noop() {
            let mut state = state_with_tables(50, 0);
            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::HalfPageDown),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 0);
            assert_eq!(state.ui.explorer_scroll_offset, 0);
        }

        #[test]
        fn half_page_down_moves_both_selection_and_scroll() {
            let mut state = state_with_tables(50, 23);
            state.ui.explorer_selected = 15;
            state.ui.explorer_scroll_offset = 10;

            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::HalfPageDown),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 25);
            assert_eq!(state.ui.explorer_scroll_offset, 20);
        }

        #[test]
        fn half_page_up_moves_both_selection_and_scroll() {
            let mut state = state_with_tables(50, 23);
            state.ui.explorer_selected = 25;
            state.ui.explorer_scroll_offset = 20;

            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::HalfPageUp),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 15);
            assert_eq!(state.ui.explorer_scroll_offset, 10);
        }

        #[test]
        fn half_page_down_preserves_relative_position() {
            let mut state = state_with_tables(50, 23);
            state.ui.explorer_selected = 15;
            state.ui.explorer_scroll_offset = 10;

            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::HalfPageDown),
                &AppServices::stub(),
                Instant::now(),
            );

            let relative = state.ui.explorer_selected - state.ui.explorer_scroll_offset;
            assert_eq!(relative, 5);
        }

        #[test]
        fn data_fewer_than_viewport_scroll_stays_zero() {
            let mut state = state_with_tables(10, 23);
            state.ui.explorer_selected = 3;

            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::HalfPageDown),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 9);
            assert_eq!(state.ui.explorer_scroll_offset, 0);
        }

        #[test]
        fn full_page_down_moves_both_selection_and_scroll() {
            let mut state = state_with_tables(50, 23);
            state.ui.explorer_selected = 10;
            state.ui.explorer_scroll_offset = 5;

            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::FullPageDown),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 30);
            assert_eq!(state.ui.explorer_scroll_offset, 25);
        }

        #[test]
        fn full_page_up_moves_both_selection_and_scroll() {
            let mut state = state_with_tables(50, 23);
            state.ui.explorer_selected = 30;
            state.ui.explorer_scroll_offset = 25;

            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::FullPageUp),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 10);
            assert_eq!(state.ui.explorer_scroll_offset, 5);
        }

        #[test]
        fn full_page_down_clamps_near_bottom() {
            let mut state = state_with_tables(50, 23);
            state.ui.explorer_selected = 40;
            state.ui.explorer_scroll_offset = 25;

            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::FullPageDown),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 49);
            assert_eq!(state.ui.explorer_scroll_offset, 30);
        }

        #[test]
        fn full_page_up_clamps_near_top() {
            let mut state = state_with_tables(50, 23);
            state.ui.explorer_selected = 10;
            state.ui.explorer_scroll_offset = 5;

            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::FullPageUp),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 0);
            assert_eq!(state.ui.explorer_scroll_offset, 0);
        }
    }

    mod viewport_selection {
        use super::*;

        #[test]
        fn select_middle_moves_to_viewport_center() {
            let mut state = state_with_tables(50, 23);
            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::ViewportMiddle),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 10);
            assert_eq!(state.ui.explorer_scroll_offset, 0);
        }

        #[test]
        fn select_middle_respects_scroll_offset() {
            let mut state = state_with_tables(50, 23);
            state.ui.explorer_scroll_offset = 15;
            state.ui.explorer_selected = 15;
            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::ViewportMiddle),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 25);
            assert_eq!(state.ui.explorer_scroll_offset, 15);
        }

        #[test]
        fn select_viewport_top_moves_to_first_visible_item() {
            let mut state = state_with_tables(50, 23);
            state.ui.explorer_scroll_offset = 10;
            state.ui.explorer_selected = 20;

            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::ViewportTop),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 10);
        }

        #[test]
        fn select_viewport_bottom_moves_to_last_visible_item() {
            let mut state = state_with_tables(50, 23);
            state.ui.explorer_scroll_offset = 10;
            state.ui.explorer_selected = 15;

            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::ViewportBottom),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 29);
        }

        #[test]
        fn select_viewport_bottom_clamps_to_last_displayed_item() {
            let mut state = state_with_tables(10, 23);
            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::ViewportBottom),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 9);
        }

        #[test]
        fn select_viewport_middle_uses_displayed_count_near_end() {
            let mut state = state_with_tables(50, 23);
            state.ui.explorer_scroll_offset = 40;
            state.ui.explorer_selected = 40;

            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::ViewportMiddle),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 45);
        }

        #[test]
        fn select_viewport_bottom_uses_displayed_count_near_end() {
            let mut state = state_with_tables(50, 23);
            state.ui.explorer_scroll_offset = 40;
            state.ui.explorer_selected = 40;

            dispatch_navigation(
                &mut state,
                &Action::Select(SelectMotion::ViewportBottom),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_selected, 49);
        }
    }

    mod cursor_reposition {
        use super::*;

        #[test]
        fn scroll_cursor_center_centers_viewport_on_selected() {
            let mut state = state_with_tables(50, 23);
            state.ui.explorer_selected = 30;
            state.ui.explorer_scroll_offset = 30;
            state.ui.key_sequence = KeySequenceState::WaitingSecondKey(Prefix::Z);

            dispatch_navigation(
                &mut state,
                &Action::ScrollToCursor {
                    target: ScrollToCursorTarget::Explorer,
                    position: CursorPosition::Center,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_scroll_offset, 20);
            assert_eq!(state.ui.key_sequence, KeySequenceState::Idle);
        }

        #[test]
        fn scroll_cursor_top_puts_selected_at_top() {
            let mut state = state_with_tables(50, 23);
            state.ui.explorer_selected = 15;
            state.ui.explorer_scroll_offset = 0;
            state.ui.key_sequence = KeySequenceState::WaitingSecondKey(Prefix::Z);

            dispatch_navigation(
                &mut state,
                &Action::ScrollToCursor {
                    target: ScrollToCursorTarget::Explorer,
                    position: CursorPosition::Top,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_scroll_offset, 15);
            assert_eq!(state.ui.key_sequence, KeySequenceState::Idle);
        }

        #[test]
        fn scroll_cursor_bottom_puts_selected_at_bottom() {
            let mut state = state_with_tables(50, 23);
            state.ui.explorer_selected = 25;
            state.ui.explorer_scroll_offset = 25;
            state.ui.key_sequence = KeySequenceState::WaitingSecondKey(Prefix::Z);

            dispatch_navigation(
                &mut state,
                &Action::ScrollToCursor {
                    target: ScrollToCursorTarget::Explorer,
                    position: CursorPosition::Bottom,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_scroll_offset, 6);
            assert_eq!(state.ui.key_sequence, KeySequenceState::Idle);
        }

        #[test]
        fn scroll_cursor_top_clamps_to_max_offset() {
            let mut state = state_with_tables(50, 23);
            state.ui.explorer_selected = 45;
            state.ui.explorer_scroll_offset = 30;
            state.ui.key_sequence = KeySequenceState::WaitingSecondKey(Prefix::Z);

            dispatch_navigation(
                &mut state,
                &Action::ScrollToCursor {
                    target: ScrollToCursorTarget::Explorer,
                    position: CursorPosition::Top,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.explorer_scroll_offset, 30);
            assert_eq!(state.ui.key_sequence, KeySequenceState::Idle);
        }
    }

    mod horizontal_scroll {
        use super::*;

        #[rstest]
        #[case(&["abcdefghij"], 4, 32)]
        #[case(&["日本語テスト"], 3, 32)]
        fn repeated_right_scroll_clamps_at_last_visible_column(
            #[case] names: &[&str],
            #[case] content_width: usize,
            #[case] presses: usize,
        ) {
            let mut state = state_with_named_tables(names, content_width);
            let expected = text_display_width(&state.tables()[0].qualified_name())
                .saturating_sub(content_width);

            for _ in 0..presses {
                dispatch_navigation(
                    &mut state,
                    &Action::Scroll {
                        target: ScrollTarget::Explorer,
                        direction: ScrollDirection::Right,
                        amount: ScrollAmount::Line,
                    },
                    &AppServices::stub(),
                    Instant::now(),
                );
            }

            assert_eq!(state.ui.explorer_horizontal_offset, expected);
        }

        #[test]
        fn right_presses_past_end_do_not_increase_offset() {
            let mut state = state_with_named_tables(&["abcdefghij"], 4);
            state.ui.explorer_horizontal_offset =
                text_display_width(&state.tables()[0].qualified_name())
                    .saturating_sub(state.ui.explorer_content_width);

            for _ in 0..3 {
                dispatch_navigation(
                    &mut state,
                    &Action::Scroll {
                        target: ScrollTarget::Explorer,
                        direction: ScrollDirection::Right,
                        amount: ScrollAmount::Line,
                    },
                    &AppServices::stub(),
                    Instant::now(),
                );
            }

            assert_eq!(
                state.ui.explorer_horizontal_offset,
                text_display_width(&state.tables()[0].qualified_name())
                    .saturating_sub(state.ui.explorer_content_width)
            );
        }

        #[test]
        fn left_press_after_end_recovers_one_column() {
            let mut state = state_with_named_tables(&["abcdefghij"], 4);
            state.ui.explorer_horizontal_offset =
                text_display_width(&state.tables()[0].qualified_name())
                    .saturating_sub(state.ui.explorer_content_width);

            dispatch_navigation(
                &mut state,
                &Action::Scroll {
                    target: ScrollTarget::Explorer,
                    direction: ScrollDirection::Left,
                    amount: ScrollAmount::Line,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(
                state.ui.explorer_horizontal_offset,
                text_display_width(&state.tables()[0].qualified_name())
                    .saturating_sub(state.ui.explorer_content_width)
                    .saturating_sub(1)
            );
        }
    }
}
