//! Workspace popup widget.

use ratatui::{
    buffer::Buffer,
    layout::{Constraint, Direction, Layout, Rect},
    widgets::{Clear, StatefulWidget, Widget},
};
use throbber_widgets_tui::{Throbber, WhichUse};

use super::items::{IssueItem, SessionItem, WorktreeItem};
use super::state::{PopupSection, WorkspacePopupState};
use super::{PopupSizing, SizeHint};
use crate::tui::components::{SelectableList, TextInput};

/// Combined workspace popup widget (sessions + worktrees + issues + branch input)
pub struct WorkspacePopup<'a> {
    /// Active sessions
    sessions: &'a [SessionItem],
    /// Available worktrees
    worktrees: &'a [WorktreeItem],
    /// GitHub issues
    issues: &'a [IssueItem],
    /// Branch name input
    input: &'a str,
    /// Cursor position in input
    cursor: usize,
    /// Current focused section
    section: PopupSection,
}

impl<'a> WorkspacePopup<'a> {
    /// Create new workspace popup
    #[must_use]
    pub fn new(
        sessions: &'a [SessionItem],
        worktrees: &'a [WorktreeItem],
        issues: &'a [IssueItem],
        input: &'a str,
        cursor: usize,
        section: PopupSection,
    ) -> Self {
        Self {
            sessions,
            worktrees,
            issues,
            input,
            cursor,
            section,
        }
    }
}

impl PopupSizing for WorkspacePopup<'_> {
    #[allow(clippy::cast_possible_truncation)] // Safe: clamped to max 25
    fn size_hint(&self) -> SizeHint {
        // Dynamic: can adjust based on section item counts
        // Input(3) + Sessions(min 3) + Worktrees(min 3) + Issues(min 3) + padding
        let sessions_height = self.sessions.len().clamp(1, 5);
        let worktrees_height = self.worktrees.len().clamp(1, 5);
        let issues_height = self.issues.len().clamp(1, 5);
        let min_height = (3 + sessions_height + worktrees_height + issues_height + 6) as u16;
        SizeHint::percent(80, 80)
            .with_min_width(50)
            .with_min_height(min_height.min(25))
    }
}

