use crate::model::shared::ui_state::ResultNavMode;
use crate::update::action::{
    Action, CursorPosition, ScrollAmount, ScrollDirection, ScrollTarget, ScrollToCursorTarget,
    SelectMotion,
};

use super::{scroll, scroll_to_cursor};
use crate::update::input::vim::types::{
    BrowseVimContext, InspectorVimContext, ResultVimContext, VimModeTransition, VimNavigation,
    VimOperator,
};

pub(in crate::update::input::vim) fn navigation(
    navigation: VimNavigation,
    ctx: BrowseVimContext,
) -> Action {
    match ctx {
        BrowseVimContext::Explorer => explorer_navigation(navigation),
        BrowseVimContext::Inspector(_) => inspector_navigation(navigation),
        BrowseVimContext::Result(result_ctx) => result_navigation(navigation, result_ctx),
    }
}

pub(in crate::update::input::vim) fn mode_transition(
    transition: VimModeTransition,
    ctx: BrowseVimContext,
) -> Action {
    match (transition, ctx) {
        (
            VimModeTransition::Escape,
            BrowseVimContext::Explorer | BrowseVimContext::Inspector(_),
        ) => Action::Escape,
        (VimModeTransition::Escape, BrowseVimContext::Result(result_ctx)) => {
            match result_ctx.mode {
                ResultNavMode::Scroll => Action::Escape,
                ResultNavMode::CellActive => {
                    if result_ctx.has_pending_draft {
                        Action::ResultDiscardCellEdit
                    } else {
                        Action::ResultExitToScroll
                    }
                }
            }
        }
        (VimModeTransition::ConfirmOrEnter, BrowseVimContext::Explorer) => Action::ConfirmSelection,
        (VimModeTransition::ConfirmOrEnter, BrowseVimContext::Result(result_ctx)) => {
            match result_ctx.mode {
                ResultNavMode::Scroll => Action::ResultActivateCell,
                ResultNavMode::CellActive => Action::None,
            }
        }
        (VimModeTransition::Insert, BrowseVimContext::Result(result_ctx))
            if result_ctx.mode == ResultNavMode::CellActive =>
        {
            Action::ResultEnterCellEdit
        }
        (VimModeTransition::ConfirmOrEnter, BrowseVimContext::Inspector(_))
        | (VimModeTransition::Insert | VimModeTransition::Append, _) => Action::None,
    }
}

pub(in crate::update::input::vim) fn operator(
    operator: VimOperator,
    ctx: BrowseVimContext,
) -> Option<Action> {
    match (operator, ctx) {
        (VimOperator::Yank, BrowseVimContext::Inspector(InspectorVimContext::Ddl)) => {
            Some(Action::DdlYank)
        }
        (VimOperator::Yank, BrowseVimContext::Result(result_ctx)) => Some(match result_ctx.mode {
            ResultNavMode::Scroll => Action::None,
            ResultNavMode::CellActive => {
                if result_ctx.yank_pending {
                    Action::ResultRowYank
                } else {
                    Action::ResultRowYankOperatorPending
                }
            }
        }),
        (VimOperator::Delete, BrowseVimContext::Result(result_ctx))
            if result_ctx.mode == ResultNavMode::CellActive =>
        {
            Some(if result_ctx.delete_pending {
                Action::StageRowForDelete
            } else {
                Action::ResultDeleteOperatorPending
            })
        }
        _ => None,
    }
}

