//! TUI application implementation.
//!
//! Main application struct, event loop, input handling, and rendering.

use std::collections::{HashMap, HashSet};
use std::io::Stdout;
use std::sync::Arc;

use ratatui::{Terminal, backend::CrosstermBackend};
use tokio::sync::{Mutex, mpsc};

use crate::config::NotificationConfig;
use crate::error::SessionError;
use crate::notification::NotificationManager;
use crate::session::{SessionCommand, SessionEvent, SessionId};
use crate::tui::popup::{
    ActionSelectPopupState, IssueItem, LoadingOperation, WorkspacePopupState, WorktreeItem,
};

use super::action::AppAction;
use super::state::{AppState, SessionSnapshot};

/// Throbber animation tick interval
const THROBBER_TICK_MS: u64 = 80;

/// TUI application
#[allow(clippy::struct_excessive_bools)]
pub struct TuiApp {
    /// Application state
    pub(super) state: AppState,
    /// Session snapshots
    pub(super) sessions: Vec<SessionSnapshot>,
    /// Active session index
    pub(super) active_idx: usize,
    /// Terminal buffers (vt100 parsers)
    pub(super) terminal_buffers: HashMap<SessionId, vt100::Parser>,
    /// Command sender
    pub(super) cmd_tx: mpsc::Sender<SessionCommand>,
    /// Terminal size (rows, cols)
    pub(super) size: (u16, u16),
    /// Workspace popup state (combined sessions + worktrees + issues)
    pub(super) popup_state: WorkspacePopupState,
    /// Cached worktree list (refreshed on popup open)
    pub(super) worktrees: Vec<WorktreeItem>,
    /// Cached GitHub issues list
    pub(super) issues: Vec<IssueItem>,
    /// Confirm quit popup state
    pub(super) quit_selected_yes: bool,
    /// Sessions with pending attention (awaiting user action)
    pub(super) pending_sessions: HashSet<SessionId>,
    /// Default arguments for Claude Code
    pub(super) default_args: Vec<String>,
    /// Scroll offset per session
    pub(super) scroll_offsets: HashMap<SessionId, usize>,
    /// Should quit flag
    pub(super) should_quit: bool,
    /// Pending auto-input for newly created session
    pub(super) pending_auto_input: Option<(SessionId, Vec<crate::session::AutoInputStep>)>,
    /// Whether "Claude Code" has been detected in session output (ready for input)
    pub(super) auto_input_ready: bool,
    /// When to send next auto-input step
    pub(super) auto_input_next_at: Option<std::time::Instant>,
    /// Action select popup state
    pub(super) action_select_state: ActionSelectPopupState,
    /// Throbber state for `IssueLoading` popup
    pub(super) issue_loading_throbber: throbber_widgets_tui::ThrobberState,
    /// Today's usage cost (from ccusage)
    pub(super) today_cost: Option<f64>,
    /// Last throbber animation tick
    pub(super) last_throbber_tick: std::time::Instant,
    /// Notification manager for webhook/bell notifications
    pub(super) notification_manager: Arc<Mutex<NotificationManager>>,
}

impl TuiApp {
    /// Create new TUI application
    #[must_use]
    pub fn new(
        cmd_tx: mpsc::Sender<SessionCommand>,
        notification_config: NotificationConfig,
        default_args: Vec<String>,
    ) -> Self {
        let notification_manager =
            Arc::new(Mutex::new(NotificationManager::new(notification_config)));

        Self {
            state: AppState::Normal,
            sessions: Vec::new(),
            active_idx: 0,
            terminal_buffers: HashMap::new(),
            cmd_tx,
            size: (24, 80),
            popup_state: WorkspacePopupState::new(),
            worktrees: Vec::new(),
            issues: Vec::new(),
            quit_selected_yes: false,
            pending_sessions: HashSet::new(),
            default_args,
            scroll_offsets: HashMap::new(),
            should_quit: false,
            pending_auto_input: None,
            auto_input_ready: false,
            auto_input_next_at: None,
            action_select_state: ActionSelectPopupState::new(),
            issue_loading_throbber: throbber_widgets_tui::ThrobberState::default(),
            today_cost: None,
            last_throbber_tick: std::time::Instant::now(),
            notification_manager,
        }
    }

    /// Get current application state
    #[must_use]
    pub fn state(&self) -> &AppState {
        &self.state
    }

    /// Get session count
    #[must_use]
    pub fn session_count(&self) -> usize {
        self.sessions.len()
    }

    /// Check if should quit
    #[must_use]
    pub fn should_quit(&self) -> bool {
        self.should_quit
    }

    /// Get default Claude Code arguments
    #[must_use]
    pub fn default_args(&self) -> &[String] {
        &self.default_args
    }

    /// Set terminal size (sync version for initial setup)
    pub fn set_size(&mut self, rows: u16, cols: u16) {
        self.size = (rows, cols);
        // Resize all terminal buffers
        for parser in self.terminal_buffers.values_mut() {
            parser.set_size(rows, cols);
        }
    }

