use crossterm::event::{MouseEvent, MouseEventKind};

use crate::{
    app::state::{AppState, SelectionAutoscroll, SelectionAutoscrollDirection},
    terminal::TerminalRuntimeRegistry,
};

impl AppState {
    pub(crate) fn update_selection_cursor(
        &mut self,
        terminal_runtimes: &TerminalRuntimeRegistry,
        pane_id: crate::layout::PaneId,
        screen_col: u16,
        screen_row: u16,
    ) {
        let Some(info) = self.pane_info_by_id(pane_id).cloned() else {
            return;
        };
        let metrics = self.pane_scroll_metrics(terminal_runtimes, pane_id);
        if let Some(selection) = self.selection.as_mut() {
            selection.drag(screen_col, screen_row, info.inner_rect, metrics);
        }
    }

    fn selection_edge_scroll_lines(distance: u16) -> usize {
        usize::from(distance).saturating_mul(3).clamp(3, 15)
    }

    pub(super) fn update_selection_drag(
        &mut self,
        terminal_runtimes: &TerminalRuntimeRegistry,
        screen_col: u16,
        screen_row: u16,
    ) {
        let Some(pane_id) = self.selection.as_ref().map(|selection| selection.pane_id) else {
            return;
        };
        let Some(info) = self.pane_info_by_id(pane_id).cloned() else {
            return;
        };

        let top = info.inner_rect.y;
        let bottom = info.inner_rect.y + info.inner_rect.height.saturating_sub(1);

        // Only activate autoscroll when the user is actively dragging.
        // An anchored click in the hot zone should not start the timer.
        // Check before advancing the cursor: if already Dragging from a prior
        // event, it stays true. If Anchored, the mouse must have moved away
        // from the anchor cell for this to count as a real drag.
        let was_dragging = self.selection.as_ref().is_some_and(|s| s.is_dragging());
        let anchor_differs_from_mouse = self.selection.as_ref().is_some_and(|s| {
            // Convert anchor to screen coords for comparison.
            // Anchor is stored in absolute row; for a simple screen
            // comparison, check whether the mouse is on a different
            // cell than the anchor's screen position.
            let (ar, ac) = s.anchor_screen_pos(
                info.inner_rect,
                self.pane_scroll_metrics(terminal_runtimes, s.pane_id),
            );
            ar != screen_row || ac != screen_col
        });
        let is_dragging = was_dragging || anchor_differs_from_mouse;

        // Advance the selection cursor.
        self.update_selection_cursor(terminal_runtimes, pane_id, screen_col, screen_row);

        // If the mouse is on a different cell than the anchor but drag()
        // didn't transition (cursor clamped to edge == anchor), force
        // Dragging so the selection becomes visible and autoscroll can run.
        if is_dragging {
            if let Some(sel) = self.selection.as_mut() {
                if sel.is_just_click() {
                    sel.force_dragging();
                }
            }
        }

        if screen_row < top {
            // Cursor above pane — immediate scroll + set autoscroll state
            if is_dragging {
                self.scroll_pane_up(
                    terminal_runtimes,
                    pane_id,
                    Self::selection_edge_scroll_lines(top - screen_row),
                );
                // Re-advance cursor after scroll so it reflects the new viewport position
                self.update_selection_cursor(terminal_runtimes, pane_id, screen_col, screen_row);
                self.selection_autoscroll = Some(SelectionAutoscroll {
                    direction: SelectionAutoscrollDirection::Up,
                    last_mouse_screen_col: screen_col,
                    last_mouse_screen_row: screen_row,
                    inner_rect: info.inner_rect,
                });
            }
        } else if screen_row > bottom {
            // Cursor below pane — immediate scroll + set autoscroll state
            if is_dragging {
                self.scroll_pane_down(
                    terminal_runtimes,
                    pane_id,
                    Self::selection_edge_scroll_lines(screen_row - bottom),
                );
                // Re-advance cursor after scroll so it reflects the new viewport position
                self.update_selection_cursor(terminal_runtimes, pane_id, screen_col, screen_row);
                self.selection_autoscroll = Some(SelectionAutoscroll {
                    direction: SelectionAutoscrollDirection::Down,
                    last_mouse_screen_col: screen_col,
                    last_mouse_screen_row: screen_row,
                    inner_rect: info.inner_rect,
                });
            }
        } else if screen_row == top {
            // Hot zone: top edge row — no immediate scroll, set autoscroll state
            if is_dragging {
                self.selection_autoscroll = Some(SelectionAutoscroll {
                    direction: SelectionAutoscrollDirection::Up,
                    last_mouse_screen_col: screen_col,
                    last_mouse_screen_row: screen_row,
                    inner_rect: info.inner_rect,
                });
            } else {
                self.selection_autoscroll = None;
            }
        } else if screen_row == bottom {
            // Hot zone: bottom edge row — no immediate scroll, set autoscroll state
            if is_dragging {
                self.selection_autoscroll = Some(SelectionAutoscroll {
                    direction: SelectionAutoscrollDirection::Down,
                    last_mouse_screen_col: screen_col,
                    last_mouse_screen_row: screen_row,
                    inner_rect: info.inner_rect,
                });
            } else {
                self.selection_autoscroll = None;
            }
        } else {
            // Safe zone: inside pane, not on edge rows — clear autoscroll
            self.selection_autoscroll = None;
        }
    }