fn explorer_navigation(navigation: VimNavigation) -> Action {
    match navigation {
        VimNavigation::MoveDown => Action::Select(SelectMotion::Next),
        VimNavigation::MoveUp => Action::Select(SelectMotion::Previous),
        VimNavigation::MoveToFirst => Action::Select(SelectMotion::First),
        VimNavigation::MoveToLast => Action::Select(SelectMotion::Last),
        VimNavigation::MoveLineStart
        | VimNavigation::MoveLineEnd
        | VimNavigation::MoveWordForward
        | VimNavigation::MoveWordBackward => Action::None,
        VimNavigation::ViewportTop => Action::Select(SelectMotion::ViewportTop),
        VimNavigation::ViewportMiddle => Action::Select(SelectMotion::ViewportMiddle),
        VimNavigation::ViewportBottom => Action::Select(SelectMotion::ViewportBottom),
        VimNavigation::MoveLeft => scroll(
            ScrollTarget::Explorer,
            ScrollDirection::Left,
            ScrollAmount::Line,
        ),
        VimNavigation::MoveRight => scroll(
            ScrollTarget::Explorer,
            ScrollDirection::Right,
            ScrollAmount::Line,
        ),
        VimNavigation::HalfPageDown => Action::Select(SelectMotion::HalfPageDown),
        VimNavigation::HalfPageUp => Action::Select(SelectMotion::HalfPageUp),
        VimNavigation::FullPageDown => Action::Select(SelectMotion::FullPageDown),
        VimNavigation::FullPageUp => Action::Select(SelectMotion::FullPageUp),
        VimNavigation::ScrollCursorCenter => {
            scroll_to_cursor(ScrollToCursorTarget::Explorer, CursorPosition::Center)
        }
        VimNavigation::ScrollCursorTop => {
            scroll_to_cursor(ScrollToCursorTarget::Explorer, CursorPosition::Top)
        }
        VimNavigation::ScrollCursorBottom => {
            scroll_to_cursor(ScrollToCursorTarget::Explorer, CursorPosition::Bottom)
        }
    }
}

fn inspector_navigation(navigation: VimNavigation) -> Action {
    match navigation {
        VimNavigation::MoveDown => scroll(
            ScrollTarget::Inspector,
            ScrollDirection::Down,
            ScrollAmount::Line,
        ),
        VimNavigation::MoveUp => scroll(
            ScrollTarget::Inspector,
            ScrollDirection::Up,
            ScrollAmount::Line,
        ),
        VimNavigation::MoveToFirst => scroll(
            ScrollTarget::Inspector,
            ScrollDirection::Up,
            ScrollAmount::ToStart,
        ),
        VimNavigation::MoveToLast => scroll(
            ScrollTarget::Inspector,
            ScrollDirection::Down,
            ScrollAmount::ToEnd,
        ),
        VimNavigation::MoveLineStart
        | VimNavigation::MoveLineEnd
        | VimNavigation::MoveWordForward
        | VimNavigation::MoveWordBackward
        | VimNavigation::ViewportTop
        | VimNavigation::ViewportMiddle
        | VimNavigation::ViewportBottom
        | VimNavigation::ScrollCursorCenter
        | VimNavigation::ScrollCursorTop
        | VimNavigation::ScrollCursorBottom => Action::None,
        VimNavigation::MoveLeft => scroll(
            ScrollTarget::Inspector,
            ScrollDirection::Left,
            ScrollAmount::Line,
        ),
        VimNavigation::MoveRight => scroll(
            ScrollTarget::Inspector,
            ScrollDirection::Right,
            ScrollAmount::Line,
        ),
        VimNavigation::HalfPageDown => scroll(
            ScrollTarget::Inspector,
            ScrollDirection::Down,
            ScrollAmount::HalfPage,
        ),
        VimNavigation::HalfPageUp => scroll(
            ScrollTarget::Inspector,
            ScrollDirection::Up,
            ScrollAmount::HalfPage,
        ),
        VimNavigation::FullPageDown => scroll(
            ScrollTarget::Inspector,
            ScrollDirection::Down,
            ScrollAmount::FullPage,
        ),
        VimNavigation::FullPageUp => scroll(
            ScrollTarget::Inspector,
            ScrollDirection::Up,
            ScrollAmount::FullPage,
        ),
    }
}

