//! Popup state types.

use std::path::PathBuf;

use ratatui::widgets::ListState;
use throbber_widgets_tui::ThrobberState;

/// Unified loading state for async operations
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum LoadingOperation {
    /// No operation in progress
    #[default]
    Idle,
    /// Fetching from remotes (git fetch)
    Fetching,
    /// Pulling changes for specific worktree
    Pulling { path: PathBuf },
    /// Deleting a worktree
    Deleting { path: PathBuf },
    /// Fetching GitHub issues list
    FetchingIssues,
    /// Fetching single issue details
    FetchingIssue { issue_number: u32 },
    /// Generating action choices for an issue
    GeneratingActions { issue_number: u32 },
}

impl LoadingOperation {
    /// Returns display message for this loading operation
    #[must_use]
    pub const fn message(&self) -> &'static str {
        match self {
            Self::Idle => "",
            Self::Fetching => "Fetching...",
            Self::Pulling { .. } => "Pulling...",
            Self::Deleting { .. } => "Deleting...",
            Self::FetchingIssues => "Loading issues...",
            Self::FetchingIssue { .. } => "Fetching issue...",
            Self::GeneratingActions { .. } => "Generating actions...",
        }
    }

    /// Check if loading operation is in progress
    #[must_use]
    pub const fn is_loading(&self) -> bool {
        !matches!(self, Self::Idle)
    }
}

/// Section focus within combined workspace popup
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PopupSection {
    /// Branch name input field
    #[default]
    BranchInput,
    /// Active sessions list
    Sessions,
    /// Available worktrees list
    Worktrees,
    /// GitHub issues list
    Issues,
}

impl PopupSection {
    /// Move to next section
    #[must_use]
    pub fn next(self) -> Self {
        match self {
            Self::BranchInput => Self::Sessions,
            Self::Sessions => Self::Worktrees,
            Self::Worktrees => Self::Issues,
            Self::Issues => Self::BranchInput,
        }
    }

    /// Move to previous section
    #[must_use]
    pub fn prev(self) -> Self {
        match self {
            Self::BranchInput => Self::Issues,
            Self::Sessions => Self::BranchInput,
            Self::Worktrees => Self::Sessions,
            Self::Issues => Self::Worktrees,
        }
    }
}

/// Combined workspace popup state
#[derive(Debug, Default)]
pub struct WorkspacePopupState {
    /// Current focus section
    pub section: PopupSection,
    /// Branch input buffer
    pub input: String,
    /// Cursor position in input
    pub cursor: usize,
    /// Session list selection
    pub session_list: ListState,
    /// Worktree list selection
    pub worktree_list: ListState,
    /// Issue list selection
    pub issue_list: ListState,
    /// Loading state for async operations
    pub loading: LoadingOperation,
    /// Spinner animation state
    pub throbber_state: ThrobberState,
}

impl WorkspacePopupState {
    /// Create new workspace popup state
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Move to next section
    pub fn next_section(&mut self) {
        self.section = self.section.next();
    }

    /// Move to previous section
    pub fn prev_section(&mut self) {
        self.section = self.section.prev();
    }

    /// Insert character at cursor position
    pub fn input_char(&mut self, c: char) {
        self.input.insert(self.cursor, c);
        self.cursor += c.len_utf8();
    }

    /// Delete character before cursor (backspace)
    pub fn input_backspace(&mut self) {
        if self.cursor > 0 {
            let prev_char_boundary = self
                .input
                .char_indices()
                .take_while(|(i, _)| *i < self.cursor)
                .last()
                .map_or(0, |(i, _)| i);
            self.input.remove(prev_char_boundary);
            self.cursor = prev_char_boundary;
        }
    }

    /// Move cursor left
    pub fn cursor_left(&mut self) {
        if self.cursor > 0 {
            self.cursor = self
                .input
                .char_indices()
                .take_while(|(i, _)| *i < self.cursor)
                .last()
                .map_or(0, |(i, _)| i);
        }
    }

    /// Move cursor right
    pub fn cursor_right(&mut self) {
        if self.cursor < self.input.len() {
            self.cursor = self
                .input
                .char_indices()
                .find(|(i, _)| *i > self.cursor)
                .map_or(self.input.len(), |(i, _)| i);
        }
    }

    /// Clear input and reset cursor
    pub fn clear_input(&mut self) {
        self.input.clear();
        self.cursor = 0;
    }

    /// Advance spinner animation (call on each frame when loading)
    pub fn tick_spinner(&mut self) {
        if self.loading.is_loading() {
            self.throbber_state.calc_next();
        }
    }

    /// Check if loading operation is in progress
    #[must_use]
    pub fn is_loading(&self) -> bool {
        self.loading.is_loading()
    }

    /// Get loading message for spinner
    #[must_use]
    pub fn loading_message(&self) -> &'static str {
        self.loading.message()
    }
}

/// State for action selection popup
#[derive(Debug, Default)]
pub struct ActionSelectPopupState {
    /// List selection state
    pub list_state: ListState,
}

