//! Terminal UI rendering functions.

use ratatui::{
    Frame,
    layout::{Alignment, Constraint, Direction, Layout, Rect},
    style::{Color, Modifier, Style},
    text::{Line, Span},
    widgets::{Block, Borders, Cell, Paragraph, Row, Table},
};

use mi6_core::{GlobalStats, SessionInfo};

use crate::format::{cpu_color, format_bytes, format_duration, format_number, truncate_with_ellipsis};
use crate::App;

/// Spacing between table columns.
const COLUMN_SPACING: u16 = 2;

/// Column definitions: (name, minimum width).
const COLUMNS: &[(&str, u16)] = &[
    ("PID", 6),
    ("Age", 3),
    ("Model", 6),
    ("Prompt", 6),
    ("CPU", 5),
    ("Mem", 6),
    ("Tokens", 6),
    ("Branch", 6),
    ("TTY", 3),
    ("Session", 36),
    ("CWD", 3),
];

/// Render the full UI layout.
pub fn render(f: &mut Frame<'_>, app: &mut App) {
    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(4), // Global stats
            Constraint::Min(10),   // Sessions table
            Constraint::Length(1), // Help line
        ])
        .split(f.area());

    render_global_stats(f, chunks[0], &app.global_stats, &app.refresh_interval_display());
    // Store the table area for mouse click handling
    app.table_area = chunks[1];
    render_sessions_table(f, chunks[1], app);
    render_help_line(f, chunks[2], app.error_message.as_deref());
}

fn render_global_stats(f: &mut Frame<'_>, area: Rect, stats: &GlobalStats, refresh_display: &str) {
    let block = Block::default()
        .title(" Global Stats ")
        .borders(Borders::ALL)
        .border_style(Style::default().fg(Color::Cyan));

    let inner = block.inner(area);
    f.render_widget(block, area);

    let stats_text = vec![
        Line::from(vec![
            Span::styled("Sessions: ", Style::default().fg(Color::Gray)),
            Span::styled(
                format!("{}", stats.active_sessions),
                Style::default().fg(Color::Green).add_modifier(Modifier::BOLD),
            ),
            Span::raw("    "),
            Span::styled("CPU: ", Style::default().fg(Color::Gray)),
            Span::styled(
                format!("{:.1}%", stats.total_cpu_percent),
                Style::default().fg(cpu_color(stats.total_cpu_percent)),
            ),
            Span::raw("    "),
            Span::styled("Memory: ", Style::default().fg(Color::Gray)),
            Span::styled(
                format_bytes(stats.total_memory_bytes),
                Style::default().fg(Color::Yellow),
            ),
        ]),
        Line::from(vec![
            Span::styled("Tokens: ", Style::default().fg(Color::Gray)),
            Span::styled(
                format_number(stats.total_tokens),
                Style::default().fg(Color::Magenta),
            ),
            Span::raw("    "),
            Span::styled("Cost: ", Style::default().fg(Color::Gray)),
            Span::styled(
                format!("${:.2}", stats.total_cost_usd),
                Style::default().fg(Color::Green),
            ),
        ]),
    ];

    let paragraph = Paragraph::new(stats_text);
    f.render_widget(paragraph, inner);

    // Render refresh interval in upper right corner
    let refresh_text = format!(" {refresh_display} ");
    let refresh_width = refresh_text.len() as u16;
    if area.width > refresh_width + 2 {
        let refresh_area = Rect {
            x: area.x + area.width - refresh_width - 2,
            y: area.y,
            width: refresh_width,
            height: 1,
        };
        let refresh_widget = Paragraph::new(refresh_text)
            .style(Style::default().fg(Color::Cyan))
            .alignment(Alignment::Right);
        f.render_widget(refresh_widget, refresh_area);
    }
}

