use crate::model::app_state::AppState;
use crate::model::shared::input_mode::InputMode;
use crate::update::action::{Action, InputTarget, ListMotion, ListTarget};
use crate::update::dispatch_result::DispatchResult;
use crate::update::input::palette::palette_command_count;

pub fn reduce_input(state: &mut AppState, action: &Action) -> DispatchResult {
    match action {
        Action::Paste(text) => match state.modal.active_mode() {
            InputMode::TablePicker => {
                state.ui.table_picker.insert_filter_str(text);
                DispatchResult::handled()
            }
            InputMode::ErTablePicker => {
                state.ui.er_picker.insert_filter_str(text);
                DispatchResult::handled()
            }
            InputMode::CommandLine => {
                let clean: String = text.chars().filter(|c| *c != '\n' && *c != '\r').collect();
                state.command_line_input.insert_str(&clean);
                state
                    .command_line_input
                    .update_viewport(state.command_line_visible_width);
                DispatchResult::handled()
            }
            InputMode::CellEdit => {
                let clean: String = text.chars().filter(|c| *c != '\n' && *c != '\r').collect();
                state
                    .result_interaction
                    .cell_edit_input_mut()
                    .insert_str(&clean);
                DispatchResult::handled()
            }
            InputMode::QueryHistoryPicker => {
                state.query_history_picker.insert_filter_str(text);
                DispatchResult::handled()
            }
            _ => DispatchResult::pass(),
        },

        Action::TextInput {
            target: InputTarget::Filter,
            ch: c,
        } => {
            state.ui.table_picker.insert_filter_char(*c);
            DispatchResult::handled()
        }
        Action::TextBackspace {
            target: InputTarget::Filter,
        } => {
            state.ui.table_picker.backspace_filter();
            DispatchResult::handled()
        }
        Action::TextMoveCursor {
            target: InputTarget::Filter,
            direction: movement,
        } => {
            state.ui.table_picker.move_filter_cursor(*movement);
            DispatchResult::handled()
        }

        Action::EnterCommandLine => {
            state.modal.push_mode(InputMode::CommandLine);
            state.command_line_input.clear();
            DispatchResult::handled()
        }
        Action::ExitCommandLine => {
            state.modal.pop_mode();
            DispatchResult::handled()
        }
        Action::TextInput {
            target: InputTarget::CommandLine,
            ch: c,
        } => {
            state.command_line_input.insert_char(*c);
            state
                .command_line_input
                .update_viewport(state.command_line_visible_width);
            DispatchResult::handled()
        }
        Action::TextBackspace {
            target: InputTarget::CommandLine,
        } => {
            state.command_line_input.backspace();
            state
                .command_line_input
                .update_viewport(state.command_line_visible_width);
            DispatchResult::handled()
        }
        Action::TextMoveCursor {
            target: InputTarget::CommandLine,
            direction: movement,
        } => {
            state.command_line_input.move_cursor(*movement);
            state
                .command_line_input
                .update_viewport(state.command_line_visible_width);
            DispatchResult::handled()
        }

        // -----------------------------------------------------------------
        // Picker navigation (TablePicker, ErTablePicker, CommandPalette)
        // -----------------------------------------------------------------
        Action::ListSelect {
            target: ListTarget::TablePicker,
            motion: ListMotion::Next,
        } => {
            let max = state.filtered_tables().len().saturating_sub(1);
            if state.ui.table_picker.selected() < max {
                state
                    .ui
                    .table_picker
                    .set_selection(state.ui.table_picker.selected() + 1);
            }
            DispatchResult::handled()
        }
        Action::ListSelect {
            target: ListTarget::TablePicker | ListTarget::CommandPalette,
            motion: ListMotion::Previous,
        } => {
            state
                .ui
                .table_picker
                .set_selection(state.ui.table_picker.selected().saturating_sub(1));
            DispatchResult::handled()
        }
        Action::ListSelect {
            target: ListTarget::ErTablePicker,
            motion: ListMotion::Next,
        } => {
            let max = state.er_filtered_tables().len().saturating_sub(1);
            if state.ui.er_picker.selected() < max {
                state
                    .ui
                    .er_picker
                    .set_selection(state.ui.er_picker.selected() + 1);
            }
            DispatchResult::handled()
        }
        Action::ListSelect {
            target: ListTarget::ErTablePicker,
            motion: ListMotion::Previous,
        } => {
            state
                .ui
                .er_picker
                .set_selection(state.ui.er_picker.selected().saturating_sub(1));
            DispatchResult::handled()
        }
        Action::ListSelect {
            target: ListTarget::CommandPalette,
            motion: ListMotion::Next,
        } => {
            let max = palette_command_count(state.settings.saved_keymap_preset()).saturating_sub(1);
            if state.ui.table_picker.selected() < max {
                state
                    .ui
                    .table_picker
                    .set_selection(state.ui.table_picker.selected() + 1);
            }
            DispatchResult::handled()
        }

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::services::AppServices;
    use crate::update::browse::navigation::dispatch_navigation;
    use std::time::Instant;

    mod paste {
        use super::*;

        #[test]
        fn table_picker_appends_text() {
            let mut state = AppState::new("test".to_string());
            state.modal.set_mode(InputMode::TablePicker);

            let effects = dispatch_navigation(
                &mut state,
                &Action::Paste("hello".to_string()),
                &AppServices::stub(),
                Instant::now(),
            );

            assert!(effects.is_handled());
            assert_eq!(state.ui.table_picker.filter_input().content(), "hello");
        }

        #[test]
        fn table_picker_strips_newlines() {
            let mut state = AppState::new("test".to_string());
            state.modal.set_mode(InputMode::TablePicker);

            dispatch_navigation(
                &mut state,
                &Action::Paste("hel\nlo\r\n".to_string()),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.table_picker.filter_input().content(), "hello");
        }

        #[test]
        fn table_picker_resets_selection() {
            let mut state = AppState::new("test".to_string());
            state.modal.set_mode(InputMode::TablePicker);
            state.ui.table_picker.set_selection(5);

            dispatch_navigation(
                &mut state,
                &Action::Paste("x".to_string()),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.table_picker.selected(), 0);
        }

        #[test]
        fn command_line_appends_text() {
            let mut state = AppState::new("test".to_string());
            state.modal.set_mode(InputMode::CommandLine);

            dispatch_navigation(
                &mut state,
                &Action::Paste("quit".to_string()),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.command_line_input.content(), "quit");
        }

        #[test]
        fn command_line_strips_newlines() {
            let mut state = AppState::new("test".to_string());
            state.modal.set_mode(InputMode::CommandLine);

            dispatch_navigation(
                &mut state,
                &Action::Paste("qu\nit".to_string()),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.command_line_input.content(), "quit");
        }

        #[test]
        fn normal_mode_returns_none() {
            let mut state = AppState::new("test".to_string());
            state.modal.set_mode(InputMode::Normal);

            let effects = dispatch_navigation(
                &mut state,
                &Action::Paste("text".to_string()),
                &AppServices::stub(),
                Instant::now(),
            );

            assert!(effects.is_pass());
        }

        #[test]
        fn er_table_picker_appends_to_filter() {
            let mut state = AppState::new("test".to_string());
            state.modal.set_mode(InputMode::ErTablePicker);

            let effects = dispatch_navigation(
                &mut state,
                &Action::Paste("public.users".to_string()),
                &AppServices::stub(),
                Instant::now(),
            );

            assert!(effects.is_handled());
            assert_eq!(state.ui.er_picker.filter_input().content(), "public.users");
            assert_eq!(state.ui.er_picker.selected(), 0);
        }

        #[test]
        fn er_table_picker_strips_newlines() {
            let mut state = AppState::new("test".to_string());
            state.modal.set_mode(InputMode::ErTablePicker);

            dispatch_navigation(
                &mut state,
                &Action::Paste("public\n.users\r\n".to_string()),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.er_picker.filter_input().content(), "public.users");
        }

        #[test]
        fn query_history_picker_appends_to_filter() {
            let mut state = AppState::new("test".to_string());
            state.modal.set_mode(InputMode::QueryHistoryPicker);
            state.query_history_picker.set_selection_for_test(3);

            let effects = dispatch_navigation(
                &mut state,
                &Action::Paste("users".to_string()),
                &AppServices::stub(),
                Instant::now(),
            );

            assert!(effects.is_handled());
            assert_eq!(state.query_history_picker.filter_input().content(), "users");
            assert_eq!(state.query_history_picker.selected(), 0);
        }

        #[test]
        fn query_history_picker_strips_newlines() {
            let mut state = AppState::new("test".to_string());
            state.modal.set_mode(InputMode::QueryHistoryPicker);

            dispatch_navigation(
                &mut state,
                &Action::Paste("us\ners\r\n".to_string()),
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.query_history_picker.filter_input().content(), "users");
        }
    }

    mod command_line_return_stack {
        use super::*;

        #[test]
        fn enter_from_normal_and_exit_returns_to_normal() {
            let mut state = AppState::new("test".to_string());

            dispatch_navigation(
                &mut state,
                &Action::EnterCommandLine,
                &AppServices::stub(),
                Instant::now(),
            );
            assert_eq!(state.input_mode(), InputMode::CommandLine);

            dispatch_navigation(
                &mut state,
                &Action::ExitCommandLine,
                &AppServices::stub(),
                Instant::now(),
            );
            assert_eq!(state.input_mode(), InputMode::Normal);
        }

        #[test]
        fn enter_from_cell_edit_and_exit_returns_to_cell_edit() {
            let mut state = AppState::new("test".to_string());
            state.modal.set_mode(InputMode::CellEdit);

            dispatch_navigation(
                &mut state,
                &Action::EnterCommandLine,
                &AppServices::stub(),
                Instant::now(),
            );
            assert_eq!(state.input_mode(), InputMode::CommandLine);

            dispatch_navigation(
                &mut state,
                &Action::ExitCommandLine,
                &AppServices::stub(),
                Instant::now(),
            );
            assert_eq!(state.input_mode(), InputMode::CellEdit);
        }
    }

    mod picker_navigation {
        use super::*;
        use crate::domain::{DatabaseMetadata, TableSummary};
        use std::sync::Arc;

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

        #[test]
        fn table_picker_next_increments() {
            let mut state = state_with_tables(5);
            state.modal.set_mode(InputMode::TablePicker);

            dispatch_navigation(
                &mut state,
                &Action::ListSelect {
                    target: ListTarget::TablePicker,
                    motion: ListMotion::Next,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.table_picker.selected(), 1);
        }

        #[test]
        fn table_picker_next_stops_at_last() {
            let mut state = state_with_tables(3);
            state.modal.set_mode(InputMode::TablePicker);
            state.ui.table_picker.set_selection(2);

            dispatch_navigation(
                &mut state,
                &Action::ListSelect {
                    target: ListTarget::TablePicker,
                    motion: ListMotion::Next,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.table_picker.selected(), 2);
        }

        #[test]
        fn table_picker_previous_decrements() {
            let mut state = state_with_tables(5);
            state.modal.set_mode(InputMode::TablePicker);
            state.ui.table_picker.set_selection(3);

            dispatch_navigation(
                &mut state,
                &Action::ListSelect {
                    target: ListTarget::TablePicker,
                    motion: ListMotion::Previous,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.table_picker.selected(), 2);
        }

        #[test]
        fn table_picker_previous_stops_at_zero() {
            let mut state = state_with_tables(5);
            state.modal.set_mode(InputMode::TablePicker);

            dispatch_navigation(
                &mut state,
                &Action::ListSelect {
                    target: ListTarget::TablePicker,
                    motion: ListMotion::Previous,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.table_picker.selected(), 0);
        }

        #[test]
        fn er_picker_next_increments() {
            let mut state = state_with_tables(5);
            state.modal.set_mode(InputMode::ErTablePicker);

            dispatch_navigation(
                &mut state,
                &Action::ListSelect {
                    target: ListTarget::ErTablePicker,
                    motion: ListMotion::Next,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.er_picker.selected(), 1);
        }

        #[test]
        fn er_picker_previous_stops_at_zero() {
            let mut state = state_with_tables(5);
            state.modal.set_mode(InputMode::ErTablePicker);

            dispatch_navigation(
                &mut state,
                &Action::ListSelect {
                    target: ListTarget::ErTablePicker,
                    motion: ListMotion::Previous,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.er_picker.selected(), 0);
        }

        #[test]
        fn command_palette_next_increments() {
            let mut state = AppState::new("test".to_string());
            state.modal.set_mode(InputMode::CommandPalette);

            dispatch_navigation(
                &mut state,
                &Action::ListSelect {
                    target: ListTarget::CommandPalette,
                    motion: ListMotion::Next,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.table_picker.selected(), 1);
        }

        #[test]
        fn command_palette_previous_stops_at_zero() {
            let mut state = AppState::new("test".to_string());
            state.modal.set_mode(InputMode::CommandPalette);

            dispatch_navigation(
                &mut state,
                &Action::ListSelect {
                    target: ListTarget::CommandPalette,
                    motion: ListMotion::Previous,
                },
                &AppServices::stub(),
                Instant::now(),
            );

            assert_eq!(state.ui.table_picker.selected(), 0);
        }
    }
}