impl ActionSelectPopupState {
    /// Create new action select popup state
    #[must_use]
    pub fn new() -> Self {
        let mut state = Self::default();
        state.list_state.select(Some(0));
        state
    }

    /// Select next item
    pub fn select_next(&mut self, len: usize) {
        if len == 0 {
            return;
        }
        let i = match self.list_state.selected() {
            Some(i) => (i + 1) % len,
            None => 0,
        };
        self.list_state.select(Some(i));
    }

    /// Select previous item
    pub fn select_prev(&mut self, len: usize) {
        if len == 0 {
            return;
        }
        let i = match self.list_state.selected() {
            Some(i) => {
                if i == 0 {
                    len - 1
                } else {
                    i - 1
                }
            }
            None => 0,
        };
        self.list_state.select(Some(i));
    }

    /// Get currently selected index
    #[must_use]
    pub fn selected(&self) -> Option<usize> {
        self.list_state.selected()
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use super::*;

    // PopupSection tests
    #[test]
    fn popup_section_default_is_branch_input() {
        assert_eq!(PopupSection::default(), PopupSection::BranchInput);
    }

    #[test]
    fn popup_section_next_cycles() {
        assert_eq!(PopupSection::BranchInput.next(), PopupSection::Sessions);
        assert_eq!(PopupSection::Sessions.next(), PopupSection::Worktrees);
        assert_eq!(PopupSection::Worktrees.next(), PopupSection::Issues);
        assert_eq!(PopupSection::Issues.next(), PopupSection::BranchInput);
    }

    #[test]
    fn popup_section_prev_cycles() {
        assert_eq!(PopupSection::BranchInput.prev(), PopupSection::Issues);
        assert_eq!(PopupSection::Sessions.prev(), PopupSection::BranchInput);
        assert_eq!(PopupSection::Worktrees.prev(), PopupSection::Sessions);
        assert_eq!(PopupSection::Issues.prev(), PopupSection::Worktrees);
    }

    // WorkspacePopupState tests
    #[test]
    fn workspace_popup_state_default() {
        let state = WorkspacePopupState::new();
        assert_eq!(state.section, PopupSection::BranchInput);
        assert!(state.input.is_empty());
        assert_eq!(state.cursor, 0);
    }

    #[test]
    fn workspace_popup_state_section_navigation() {
        let mut state = WorkspacePopupState::new();
        assert_eq!(state.section, PopupSection::BranchInput);

        state.next_section();
        assert_eq!(state.section, PopupSection::Sessions);

        state.next_section();
        assert_eq!(state.section, PopupSection::Worktrees);

        state.next_section();
        assert_eq!(state.section, PopupSection::Issues);

        state.next_section();
        assert_eq!(state.section, PopupSection::BranchInput);

        state.prev_section();
        assert_eq!(state.section, PopupSection::Issues);

        state.prev_section();
        assert_eq!(state.section, PopupSection::Worktrees);
    }

    #[test]
    fn workspace_popup_state_input_char() {
        let mut state = WorkspacePopupState::new();
        state.input_char('f');
        state.input_char('o');
        state.input_char('o');
        assert_eq!(state.input, "foo");
        assert_eq!(state.cursor, 3);
    }

    #[test]
    fn workspace_popup_state_input_backspace() {
        let mut state = WorkspacePopupState::new();
        state.input_char('a');
        state.input_char('b');
        state.input_char('c');
        assert_eq!(state.input, "abc");

        state.input_backspace();
        assert_eq!(state.input, "ab");
        assert_eq!(state.cursor, 2);

        state.input_backspace();
        assert_eq!(state.input, "a");
        assert_eq!(state.cursor, 1);
    }

    #[test]
    fn workspace_popup_state_input_backspace_empty() {
        let mut state = WorkspacePopupState::new();
        state.input_backspace(); // should not panic
        assert!(state.input.is_empty());
        assert_eq!(state.cursor, 0);
    }

    #[test]
    fn workspace_popup_state_cursor_movement() {
        let mut state = WorkspacePopupState::new();
        state.input_char('a');
        state.input_char('b');
        state.input_char('c');
        assert_eq!(state.cursor, 3);

        state.cursor_left();
        assert_eq!(state.cursor, 2);

        state.cursor_left();
        assert_eq!(state.cursor, 1);

        state.cursor_right();
        assert_eq!(state.cursor, 2);

        state.cursor_right();
        assert_eq!(state.cursor, 3);

        // At end, should stay
        state.cursor_right();
        assert_eq!(state.cursor, 3);

        // At start, should stay
        state.cursor = 0;
        state.cursor_left();
        assert_eq!(state.cursor, 0);
    }

    #[test]
    fn workspace_popup_state_clear_input() {
        let mut state = WorkspacePopupState::new();
        state.input_char('t');
        state.input_char('e');
        state.input_char('s');
        state.input_char('t');

        state.clear_input();
        assert!(state.input.is_empty());
        assert_eq!(state.cursor, 0);
    }

    #[test]
    fn workspace_popup_state_unicode_input() {
        let mut state = WorkspacePopupState::new();
        state.input_char('日');
        state.input_char('本');
        assert_eq!(state.input, "日本");
        assert_eq!(state.cursor, 6); // 3 bytes each

        state.input_backspace();
        assert_eq!(state.input, "日");
        assert_eq!(state.cursor, 3);
    }

    // LoadingOperation tests
    use rstest::rstest;

    #[test]
    fn loading_operation_default_is_idle() {
        assert_eq!(LoadingOperation::default(), LoadingOperation::Idle);
    }

    #[rstest]
    #[case(LoadingOperation::Idle, false, "")]
    #[case(LoadingOperation::Fetching, true, "Fetching...")]
    #[case(LoadingOperation::Pulling { path: PathBuf::from("/test") }, true, "Pulling...")]
    #[case(LoadingOperation::Deleting { path: PathBuf::from("/test") }, true, "Deleting...")]
    #[case(LoadingOperation::FetchingIssues, true, "Loading issues...")]
    #[case(LoadingOperation::FetchingIssue { issue_number: 1 }, true, "Fetching issue...")]
    #[case(LoadingOperation::GeneratingActions { issue_number: 42 }, true, "Generating actions...")]
    fn loading_operation_behavior(
        #[case] op: LoadingOperation,
        #[case] expected_loading: bool,
        #[case] expected_message: &str,
    ) {
        assert_eq!(op.is_loading(), expected_loading);
        assert_eq!(op.message(), expected_message);
    }

    #[test]
    fn workspace_popup_state_is_loading() {
        let mut state = WorkspacePopupState::new();
        assert!(!state.is_loading());

        state.loading = LoadingOperation::Fetching;
        assert!(state.is_loading());

        state.loading = LoadingOperation::Idle;
        assert!(!state.is_loading());
    }

    #[test]
    fn workspace_popup_state_tick_spinner_when_loading() {
        let mut state = WorkspacePopupState::new();

        // When idle, tick should not change state
        state.tick_spinner();

        // When loading, tick should advance throbber
        state.loading = LoadingOperation::Fetching;
        state.tick_spinner();
        // ThrobberState has internal state that advances
    }

    #[test]
    fn loading_operation_eq() {
        assert_eq!(LoadingOperation::Idle, LoadingOperation::Idle);
        assert_eq!(LoadingOperation::Fetching, LoadingOperation::Fetching);
        assert_eq!(
            LoadingOperation::Pulling {
                path: PathBuf::from("/a")
            },
            LoadingOperation::Pulling {
                path: PathBuf::from("/a")
            }
        );
        assert_eq!(
            LoadingOperation::Deleting {
                path: PathBuf::from("/b")
            },
            LoadingOperation::Deleting {
                path: PathBuf::from("/b")
            }
        );
        assert_eq!(
            LoadingOperation::FetchingIssues,
            LoadingOperation::FetchingIssues
        );
        assert_eq!(
            LoadingOperation::FetchingIssue { issue_number: 1 },
            LoadingOperation::FetchingIssue { issue_number: 1 }
        );
        assert_eq!(
            LoadingOperation::GeneratingActions { issue_number: 42 },
            LoadingOperation::GeneratingActions { issue_number: 42 }
        );
    }

    // ActionSelectPopupState tests
    #[test]
    fn action_select_popup_state_new() {
        let state = ActionSelectPopupState::new();
        assert_eq!(state.selected(), Some(0));
    }

    #[test]
    fn action_select_popup_state_select_next() {
        let mut state = ActionSelectPopupState::new();
        state.select_next(3);
        assert_eq!(state.selected(), Some(1));
        state.select_next(3);
        assert_eq!(state.selected(), Some(2));
        state.select_next(3);
        assert_eq!(state.selected(), Some(0)); // wraps
    }

    #[test]
    fn action_select_popup_state_select_prev() {
        let mut state = ActionSelectPopupState::new();
        state.select_prev(3);
        assert_eq!(state.selected(), Some(2)); // wraps to end
        state.select_prev(3);
        assert_eq!(state.selected(), Some(1));
    }

    #[test]
    fn action_select_popup_state_empty_list() {
        let mut state = ActionSelectPopupState::new();
        state.select_next(0); // should not panic
        state.select_prev(0); // should not panic
    }

    #[test]
    fn action_select_popup_state_select_next_no_initial_selection() {
        let mut state = ActionSelectPopupState::new();
        // Clear the initial selection
        state.list_state = ListState::default();
        assert_eq!(state.selected(), None);

        state.select_next(3);
        assert_eq!(state.selected(), Some(0));
    }

    #[test]
    fn action_select_popup_state_select_prev_no_initial_selection() {
        let mut state = ActionSelectPopupState::new();
        // Clear the initial selection
        state.list_state = ListState::default();
        assert_eq!(state.selected(), None);

        state.select_prev(3);
        assert_eq!(state.selected(), Some(0));
    }
}