fn result_navigation(navigation: VimNavigation, ctx: ResultVimContext) -> Action {
    match navigation {
        VimNavigation::MoveDown => scroll(
            ScrollTarget::Result,
            ScrollDirection::Down,
            ScrollAmount::Line,
        ),
        VimNavigation::MoveUp => scroll(
            ScrollTarget::Result,
            ScrollDirection::Up,
            ScrollAmount::Line,
        ),
        VimNavigation::MoveToFirst => scroll(
            ScrollTarget::Result,
            ScrollDirection::Up,
            ScrollAmount::ToStart,
        ),
        VimNavigation::MoveToLast => scroll(
            ScrollTarget::Result,
            ScrollDirection::Down,
            ScrollAmount::ToEnd,
        ),
        VimNavigation::MoveLineStart
        | VimNavigation::MoveLineEnd
        | VimNavigation::MoveWordForward
        | VimNavigation::MoveWordBackward => Action::None,
        VimNavigation::ViewportTop => scroll(
            ScrollTarget::Result,
            ScrollDirection::Up,
            ScrollAmount::ViewportTop,
        ),
        VimNavigation::ViewportMiddle => scroll(
            ScrollTarget::Result,
            ScrollDirection::Up,
            ScrollAmount::ViewportMiddle,
        ),
        VimNavigation::ViewportBottom => scroll(
            ScrollTarget::Result,
            ScrollDirection::Down,
            ScrollAmount::ViewportBottom,
        ),
        VimNavigation::MoveLeft => {
            if ctx.mode == ResultNavMode::CellActive {
                Action::ResultCellLeft
            } else {
                scroll(
                    ScrollTarget::Result,
                    ScrollDirection::Left,
                    ScrollAmount::Line,
                )
            }
        }
        VimNavigation::MoveRight => {
            if ctx.mode == ResultNavMode::CellActive {
                Action::ResultCellRight
            } else {
                scroll(
                    ScrollTarget::Result,
                    ScrollDirection::Right,
                    ScrollAmount::Line,
                )
            }
        }
        VimNavigation::HalfPageDown => scroll(
            ScrollTarget::Result,
            ScrollDirection::Down,
            ScrollAmount::HalfPage,
        ),
        VimNavigation::HalfPageUp => scroll(
            ScrollTarget::Result,
            ScrollDirection::Up,
            ScrollAmount::HalfPage,
        ),
        VimNavigation::FullPageDown => scroll(
            ScrollTarget::Result,
            ScrollDirection::Down,
            ScrollAmount::FullPage,
        ),
        VimNavigation::FullPageUp => scroll(
            ScrollTarget::Result,
            ScrollDirection::Up,
            ScrollAmount::FullPage,
        ),
        VimNavigation::ScrollCursorCenter => {
            scroll_to_cursor(ScrollToCursorTarget::Result, CursorPosition::Center)
        }
        VimNavigation::ScrollCursorTop => {
            scroll_to_cursor(ScrollToCursorTarget::Result, CursorPosition::Top)
        }
        VimNavigation::ScrollCursorBottom => {
            scroll_to_cursor(ScrollToCursorTarget::Result, CursorPosition::Bottom)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::model::shared::ui_state::ResultNavMode;
    use crate::update::input::vim::{VimCommand, VimSurfaceContext, action_for_command};
    use rstest::rstest;

    fn result_ctx(mode: ResultNavMode) -> ResultVimContext {
        ResultVimContext {
            mode,
            has_pending_draft: false,
            yank_pending: false,
            delete_pending: false,
        }
    }

    fn browse_result(ctx: ResultVimContext) -> VimSurfaceContext {
        VimSurfaceContext::Browse(BrowseVimContext::Result(ctx))
    }

    #[test]
    fn result_cell_escape_with_draft_discards_edit() {
        let action = action_for_command(
            VimCommand::ModeTransition(VimModeTransition::Escape),
            browse_result(ResultVimContext {
                has_pending_draft: true,
                ..result_ctx(ResultNavMode::CellActive)
            }),
        );

        assert!(matches!(action, Some(Action::ResultDiscardCellEdit)));
    }

    #[test]
    fn result_scroll_mode_move_down_resolves_to_result_line_scroll() {
        let action = action_for_command(
            VimCommand::Navigation(VimNavigation::MoveDown),
            browse_result(result_ctx(ResultNavMode::Scroll)),
        );

        assert!(matches!(
            action,
            Some(Action::Scroll {
                target: ScrollTarget::Result,
                direction: ScrollDirection::Down,
                amount: ScrollAmount::Line,
            })
        ));
    }

    #[test]
    fn explorer_scroll_cursor_center_resolves_to_scroll_to_cursor() {
        let action = action_for_command(
            VimCommand::Navigation(VimNavigation::ScrollCursorCenter),
            VimSurfaceContext::Browse(BrowseVimContext::Explorer),
        );

        assert!(matches!(
            action,
            Some(Action::ScrollToCursor {
                target: ScrollToCursorTarget::Explorer,
                position: CursorPosition::Center,
            })
        ));
    }

    #[test]
    fn inspector_viewport_navigation_resolves_to_none_action() {
        let action = action_for_command(
            VimCommand::Navigation(VimNavigation::ViewportTop),
            VimSurfaceContext::Browse(BrowseVimContext::Inspector(InspectorVimContext::Other)),
        );

        assert!(matches!(action, Some(Action::None)));
    }

    #[rstest]
    #[case(VimNavigation::MoveLeft, Action::ResultCellLeft)]
    #[case(VimNavigation::MoveRight, Action::ResultCellRight)]
    fn result_cell_left_right_use_cell_actions(
        #[case] navigation: VimNavigation,
        #[case] expected: Action,
    ) {
        let action = action_for_command(
            VimCommand::Navigation(navigation),
            browse_result(result_ctx(ResultNavMode::CellActive)),
        );

        assert!(matches!(
            (action, expected),
            (Some(Action::ResultCellLeft), Action::ResultCellLeft)
                | (Some(Action::ResultCellRight), Action::ResultCellRight)
        ));
    }

    #[test]
    fn result_row_yank_without_pending_sets_pending() {
        let action = action_for_command(
            VimCommand::Operator(VimOperator::Yank),
            browse_result(result_ctx(ResultNavMode::CellActive)),
        );

        assert!(matches!(action, Some(Action::ResultRowYankOperatorPending)));
    }

    #[test]
    fn result_row_yank_with_pending_executes_yank() {
        let action = action_for_command(
            VimCommand::Operator(VimOperator::Yank),
            browse_result(ResultVimContext {
                yank_pending: true,
                ..result_ctx(ResultNavMode::CellActive)
            }),
        );

        assert!(matches!(action, Some(Action::ResultRowYank)));
    }

    #[test]
    fn result_row_delete_without_pending_sets_pending() {
        let action = action_for_command(
            VimCommand::Operator(VimOperator::Delete),
            browse_result(result_ctx(ResultNavMode::CellActive)),
        );

        assert!(matches!(action, Some(Action::ResultDeleteOperatorPending)));
    }

    #[test]
    fn result_row_delete_with_pending_stages_delete() {
        let action = action_for_command(
            VimCommand::Operator(VimOperator::Delete),
            browse_result(ResultVimContext {
                delete_pending: true,
                ..result_ctx(ResultNavMode::CellActive)
            }),
        );

        assert!(matches!(action, Some(Action::StageRowForDelete)));
    }

    #[test]
    fn result_cell_yank_is_not_triggered_by_y_operator() {
        let action = action_for_command(
            VimCommand::Operator(VimOperator::Yank),
            browse_result(ResultVimContext {
                yank_pending: false,
                ..result_ctx(ResultNavMode::CellActive)
            }),
        );

        assert!(matches!(action, Some(Action::ResultRowYankOperatorPending)));
    }

    #[test]
    fn inspector_ddl_yank_resolves_to_ddl_yank() {
        let action = action_for_command(
            VimCommand::Operator(VimOperator::Yank),
            VimSurfaceContext::Browse(BrowseVimContext::Inspector(InspectorVimContext::Ddl)),
        );

        assert!(matches!(action, Some(Action::DdlYank)));
    }

    #[test]
    fn result_search_continuation_stays_unsupported() {
        let action = action_for_command(
            VimCommand::SearchContinuation(crate::update::input::vim::SearchContinuation::Next),
            browse_result(result_ctx(ResultNavMode::Scroll)),
        );

        assert!(action.is_none());
    }
}
