//! mi6 - A top-like CLI for monitoring agentic coding sessions.

#![allow(clippy::print_stdout, clippy::print_stderr)]

mod format;
mod ui;

use std::io;
use std::sync::mpsc;
use std::thread;
use std::time::{Duration, Instant};

use anyhow::Result;
use crossterm::{
    event::{
        self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind,
        MouseEventKind,
    },
    execute,
    terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::{Terminal, backend::CrosstermBackend, layout::Rect, widgets::TableState};

use mi6_core::{GlobalStats, SessionInfo, SessionMonitor};

/// Available refresh intervals in milliseconds.
const REFRESH_INTERVALS_MS: &[u64] = &[100, 200, 300, 400, 500, 1000, 2000, 3000, 4000, 5000, 10000];

/// Default refresh interval index (300ms).
const DEFAULT_REFRESH_IDX: usize = 2;

/// Data received from background refresh thread.
struct RefreshData {
    sessions: Vec<SessionInfo>,
    global_stats: GlobalStats,
    error: Option<String>,
}

/// Application state.
pub struct App {
    pub sessions: Vec<SessionInfo>,
    pub global_stats: GlobalStats,
    pub table_state: TableState,
    pub error_message: Option<String>,
    refresh_interval_idx: usize,
    pub show_dead: bool,
    data_rx: mpsc::Receiver<RefreshData>,
    interval_tx: mpsc::Sender<Duration>,
    /// Area where the sessions table is rendered (for mouse click handling).
    pub(crate) table_area: Rect,
}

impl App {
    fn new() -> Self {
        let (data_tx, data_rx) = mpsc::channel();
        let (interval_tx, interval_rx) = mpsc::channel();

        // Spawn background refresh thread - does ALL slow work
        let initial_interval = Duration::from_millis(REFRESH_INTERVALS_MS[DEFAULT_REFRESH_IDX]);
        thread::spawn(move || {
            refresh_thread(data_tx, interval_rx, initial_interval);
        });

        // Return immediately with empty state - UI shows instantly
        Self {
            sessions: Vec::new(),
            global_stats: GlobalStats::default(),
            table_state: TableState::default(),
            error_message: None,
            refresh_interval_idx: DEFAULT_REFRESH_IDX,
            show_dead: false,
            data_rx,
            interval_tx,
            table_area: Rect::default(),
        }
    }

    /// Check for new data from background thread (non-blocking).
    /// Returns true if new data was received.
    fn poll_data(&mut self) -> bool {
        let mut got_data = false;
        while let Ok(data) = self.data_rx.try_recv() {
            self.sessions = data.sessions;
            self.global_stats = data.global_stats;
            self.error_message = data.error;
            got_data = true;
        }
        got_data
    }

    pub fn visible_sessions(&self) -> Vec<&SessionInfo> {
        if self.show_dead {
            self.sessions.iter().collect()
        } else {
            // Show sessions that are either:
            // 1. Confirmed alive (have PID and PID is running), or
            // 2. Have no PID but had recent activity (within last 5 minutes)
            //    These are likely active but PID wasn't captured by fyi hooks
            let five_min_ago = chrono::Utc::now() - chrono::Duration::minutes(5);
            self.sessions
                .iter()
                .filter(|s| s.is_alive || (s.pid.is_none() && s.last_activity > five_min_ago))
                .collect()
        }
    }

    fn toggle_show_dead(&mut self) {
        self.show_dead = !self.show_dead;
        // Reset selection when toggling
        self.table_state.select(None);
    }

    fn refresh_interval(&self) -> Duration {
        Duration::from_millis(REFRESH_INTERVALS_MS[self.refresh_interval_idx])
    }

    pub fn refresh_interval_display(&self) -> String {
        let ms = REFRESH_INTERVALS_MS[self.refresh_interval_idx];
        if ms >= 1000 {
            format!("{}s", ms / 1000)
        } else {
            format!("{ms}ms")
        }
    }

    fn increase_refresh_interval(&mut self) {
        if self.refresh_interval_idx < REFRESH_INTERVALS_MS.len() - 1 {
            self.refresh_interval_idx += 1;
            let _ = self.interval_tx.send(self.refresh_interval());
        }
    }

    fn decrease_refresh_interval(&mut self) {
        if self.refresh_interval_idx > 0 {
            self.refresh_interval_idx -= 1;
            let _ = self.interval_tx.send(self.refresh_interval());
        }
    }

    fn next_row(&mut self) {
        let count = self.visible_sessions().len();
        if count == 0 {
            return;
        }
        let i = match self.table_state.selected() {
            Some(i) => (i + 1).min(count - 1),
            None => 0,
        };
        self.table_state.select(Some(i));
    }

    fn previous_row(&mut self) {
        let count = self.visible_sessions().len();
        if count == 0 {
            return;
        }
        let i = match self.table_state.selected() {
            Some(i) => i.saturating_sub(1),
            None => 0,
        };
        self.table_state.select(Some(i));
    }

    /// Handle a mouse click at the given position, selecting the row if applicable.
    fn handle_click(&mut self, x: u16, y: u16) {
        // Check if click is within the table area
        if x < self.table_area.x
            || x >= self.table_area.x + self.table_area.width
            || y < self.table_area.y
            || y >= self.table_area.y + self.table_area.height
        {
            return;
        }

        // Calculate row index: subtract top border (1) and header row (1)
        let y_in_table = y.saturating_sub(self.table_area.y);
        if y_in_table < 2 {
            // Click was on border or header
            return;
        }

        // Account for scroll offset (first visible row index)
        let visual_row = (y_in_table - 2) as usize;
        let row_index = self.table_state.offset() + visual_row;
        let count = self.visible_sessions().len();
        if row_index < count {
            self.table_state.select(Some(row_index));
        }
    }
}

/// Background thread that periodically refreshes session data.
fn refresh_thread(
    data_tx: mpsc::Sender<RefreshData>,
    interval_rx: mpsc::Receiver<Duration>,
    initial_interval: Duration,
) {
    // Phase 1: Fast initial load - just DB, no system info
    let mut monitor = match SessionMonitor::new_fast() {
        Ok(m) => m,
        Err(e) => {
            let _ = data_tx.send(RefreshData {
                sessions: Vec::new(),
                global_stats: GlobalStats::default(),
                error: Some(format!("Failed to open fyi database: {e}")),
            });
            return;
        }
    };

    // Send initial data immediately - fast mode skips git/per-session queries
    if let Ok(sessions) = monitor.get_sessions_fast() {
        let global_stats = monitor.get_global_stats().unwrap_or_default();
        let _ = data_tx.send(RefreshData {
            sessions,
            global_stats,
            error: None,
        });
    }

    // Phase 2: Now do the slow system refresh
    monitor.refresh();

    let mut interval = initial_interval;

    loop {
        let (sessions, error) = match monitor.get_sessions() {
            Ok(s) => (s, None),
            Err(e) => (Vec::new(), Some(format!("Error: {e}"))),
        };

        let global_stats = monitor.get_global_stats().unwrap_or_default();

        // Send data to main thread
        if data_tx
            .send(RefreshData {
                sessions,
                global_stats,
                error,
            })
            .is_err()
        {
            return;
        }

        // Sleep for the interval, but check for interval updates
        let sleep_start = Instant::now();
        while sleep_start.elapsed() < interval {
            if let Ok(new_interval) = interval_rx.try_recv() {
                interval = new_interval;
            }
            thread::sleep(Duration::from_millis(50));
        }

        // Refresh for next iteration
        monitor.refresh();
    }
}

fn main() -> Result<()> {
    // Setup terminal
    enable_raw_mode()?;
    let mut stdout = io::stdout();
    execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
    let backend = CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend)?;

    // Create app and run
    let app = App::new();
    let result = run_app(&mut terminal, app);

    // Restore terminal
    disable_raw_mode()?;
    execute!(
        terminal.backend_mut(),
        LeaveAlternateScreen,
        DisableMouseCapture
    )?;
    terminal.show_cursor()?;

    if let Err(e) = result {
        eprintln!("Error: {e}");
    }

    Ok(())
}

fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, mut app: App) -> Result<()> {
    let mut needs_redraw = true;

    loop {
        // Check for new data from background thread (non-blocking)
        if app.poll_data() {
            needs_redraw = true;
        }

        // Poll for input with short timeout
        if event::poll(Duration::from_millis(16))? {
            match event::read()? {
                Event::Key(key) => {
                    if key.kind == KeyEventKind::Press {
                        needs_redraw = true;
                        match key.code {
                            KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
                            KeyCode::Down | KeyCode::Char('j') => app.next_row(),
                            KeyCode::Up | KeyCode::Char('k') => app.previous_row(),
                            KeyCode::Char('a') => app.toggle_show_dead(),
                            KeyCode::Char('+') | KeyCode::Char('=') => app.increase_refresh_interval(),
                            KeyCode::Char('-') => app.decrease_refresh_interval(),
                            _ => {}
                        }
                    }
                }
                Event::Mouse(mouse) => {
                    match mouse.kind {
                        MouseEventKind::Down(event::MouseButton::Left) => {
                            app.handle_click(mouse.column, mouse.row);
                            needs_redraw = true;
                        }
                        MouseEventKind::ScrollDown => {
                            app.next_row();
                            needs_redraw = true;
                        }
                        MouseEventKind::ScrollUp => {
                            app.previous_row();
                            needs_redraw = true;
                        }
                        _ => {}
                    }
                }
                Event::Resize(_, _) => {
                    needs_redraw = true;
                }
                _ => {}
            }
        }

        // Only redraw when something changed
        if needs_redraw {
            terminal.draw(|f| ui::render(f, &mut app))?;
            needs_redraw = false;
        }
    }
}