    pub(super) fn scroll_selection_with_wheel(
        &mut self,
        terminal_runtimes: &TerminalRuntimeRegistry,
        mouse: MouseEvent,
    ) -> bool {
        let lines_per_notch = self.mouse_scroll_lines;

        let Some(selection) = self.selection.as_ref() else {
            return false;
        };
        if !selection.is_in_progress() {
            return false;
        }
        let pane_id = selection.pane_id;
        self.focus_pane(pane_id);
        match mouse.kind {
            MouseEventKind::ScrollUp => {
                self.scroll_pane_up(terminal_runtimes, pane_id, lines_per_notch)
            }
            MouseEventKind::ScrollDown => {
                self.scroll_pane_down(terminal_runtimes, pane_id, lines_per_notch)
            }
            _ => return false,
        }
        self.update_selection_cursor(terminal_runtimes, pane_id, mouse.column, mouse.row);
        true
    }
}

#[cfg(test)]
mod autoscroll_tests {
    use super::*;
    use crate::layout::PaneInfo;
    use crate::terminal::TerminalRuntimeRegistry;
    use crate::workspace::Workspace;
    use ratatui::layout::Rect;

    /// Build an AppState with one workspace/pane and pane_infos populated
    /// so pane_info_by_id works. Returns (state, pane_id).
    fn make_state_with_pane() -> (AppState, crate::layout::PaneId) {
        let mut state = AppState::test_new();
        let ws = Workspace::test_new("test");
        let pane_id = ws.tabs[0].root_pane;
        state.workspaces.push(ws);
        state.active = Some(0);
        state.view.pane_infos.push(PaneInfo {
            id: pane_id,
            rect: Rect::new(0, 0, 80, 24),
            inner_rect: Rect::new(0, 0, 80, 24),
            scrollbar_rect: None,
            borders: ratatui::widgets::Borders::NONE,
            is_focused: true,
        });
        (state, pane_id)
    }

    #[test]
    fn above_pane_sets_autoscroll_up() {
        // Build state with pane starting at row 5 so we can drag above it
        let mut state = AppState::test_new();
        let ws = Workspace::test_new("test");
        let pane_id = ws.tabs[0].root_pane;
        state.workspaces.push(ws);
        state.active = Some(0);
        state.view.pane_infos.push(PaneInfo {
            id: pane_id,
            rect: Rect::new(0, 5, 80, 24),
            inner_rect: Rect::new(0, 5, 80, 24),
            scrollbar_rect: None,
            borders: ratatui::widgets::Borders::NONE,
            is_focused: true,
        });
        // Anchor at (5, 10), drag to different cell above pane
        let mut sel = crate::selection::Selection::anchor(pane_id, 5, 10, None);
        sel.drag(4, 5, Rect::new(0, 5, 80, 24), None);
        state.selection = Some(sel);
        let terminal_runtimes = TerminalRuntimeRegistry::new();
        state.update_selection_drag(&terminal_runtimes, 5, 4);
        let autoscroll = state.selection_autoscroll.as_ref().unwrap();
        assert_eq!(autoscroll.direction, SelectionAutoscrollDirection::Up);
    }