fn render_sessions_table(f: &mut Frame<'_>, area: Rect, app: &mut App) {
    let visible = app.visible_sessions();

    // Build all row data first to calculate column widths
    let row_data: Vec<Vec<(String, Option<Color>)>> = visible
        .iter()
        .map(|session| build_row_data(session))
        .collect();

    // Calculate actual column widths from data (min_width as floor, expand for content)
    // Use char count, not byte length, for proper Unicode handling
    let mut col_widths: Vec<usize> = COLUMNS
        .iter()
        .map(|(name, min)| (*min as usize).max(name.len()))
        .collect();
    for row in &row_data {
        for (i, (text, _)) in row.iter().enumerate() {
            if i < col_widths.len() {
                col_widths[i] = col_widths[i].max(text.chars().count());
            }
        }
    }

    // Build header
    let header_cells = COLUMNS.iter().map(|(name, _)| {
        Cell::from(*name).style(Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD))
    });
    let header = Row::new(header_cells).height(1);

    // Build rows - truncate content to fit column widths
    let rows = row_data.into_iter().map(|cells| {
        let row_cells: Vec<Cell<'_>> = cells
            .into_iter()
            .enumerate()
            .map(|(i, (text, color))| {
                let width = col_widths.get(i).copied().unwrap_or(text.chars().count());
                let display_text = truncate_with_ellipsis(&text, width);
                let cell = Cell::from(display_text);
                if let Some(c) = color {
                    cell.style(Style::default().fg(c))
                } else {
                    cell
                }
            })
            .collect();
        Row::new(row_cells)
    });

    // Calculate total table width needed
    let total_width: u16 = col_widths.iter().map(|&w| w as u16).sum::<u16>()
        + (COLUMN_SPACING * (col_widths.len().saturating_sub(1) as u16))
        + 2; // borders

    // Create a fixed-width area that won't shrink below minimum
    let table_area = Rect {
        x: area.x,
        y: area.y,
        width: area.width.max(total_width),
        height: area.height,
    };

    // Use Constraint::Length for fixed widths
    let widths: Vec<Constraint> = col_widths
        .iter()
        .map(|&w| Constraint::Length(w as u16))
        .collect();

    let title = if app.show_dead {
        format!(" Sessions ({}/{}) [all] ", visible.len(), app.sessions.len())
    } else {
        format!(" Sessions ({}) ", visible.len())
    };

    let table = Table::new(rows, widths)
        .header(header)
        .column_spacing(COLUMN_SPACING)
        .block(
            Block::default()
                .title(title)
                .borders(Borders::ALL)
                .border_style(Style::default().fg(Color::Cyan)),
        )
        .row_highlight_style(
            Style::default()
                .bg(Color::DarkGray)
                .add_modifier(Modifier::BOLD),
        );

    f.render_stateful_widget(table, table_area, &mut app.table_state);
}

/// Build row data for a single session.
fn build_row_data(session: &SessionInfo) -> Vec<(String, Option<Color>)> {
    // Determine status: alive (has PID, running), unknown (no PID but recent), or dead
    let (pid_text, pid_color, cpu_col) = if session.is_alive {
        // Confirmed alive - has PID and process is running
        (
            session.pid.map_or_else(|| "-".to_string(), |p| p.to_string()),
            None,
            cpu_color(session.cpu_percent),
        )
    } else if session.pid.is_none() {
        // No PID recorded - show as unknown with cyan color
        ("?".to_string(), Some(Color::Cyan), Color::Cyan)
    } else {
        // Had PID but process is dead - show red
        (
            session.pid.map_or_else(|| "-".to_string(), |p| p.to_string()),
            Some(Color::Red),
            Color::Red,
        )
    };

    vec![
        (pid_text, pid_color),
        (format_duration(session.started_at), None),
        (session.model.as_deref().unwrap_or("-").to_string(), None),
        (truncate_with_ellipsis(session.prompt_preview.as_deref().unwrap_or("-"), 14), None),
        (format!("{:.1}%", session.cpu_percent), Some(cpu_col)),
        (format_bytes(session.memory_bytes), None),
        (format_number(session.token_stats.input + session.token_stats.output), None),
        (session.branch.as_deref().unwrap_or("-").to_string(), None),
        (session.tty.as_deref().unwrap_or("-").to_string(), None),
        (session.id.clone(), None),
        (session.cwd.as_deref().unwrap_or("-").to_string(), None),
    ]
}

fn render_help_line(f: &mut Frame<'_>, area: Rect, error: Option<&str>) {
    let text = if let Some(err) = error {
        Line::from(vec![
            Span::styled("Error: ", Style::default().fg(Color::Red)),
            Span::styled(err, Style::default().fg(Color::Red)),
        ])
    } else {
        Line::from(vec![
            Span::styled("q", Style::default().fg(Color::Yellow)),
            Span::raw(" quit  "),
            Span::styled("j/k", Style::default().fg(Color::Yellow)),
            Span::raw(" up/down  "),
            Span::styled("h/l", Style::default().fg(Color::Yellow)),
            Span::raw(" scroll  "),
            Span::styled("a", Style::default().fg(Color::Yellow)),
            Span::raw(" all  "),
            Span::styled("+/-", Style::default().fg(Color::Yellow)),
            Span::raw(" interval  "),
            Span::styled("?", Style::default().fg(Color::Cyan)),
            Span::raw("=no PID"),
        ])
    };

    let paragraph = Paragraph::new(text);
    f.render_widget(paragraph, area);
}