    /// Mark a session as pending attention
    pub fn mark_pending(&mut self, id: &SessionId) {
        self.pending_sessions.insert(*id);
    }

    /// Clear pending status for a session
    pub fn clear_pending(&mut self, id: &SessionId) {
        self.pending_sessions.remove(id);
    }

    /// Get the count of pending sessions
    #[must_use]
    pub fn pending_count(&self) -> usize {
        self.pending_sessions.len()
    }

    /// Check if a session has pending attention
    #[must_use]
    pub fn is_pending(&self, id: &SessionId) -> bool {
        self.pending_sessions.contains(id)
    }

    /// Dispatch action
    pub async fn dispatch(&mut self, action: AppAction) -> Result<(), SessionError> {
        match action {
            // Session lifecycle
            AppAction::TerminateCurrentSession => self.dispatch_terminate().await,
            AppAction::CloseSession { id } => self.close_session(id).await,
            AppAction::SwitchSession(idx) => self.switch_session(idx),
            AppAction::NextSession => self.next_session(),
            AppAction::PrevSession => self.prev_session(),

            // Popup visibility
            AppAction::ShowWorkspacePopup => self.dispatch_show_workspace().await,
            AppAction::HidePopup => {
                self.state = AppState::Normal;
                self.popup_state.clear_input();
            }
            AppAction::ShowConfirmQuit => {
                self.state = AppState::ConfirmQuit;
                self.quit_selected_yes = false;
            }
            AppAction::DismissError => self.return_to_base(),

            // Quit actions
            AppAction::ConfirmQuit | AppAction::Quit => self.should_quit = true,
            AppAction::ToggleQuitSelection => self.quit_selected_yes = !self.quit_selected_yes,

            // Terminal I/O
            AppAction::SendInput(data) => self.send_input(&data).await,
            AppAction::Scroll(delta) => self.scroll(delta),
            AppAction::ResizeTerminal(rows, cols) => self.resize_terminal(rows, cols).await,

            // List navigation
            AppAction::SelectNext => self.select_next(),
            AppAction::SelectPrev => self.select_prev(),
            AppAction::CrossSectionNext => self.cross_section_next(),
            AppAction::CrossSectionPrev => self.cross_section_prev(),
            AppAction::NextPopupSection => self.popup_state.next_section(),
            AppAction::PrevPopupSection => self.popup_state.prev_section(),
            AppAction::ConfirmSelection => self.dispatch_confirm_selection(),

            // Input field
            AppAction::InputChar(c) => self.popup_state.input_char(c),
            AppAction::InputBackspace => self.popup_state.input_backspace(),
            AppAction::InputCursorLeft => self.popup_state.cursor_left(),
            AppAction::InputCursorRight => self.popup_state.cursor_right(),

            // Worktree operations
            AppAction::CreateSessionWithBranch(branch) => {
                self.dispatch_create_with_branch(branch).await;
            }
            AppAction::AdoptWorktree { path } => self.dispatch_adopt_worktree(&path).await,
            AppAction::DeleteWorktree { path } => self.dispatch_delete_worktree(path).await,
            AppAction::PullWorktree { path } => self.dispatch_pull_worktree(path).await,

            // Issue flow
            AppAction::SelectIssue { number } => self.dispatch_select_issue(number).await,
            AppAction::SelectActionChoice { index } => self.dispatch_select_action(index),
            AppAction::TogglePermissionsChoice => self.dispatch_toggle_permissions(),
            AppAction::ConfirmDangerousPermissions => self.dispatch_confirm_permissions().await,
            AppAction::CancelIssueFlow => self.cancel_issue_flow(),
        }
        Ok(())
    }

    // === Dispatch helpers ===

    async fn dispatch_terminate(&mut self) {
        if let Err(e) = self.terminate_current_session().await {
            self.show_error(e.to_string());
        }
    }

    async fn dispatch_show_workspace(&mut self) {
        self.state = AppState::WorkspacePopup;
        self.popup_state = WorkspacePopupState::new();
        self.popup_state.session_list.select(Some(self.active_idx));
        self.refresh_worktrees().await;
        if !self.worktrees.is_empty() {
            self.popup_state.worktree_list.select(Some(0));
        }
        if !self.issues.is_empty() {
            self.popup_state.issue_list.select(Some(0));
        }
        self.popup_state.loading = LoadingOperation::Fetching;
        let _ = self
            .cmd_tx
            .send(SessionCommand::RefreshWorktreesAsync)
            .await;
        let _ = self.cmd_tx.send(SessionCommand::FetchIssuesAsync).await;
    }

    fn dispatch_confirm_selection(&mut self) {
        if let Some(idx) = self.popup_state.session_list.selected() {
            self.switch_session(idx);
            self.state = AppState::Normal;
            self.popup_state.clear_input();
        }
    }