    #[test]
    fn top_hot_zone_sets_autoscroll_up_on_drag() {
        let (mut state, pane_id) = make_state_with_pane();
        // Anchor at (5, 10), drag to top edge row (row 0) — different cell
        let mut sel = crate::selection::Selection::anchor(pane_id, 5, 10, None);
        sel.drag(0, 0, Rect::new(0, 0, 80, 24), None);
        state.selection = Some(sel);
        let terminal_runtimes = TerminalRuntimeRegistry::new();
        state.update_selection_drag(&terminal_runtimes, 0, 0);
        let autoscroll = state.selection_autoscroll.as_ref().unwrap();
        assert_eq!(autoscroll.direction, SelectionAutoscrollDirection::Up);
    }

    #[test]
    fn top_hot_zone_clears_autoscroll_on_click() {
        // An anchored click on the top edge row should NOT start autoscroll.
        let (mut state, pane_id) = make_state_with_pane();
        state.selection = Some(crate::selection::Selection::anchor(pane_id, 0, 0, None));
        // Same-cell drag on top edge row — still anchored
        let terminal_runtimes = TerminalRuntimeRegistry::new();
        state.update_selection_drag(&terminal_runtimes, 0, 0);
        assert!(state.selection_autoscroll.is_none());
    }

    #[test]
    fn bottom_hot_zone_sets_autoscroll_down_on_drag() {
        let (mut state, pane_id) = make_state_with_pane();
        // Anchor at (0, 0), drag to bottom edge row (row 23) — different cell
        let mut sel = crate::selection::Selection::anchor(pane_id, 0, 0, None);
        sel.drag(23, 0, Rect::new(0, 0, 80, 24), None);
        state.selection = Some(sel);
        let terminal_runtimes = TerminalRuntimeRegistry::new();
        state.update_selection_drag(&terminal_runtimes, 0, 23);
        let autoscroll = state.selection_autoscroll.as_ref().unwrap();
        assert_eq!(autoscroll.direction, SelectionAutoscrollDirection::Down);
    }

    #[test]
    fn bottom_hot_zone_clears_autoscroll_on_click() {
        // An anchored click on the bottom edge row should NOT start autoscroll.
        let (mut state, pane_id) = make_state_with_pane();
        // Anchor at bottom edge row
        state.selection = Some(crate::selection::Selection::anchor(pane_id, 23, 0, None));
        // Same-cell drag — still anchored
        let terminal_runtimes = TerminalRuntimeRegistry::new();
        state.update_selection_drag(&terminal_runtimes, 0, 23);
        assert!(state.selection_autoscroll.is_none());
    }

    #[test]
    fn below_pane_sets_autoscroll_down_on_drag() {
        let (mut state, pane_id) = make_state_with_pane();
        // Anchor at (0, 0), drag to different cell below pane
        let mut sel = crate::selection::Selection::anchor(pane_id, 0, 0, None);
        sel.drag(5, 5, Rect::new(0, 0, 80, 24), None);
        state.selection = Some(sel);
        // Drag cursor one row below the pane bottom
        let terminal_runtimes = TerminalRuntimeRegistry::new();
        state.update_selection_drag(&terminal_runtimes, 0, 24);
        let autoscroll = state.selection_autoscroll.as_ref().unwrap();
        assert_eq!(autoscroll.direction, SelectionAutoscrollDirection::Down);
    }

    #[test]
    fn safe_zone_clears_autoscroll() {
        let (mut state, pane_id) = make_state_with_pane();
        // Anchor at (0, 0), drag to (5, 5) so it's truly dragging
        let mut sel = crate::selection::Selection::anchor(pane_id, 0, 0, None);
        sel.drag(5, 5, Rect::new(0, 0, 80, 24), None);
        state.selection = Some(sel);
        // Set autoscroll first
        state.selection_autoscroll = Some(SelectionAutoscroll {
            direction: SelectionAutoscrollDirection::Down,
            last_mouse_screen_col: 5,
            last_mouse_screen_row: 23,
            inner_rect: Rect::new(0, 0, 80, 24),
        });
        // Move cursor into safe zone (middle of pane, not on edge rows)
        let terminal_runtimes = TerminalRuntimeRegistry::new();
        state.update_selection_drag(&terminal_runtimes, 5, 12);
        assert!(state.selection_autoscroll.is_none());
    }
}