impl StatefulWidget for WorkspacePopup<'_> {
    type State = WorkspacePopupState;

    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
        // Clear background
        Clear.render(area, buf);

        // Calculate layout: Input(3) | Sessions(30%) | Worktrees(25%) | Issues(25%) | Spinner(1 if loading)
        let has_spinner = state.is_loading();
        let constraints = if has_spinner {
            vec![
                Constraint::Length(3),      // Input
                Constraint::Percentage(30), // Sessions
                Constraint::Percentage(30), // Worktrees
                Constraint::Percentage(30), // Issues
                Constraint::Length(1),      // Spinner
            ]
        } else {
            vec![
                Constraint::Length(3),      // Input
                Constraint::Percentage(30), // Sessions
                Constraint::Percentage(30), // Worktrees
                Constraint::Percentage(30), // Issues
            ]
        };

        let chunks = Layout::default()
            .direction(Direction::Vertical)
            .constraints(constraints)
            .split(area);

        // Render components using new abstractions
        let input_widget = TextInput::new(self.input, self.cursor, "New Branch (Enter: create)")
            .focused(self.section == PopupSection::BranchInput);
        input_widget.render(chunks[0], buf);

        let sessions_list =
            SelectableList::new(self.sessions, "Sessions (Enter: switch, d: close)")
                .focused(self.section == PopupSection::Sessions);
        sessions_list.render(chunks[1], buf, &mut state.session_list);

        let worktrees_list = SelectableList::new(
            self.worktrees,
            "Worktrees (Enter: adopt, d: delete, p: pull)",
        )
        .focused(self.section == PopupSection::Worktrees);
        worktrees_list.render(chunks[2], buf, &mut state.worktree_list);

        let issues_list = SelectableList::new(self.issues, "Issues (Enter: start work)")
            .focused(self.section == PopupSection::Issues);
        issues_list.render(chunks[3], buf, &mut state.issue_list);

        // Render spinner if loading
        if has_spinner {
            let throbber = Throbber::default()
                .label(state.loading_message())
                .style(ratatui::style::Style::default().fg(ratatui::style::Color::Cyan))
                .throbber_style(ratatui::style::Style::default().fg(ratatui::style::Color::Yellow))
                .use_type(WhichUse::Spin);
            StatefulWidget::render(throbber, chunks[4], buf, &mut state.throbber_state);
        }
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use super::*;
    use crate::tui::tabs::StatusIndicator;
    use crate::tui::test_utils::buffer_to_text;
    use crate::worktree::GitWorktreeStatus;
    use std::path::PathBuf;

    #[test]
    fn workspace_popup_renders_empty() {
        let sessions: Vec<SessionItem> = vec![];
        let worktrees: Vec<WorktreeItem> = vec![];
        let popup =
            WorkspacePopup::new(&sessions, &worktrees, &[], "", 0, PopupSection::BranchInput);
        let area = Rect::new(0, 0, 60, 20);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();

        popup.render(area, &mut buf, &mut state);

        let output = buffer_to_text(&buf);
        assert!(output.contains("New Branch"));
        assert!(output.contains("Sessions"));
        assert!(output.contains("Worktrees"));
    }

    #[test]
    fn workspace_popup_renders_with_sessions() {
        let sessions = vec![
            SessionItem::new("session-1", StatusIndicator::Running),
            SessionItem::new("session-2", StatusIndicator::Terminated),
        ];
        let worktrees: Vec<WorktreeItem> = vec![];
        let popup = WorkspacePopup::new(&sessions, &worktrees, &[], "", 0, PopupSection::Sessions);
        let area = Rect::new(0, 0, 60, 20);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();

        popup.render(area, &mut buf, &mut state);

        let output = buffer_to_text(&buf);
        assert!(output.contains("session-1"));
        assert!(output.contains("session-2"));
    }

    #[test]
    fn workspace_popup_renders_sessions_with_branches() {
        let sessions = vec![
            SessionItem::new("session-1", StatusIndicator::Running)
                .branch(Some("feature/test".to_string())),
            SessionItem::new("session-2", StatusIndicator::Terminated)
                .branch(Some("bugfix/issue".to_string())),
        ];
        let worktrees: Vec<WorktreeItem> = vec![];
        let popup = WorkspacePopup::new(&sessions, &worktrees, &[], "", 0, PopupSection::Sessions);
        let area = Rect::new(0, 0, 60, 20);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();

        popup.render(area, &mut buf, &mut state);

        let output = buffer_to_text(&buf);
        assert!(output.contains("session-1"));
        assert!(output.contains("feature/test"));
        assert!(output.contains("session-2"));
        assert!(output.contains("bugfix/issue"));
    }

    #[test]
    fn workspace_popup_renders_pending_indicator() {
        let sessions = vec![
            SessionItem::new("session-1", StatusIndicator::Running).pending(true),
            SessionItem::new("session-2", StatusIndicator::Running).pending(false),
        ];
        let worktrees: Vec<WorktreeItem> = vec![];
        let popup = WorkspacePopup::new(&sessions, &worktrees, &[], "", 0, PopupSection::Sessions);
        let area = Rect::new(0, 0, 60, 20);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();

        popup.render(area, &mut buf, &mut state);

        let output = buffer_to_text(&buf);
        // First session has pending indicator
        assert!(
            output.contains("[!]"),
            "Expected [!] indicator for pending session"
        );
        // Verify it only appears once (only session-1 is pending)
        assert_eq!(output.matches("[!]").count(), 1);
    }

    #[test]
    fn workspace_popup_renders_with_worktrees() {
        let sessions: Vec<SessionItem> = vec![];
        let worktrees = vec![
            WorktreeItem::new(
                "tazuna/abc123",
                PathBuf::from("/worktrees/abc12345"),
                GitWorktreeStatus {
                    dirty: true,
                    ..Default::default()
                },
            ),
            WorktreeItem::new(
                "feature/test",
                PathBuf::from("/worktrees/def67890"),
                GitWorktreeStatus {
                    ahead: 2,
                    behind: 1,
                    ..Default::default()
                },
            ),
        ];
        let popup = WorkspacePopup::new(&sessions, &worktrees, &[], "", 0, PopupSection::Worktrees);
        let area = Rect::new(0, 0, 60, 20);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();

        popup.render(area, &mut buf, &mut state);

        let output = buffer_to_text(&buf);
        // Display names from path
        assert!(output.contains("abc12345"));
        assert!(output.contains("def67890"));
        assert!(output.contains("tazuna/abc123"));
        assert!(output.contains("feature/test"));
        // Status indicators
        assert!(output.contains("[*]")); // dirty
        assert!(output.contains("[↑2↓1]")); // ahead/behind
    }

    #[test]
    fn workspace_popup_renders_input_with_cursor() {
        let sessions: Vec<SessionItem> = vec![];
        let worktrees: Vec<WorktreeItem> = vec![];
        let popup = WorkspacePopup::new(
            &sessions,
            &worktrees,
            &[],
            "feature/",
            8,
            PopupSection::BranchInput,
        );
        let area = Rect::new(0, 0, 60, 20);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();

        popup.render(area, &mut buf, &mut state);

        let output = buffer_to_text(&buf);
        assert!(output.contains("feature/"));
    }

    #[test]
    fn workspace_popup_section_focus_changes_border() {
        let sessions = vec![SessionItem::new("test", StatusIndicator::Running)];
        let worktrees: Vec<WorktreeItem> = vec![];
        let area = Rect::new(0, 0, 60, 20);

        // BranchInput focused
        let popup =
            WorkspacePopup::new(&sessions, &worktrees, &[], "", 0, PopupSection::BranchInput);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();
        popup.render(area, &mut buf, &mut state);

        // Sessions focused
        let popup = WorkspacePopup::new(&sessions, &worktrees, &[], "", 0, PopupSection::Sessions);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();
        popup.render(area, &mut buf, &mut state);

        // Worktrees focused
        let popup = WorkspacePopup::new(&sessions, &worktrees, &[], "", 0, PopupSection::Worktrees);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();
        popup.render(area, &mut buf, &mut state);

        // If we get here without panicking, the render works for all sections
    }

    #[test]
    fn workspace_popup_renders_with_spinner_when_loading() {
        use super::super::state::LoadingOperation;

        let sessions: Vec<SessionItem> = vec![];
        let worktrees: Vec<WorktreeItem> = vec![];
        let popup =
            WorkspacePopup::new(&sessions, &worktrees, &[], "", 0, PopupSection::BranchInput);
        let area = Rect::new(0, 0, 60, 25);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();
        state.loading = LoadingOperation::Fetching;

        popup.render(area, &mut buf, &mut state);

        let output = buffer_to_text(&buf);
        assert!(output.contains("Fetching"));
    }

    #[test]
    fn workspace_popup_renders_pulling_spinner() {
        use super::super::state::LoadingOperation;

        let sessions: Vec<SessionItem> = vec![];
        let worktrees: Vec<WorktreeItem> = vec![];
        let popup =
            WorkspacePopup::new(&sessions, &worktrees, &[], "", 0, PopupSection::BranchInput);
        let area = Rect::new(0, 0, 60, 25);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();
        state.loading = LoadingOperation::Pulling {
            path: PathBuf::from("/test"),
        };

        popup.render(area, &mut buf, &mut state);

        let output = buffer_to_text(&buf);
        assert!(output.contains("Pulling"));
    }

    #[test]
    fn workspace_popup_renders_deleting_spinner() {
        use super::super::state::LoadingOperation;

        let sessions: Vec<SessionItem> = vec![];
        let worktrees: Vec<WorktreeItem> = vec![];
        let popup =
            WorkspacePopup::new(&sessions, &worktrees, &[], "", 0, PopupSection::BranchInput);
        let area = Rect::new(0, 0, 60, 25);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();
        state.loading = LoadingOperation::Deleting {
            path: PathBuf::from("/test"),
        };

        popup.render(area, &mut buf, &mut state);

        let output = buffer_to_text(&buf);
        assert!(output.contains("Deleting"));
    }

    #[test]
    fn workspace_popup_renders_with_issues() {
        let sessions: Vec<SessionItem> = vec![];
        let worktrees: Vec<WorktreeItem> = vec![];
        let issues = vec![
            IssueItem::new(1, "First issue", "[bug]"),
            IssueItem::new(2, "Second issue", "[enhancement]"),
        ];
        let popup =
            WorkspacePopup::new(&sessions, &worktrees, &issues, "", 0, PopupSection::Issues);
        let area = Rect::new(0, 0, 80, 25);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();

        popup.render(area, &mut buf, &mut state);

        let output = buffer_to_text(&buf);
        assert!(output.contains("#1"));
        assert!(output.contains("First issue"));
        assert!(output.contains("[bug]"));
    }

    #[test]
    fn workspace_popup_renders_issues_without_labels() {
        let sessions: Vec<SessionItem> = vec![];
        let worktrees: Vec<WorktreeItem> = vec![];
        let issues = vec![IssueItem::new(42, "No labels issue", "")];
        let popup =
            WorkspacePopup::new(&sessions, &worktrees, &issues, "", 0, PopupSection::Issues);
        let area = Rect::new(0, 0, 80, 25);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();

        popup.render(area, &mut buf, &mut state);

        let output = buffer_to_text(&buf);
        assert!(output.contains("#42"));
        assert!(output.contains("No labels issue"));
    }

    // Focus-aware highlight tests
    #[test]
    fn sessions_focused_shows_highlight_symbol() {
        let sessions = vec![SessionItem::new("test-session", StatusIndicator::Running)];
        let worktrees: Vec<WorktreeItem> = vec![];
        let popup = WorkspacePopup::new(
            &sessions,
            &worktrees,
            &[],
            "",
            0,
            PopupSection::Sessions, // Sessions focused
        );
        let area = Rect::new(0, 0, 60, 20);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();
        state.session_list.select(Some(0));

        popup.render(area, &mut buf, &mut state);

        let output = buffer_to_text(&buf);
        // Focused section should show highlight symbol "> "
        assert!(
            output.contains("> "),
            "Expected highlight symbol when sessions focused"
        );
    }

    #[test]
    fn sessions_unfocused_hides_highlight_symbol() {
        let sessions = vec![SessionItem::new("test-session", StatusIndicator::Running)];
        let worktrees: Vec<WorktreeItem> = vec![];
        let popup = WorkspacePopup::new(
            &sessions,
            &worktrees,
            &[],
            "",
            0,
            PopupSection::Worktrees, // Sessions NOT focused
        );
        let area = Rect::new(0, 0, 60, 20);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();
        state.session_list.select(Some(0));

        popup.render(area, &mut buf, &mut state);

        // Count "> " occurrences - should not appear in sessions area when unfocused
        let output = buffer_to_text(&buf);
        // When sessions unfocused, should show "  " (2 spaces) instead of "> "
        // The session name should appear without "> " prefix
        assert!(
            !output.contains("> ●") && !output.contains("> ✗"),
            "Unfocused sessions should not show highlight symbol"
        );
    }

    #[test]
    fn worktrees_focused_shows_highlight_symbol() {
        let sessions: Vec<SessionItem> = vec![];
        let worktrees = vec![WorktreeItem::new(
            "feature/test",
            PathBuf::from("/worktrees/abc12345"),
            GitWorktreeStatus::default(),
        )];
        let popup = WorkspacePopup::new(
            &sessions,
            &worktrees,
            &[],
            "",
            0,
            PopupSection::Worktrees, // Worktrees focused
        );
        let area = Rect::new(0, 0, 60, 20);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();
        state.worktree_list.select(Some(0));

        popup.render(area, &mut buf, &mut state);

        let output = buffer_to_text(&buf);
        assert!(
            output.contains("> "),
            "Expected highlight symbol when worktrees focused"
        );
    }

    #[test]
    fn worktrees_unfocused_hides_highlight_symbol() {
        let sessions: Vec<SessionItem> = vec![];
        let worktrees = vec![WorktreeItem::new(
            "feature/test",
            PathBuf::from("/worktrees/abc12345"),
            GitWorktreeStatus::default(),
        )];
        let popup = WorkspacePopup::new(
            &sessions,
            &worktrees,
            &[],
            "",
            0,
            PopupSection::Sessions, // Worktrees NOT focused
        );
        let area = Rect::new(0, 0, 60, 20);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();
        state.worktree_list.select(Some(0));

        popup.render(area, &mut buf, &mut state);

        let output = buffer_to_text(&buf);
        // When worktrees unfocused, should not show "> ◆"
        assert!(
            !output.contains("> ◆"),
            "Unfocused worktrees should not show highlight symbol"
        );
    }

    #[test]
    fn issues_focused_shows_highlight_symbol() {
        let sessions: Vec<SessionItem> = vec![];
        let worktrees: Vec<WorktreeItem> = vec![];
        let issues = vec![IssueItem::new(1, "Test issue", "bug")];
        let popup = WorkspacePopup::new(
            &sessions,
            &worktrees,
            &issues,
            "",
            0,
            PopupSection::Issues, // Issues focused
        );
        let area = Rect::new(0, 0, 80, 25);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();
        state.issue_list.select(Some(0));

        popup.render(area, &mut buf, &mut state);

        let output = buffer_to_text(&buf);
        assert!(
            output.contains("> "),
            "Expected highlight symbol when issues focused"
        );
    }

    #[test]
    fn issues_unfocused_hides_highlight_symbol() {
        let sessions: Vec<SessionItem> = vec![];
        let worktrees: Vec<WorktreeItem> = vec![];
        let issues = vec![IssueItem::new(1, "Test issue", "bug")];
        let popup = WorkspacePopup::new(
            &sessions,
            &worktrees,
            &issues,
            "",
            0,
            PopupSection::BranchInput, // Issues NOT focused
        );
        let area = Rect::new(0, 0, 80, 25);
        let mut buf = Buffer::empty(area);
        let mut state = WorkspacePopupState::new();
        state.issue_list.select(Some(0));

        popup.render(area, &mut buf, &mut state);

        let output = buffer_to_text(&buf);
        // When issues unfocused, should not show "> #"
        assert!(
            !output.contains("> #"),
            "Unfocused issues should not show highlight symbol"
        );
    }
}