    async fn dispatch_create_with_branch(&mut self, branch: String) {
        match self.create_session_with_branch(Some(branch)).await {
            Ok(()) => {
                self.popup_state.clear_input();
                self.state = AppState::Normal;
            }
            Err(e) => self.show_error(e.to_string()),
        }
    }

    async fn dispatch_adopt_worktree(&mut self, path: &std::path::Path) {
        match self.adopt_worktree(path).await {
            Ok(()) => self.state = AppState::Normal,
            Err(e) => self.show_error(e.to_string()),
        }
    }

    async fn dispatch_delete_worktree(&mut self, path: std::path::PathBuf) {
        self.popup_state.loading = LoadingOperation::Deleting { path: path.clone() };
        let _ = self
            .cmd_tx
            .send(SessionCommand::DeleteWorktreeAsync { path })
            .await;
    }

    async fn dispatch_pull_worktree(&mut self, path: std::path::PathBuf) {
        self.popup_state.loading = LoadingOperation::Pulling { path: path.clone() };
        let _ = self
            .cmd_tx
            .send(SessionCommand::PullWorktreeAsync { path })
            .await;
    }

    async fn dispatch_select_issue(&mut self, number: u32) {
        self.state = AppState::IssueLoading {
            issue_number: number,
            phase: LoadingOperation::FetchingIssue {
                issue_number: number,
            },
        };
        let _ = self
            .cmd_tx
            .send(SessionCommand::GenerateIssueActions {
                issue_number: number,
            })
            .await;
    }

    fn dispatch_select_action(&mut self, index: usize) {
        if let AppState::ActionSelectPopup { ref choices, .. } = self.state
            && let Some(choice) = choices.get(index)
        {
            self.state = AppState::ConfirmPermissions {
                branch: choice.branch.clone(),
                prompt: choice.prompt.clone(),
                selected_yes: false,
            };
        }
    }

    fn dispatch_toggle_permissions(&mut self) {
        if let AppState::ConfirmPermissions {
            ref mut selected_yes,
            ..
        } = self.state
        {
            *selected_yes = !*selected_yes;
        }
    }

    async fn dispatch_confirm_permissions(&mut self) {
        let Some((branch, prompt)) = (match &self.state {
            AppState::ConfirmPermissions { branch, prompt, .. } => {
                Some((branch.clone(), prompt.clone()))
            }
            _ => None,
        }) else {
            return;
        };

        let auto_input = vec![
            crate::session::AutoInputStep {
                data: b"/plan".to_vec(),
                delay_ms: 0,
            },
            crate::session::AutoInputStep {
                data: b"\r".to_vec(),
                delay_ms: 50,
            },
            crate::session::AutoInputStep {
                data: prompt.into_bytes(),
                delay_ms: 500,
            },
            crate::session::AutoInputStep {
                data: b"\r".to_vec(),
                delay_ms: 50,
            },
        ];

        let (response_tx, response_rx) = tokio::sync::oneshot::channel();
        let _ = self
            .cmd_tx
            .send(SessionCommand::CreateWithAutoInput {
                cmd: "claude".to_string(),
                args: vec!["--dangerously-skip-permissions".to_string()],
                cwd: None,
                branch_name: Some(branch),
                rows: self.size.0,
                cols: self.size.1,
                auto_input,
                response_tx,
            })
            .await;

        match response_rx.await {
            Ok(Ok(_id)) => self.state = AppState::Normal,
            Ok(Err(e)) => self.show_error(e.to_string()),
            Err(_) => self.show_error("Session creation cancelled".to_string()),
        }
    }

    /// Run main event loop
    pub async fn run(
        &mut self,
        mut event_rx: mpsc::Receiver<SessionEvent>,
        terminal: &mut Terminal<CrosstermBackend<Stdout>>,
    ) -> anyhow::Result<()> {
        use crossterm::event::{poll, read};
        use std::time::Duration;

        // Initial render
        terminal.draw(|f| self.render(f))?;

        loop {
            // Handle session events (non-blocking)
            while let Ok(event) = event_rx.try_recv() {
                self.handle_session_event(event);
            }

            // Process pending auto-input
            self.process_pending_auto_input().await;

            // Poll for crossterm events
            if poll(Duration::from_millis(16))? {
                let event = read()?;
                if let Some(action) = self.handle_event(&event) {
                    self.dispatch(action).await?;
                }
            }

            // Animate throbbers at library-recommended rate (250ms)
            if self.last_throbber_tick.elapsed() >= Duration::from_millis(THROBBER_TICK_MS) {
                if self.popup_state.loading != LoadingOperation::Idle {
                    self.popup_state.throbber_state.calc_next();
                }
                if matches!(self.state, AppState::IssueLoading { .. }) {
                    self.issue_loading_throbber.calc_next();
                }
                self.last_throbber_tick = std::time::Instant::now();
            }

            // Check quit
            if self.should_quit {
                break;
            }

            // Render
            terminal.draw(|f| self.render(f))?;
        }

        Ok(())
    }
}

#[cfg(test)]
#[path = "tuiapp_tests.rs"]
mod tests;
