#![allow(dead_code)] // Phase 2: wired into render_screen dispatch

//! Dashboard screen view — entity counts, project sync status, recent activity.
//!
//! Responsive layout using [`crate::layout::classify_width`]:
//! - Wide (Lg/Xl, >=120 cols): 3-column `[Stats | Projects | Recent]`
//! - Medium (Md, 90–119): 2-column `[Stats+Projects | Recent]`
//! - Narrow (Xs/Sm, <90): single column stacked

use ftui::core::geometry::Rect;
use ftui::layout::{Breakpoint, Constraint, Flex};
use ftui::render::cell::{Cell, PackedRgba};
use ftui::render::drawing::Draw;
use ftui::render::frame::Frame;

use crate::layout::classify_width;
use crate::state::dashboard::{DashboardState, EntityCounts, LastSyncInfo, RecentActivityItem};

// ---------------------------------------------------------------------------
// Colors (Flexoki palette — will use injected Theme in a later phase)
// ---------------------------------------------------------------------------

const TEXT: PackedRgba = PackedRgba::rgb(0xCE, 0xCD, 0xC3); // tx
const TEXT_MUTED: PackedRgba = PackedRgba::rgb(0x87, 0x87, 0x80); // tx-2
const ACCENT: PackedRgba = PackedRgba::rgb(0xDA, 0x70, 0x2C); // orange
const GREEN: PackedRgba = PackedRgba::rgb(0x87, 0x9A, 0x39); // green
const YELLOW: PackedRgba = PackedRgba::rgb(0xD0, 0xA2, 0x15); // yellow
const RED: PackedRgba = PackedRgba::rgb(0xAF, 0x3A, 0x29); // red
const CYAN: PackedRgba = PackedRgba::rgb(0x3A, 0xA9, 0x9F); // cyan
const BORDER: PackedRgba = PackedRgba::rgb(0x87, 0x87, 0x80); // tx-2

// ---------------------------------------------------------------------------
// Public entry point
// ---------------------------------------------------------------------------

/// Render the full dashboard screen into `area`.
pub fn render_dashboard(frame: &mut Frame<'_>, state: &DashboardState, area: Rect) {
    if area.height < 2 || area.width < 10 {
        return; // Too small to render.
    }

    let bp = classify_width(area.width);

    match bp {
        Breakpoint::Lg | Breakpoint::Xl => render_wide(frame, state, area),
        Breakpoint::Md => render_medium(frame, state, area),
        Breakpoint::Xs | Breakpoint::Sm => render_narrow(frame, state, area),
    }
}

// ---------------------------------------------------------------------------
// Layout variants
// ---------------------------------------------------------------------------

/// Wide: 3-column [Stats | Projects | Recent Activity].
fn render_wide(frame: &mut Frame<'_>, state: &DashboardState, area: Rect) {
    let cols = Flex::horizontal()
        .constraints([
            Constraint::Ratio(1, 3),
            Constraint::Ratio(1, 3),
            Constraint::Ratio(1, 3),
        ])
        .split(area);

    render_stat_panel(frame, &state.counts, cols[0]);
    render_project_list(frame, state, cols[1]);
    render_recent_activity(frame, state, cols[2]);
}

/// Medium: 2-column [Stats+Projects stacked | Recent Activity].
fn render_medium(frame: &mut Frame<'_>, state: &DashboardState, area: Rect) {
    let cols = Flex::horizontal()
        .constraints([Constraint::Ratio(2, 5), Constraint::Ratio(3, 5)])
        .split(area);

    // Left column: stats on top, projects below.
    let left_rows = Flex::vertical()
        .constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)])
        .split(cols[0]);

    render_stat_panel(frame, &state.counts, left_rows[0]);
    render_project_list(frame, state, left_rows[1]);

    render_recent_activity(frame, state, cols[1]);
}

/// Narrow: single column stacked.
fn render_narrow(frame: &mut Frame<'_>, state: &DashboardState, area: Rect) {
    let rows = Flex::vertical()
        .constraints([
            Constraint::Fixed(8), // stats
            Constraint::Fixed(4), // projects (compact)
            Constraint::Fill,     // recent
        ])
        .split(area);

    render_stat_panel(frame, &state.counts, rows[0]);
    render_project_list(frame, state, rows[1]);
    render_recent_activity(frame, state, rows[2]);
}

// ---------------------------------------------------------------------------
// Panels
// ---------------------------------------------------------------------------

/// Entity counts panel.
fn render_stat_panel(frame: &mut Frame<'_>, counts: &EntityCounts, area: Rect) {
    if area.height == 0 || area.width < 5 {
        return;
    }

    let title_cell = Cell {
        fg: ACCENT,
        ..Cell::default()
    };
    let label_cell = Cell {
        fg: TEXT_MUTED,
        ..Cell::default()
    };
    let value_cell = Cell {
        fg: TEXT,
        ..Cell::default()
    };

    let max_x = area.x.saturating_add(area.width);
    let mut y = area.y;
    let x = area.x.saturating_add(1); // 1-char left padding

    // Title
    frame.print_text_clipped(x, y, "Entity Counts", title_cell, max_x);
    y = y.saturating_add(1);

    // Separator
    render_horizontal_rule(frame, area.x, y, area.width, BORDER);
    y = y.saturating_add(1);

    // Stats rows
    let stats: &[(&str, String)] = &[
        (
            "Issues",
            format!("{} open / {}", counts.issues_open, counts.issues_total),
        ),
        (
            "MRs",
            format!("{} open / {}", counts.mrs_open, counts.mrs_total),
        ),
        ("Discussions", counts.discussions.to_string()),
        (
            "Notes",
            format!(
                "{} ({}% system)",
                counts.notes_total, counts.notes_system_pct
            ),
        ),
        ("Documents", counts.documents.to_string()),
        ("Embeddings", counts.embeddings.to_string()),
    ];

    for (label, value) in stats {
        if y >= area.y.saturating_add(area.height) {
            break;
        }
        let after_label = frame.print_text_clipped(x, y, label, label_cell, max_x);
        let after_colon = frame.print_text_clipped(after_label, y, ": ", label_cell, max_x);
        frame.print_text_clipped(after_colon, y, value, value_cell, max_x);
        y = y.saturating_add(1);
    }
}

/// Per-project sync freshness list.
fn render_project_list(frame: &mut Frame<'_>, state: &DashboardState, area: Rect) {
    if area.height == 0 || area.width < 5 {
        return;
    }

    let title_cell = Cell {
        fg: ACCENT,
        ..Cell::default()
    };
    let label_cell = Cell {
        fg: TEXT,
        ..Cell::default()
    };

    let max_x = area.x.saturating_add(area.width);
    let mut y = area.y;
    let x = area.x.saturating_add(1);

    frame.print_text_clipped(x, y, "Projects", title_cell, max_x);
    y = y.saturating_add(1);
    render_horizontal_rule(frame, area.x, y, area.width, BORDER);
    y = y.saturating_add(1);

    if state.projects.is_empty() {
        let muted = Cell {
            fg: TEXT_MUTED,
            ..Cell::default()
        };
        frame.print_text_clipped(x, y, "No projects synced", muted, max_x);
        return;
    }

    for proj in &state.projects {
        if y >= area.y.saturating_add(area.height) {
            break;
        }

        let freshness_color = staleness_color(proj.minutes_since_sync);
        let freshness_cell = Cell {
            fg: freshness_color,
            ..Cell::default()
        };

        let indicator = staleness_indicator(proj.minutes_since_sync);
        let after_dot = frame.print_text_clipped(x, y, &indicator, freshness_cell, max_x);
        let after_space = frame.print_text_clipped(after_dot, y, " ", label_cell, max_x);
        frame.print_text_clipped(after_space, y, &proj.path, label_cell, max_x);
        y = y.saturating_add(1);
    }

    // Last sync summary if available.
    if let Some(ref sync) = state.last_sync
        && y < area.y.saturating_add(area.height)
    {
        y = y.saturating_add(1); // blank line
        render_sync_summary(frame, sync, x, y, max_x);
    }
}

/// Scrollable recent activity list.
fn render_recent_activity(frame: &mut Frame<'_>, state: &DashboardState, area: Rect) {
    if area.height == 0 || area.width < 5 {
        return;
    }

    let title_cell = Cell {
        fg: ACCENT,
        ..Cell::default()
    };

    let max_x = area.x.saturating_add(area.width);
    let mut y = area.y;
    let x = area.x.saturating_add(1);

    frame.print_text_clipped(x, y, "Recent Activity", title_cell, max_x);
    y = y.saturating_add(1);
    render_horizontal_rule(frame, area.x, y, area.width, BORDER);
    y = y.saturating_add(1);

    if state.recent.is_empty() {
        let muted = Cell {
            fg: TEXT_MUTED,
            ..Cell::default()
        };
        frame.print_text_clipped(x, y, "No recent activity", muted, max_x);
        return;
    }

    let visible_rows = (area.y.saturating_add(area.height)).saturating_sub(y) as usize;
    let items = &state.recent;
    let start = state.scroll_offset.min(items.len().saturating_sub(1));
    let end = (start + visible_rows).min(items.len());

    for item in &items[start..end] {
        if y >= area.y.saturating_add(area.height) {
            break;
        }
        render_activity_row(frame, item, x, y, max_x);
        y = y.saturating_add(1);
    }

    // Scroll indicator if there's more content.
    if end < items.len() && y < area.y.saturating_add(area.height) {
        let muted = Cell {
            fg: TEXT_MUTED,
            ..Cell::default()
        };
        let remaining = items.len() - end;
        frame.print_text_clipped(x, y, &format!("... {remaining} more"), muted, max_x);
    }
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/// Render a single recent activity row.
fn render_activity_row(
    frame: &mut Frame<'_>,
    item: &RecentActivityItem,
    x: u16,
    y: u16,
    max_x: u16,
) {
    let type_color = if item.entity_type == "issue" {
        CYAN
    } else {
        ACCENT
    };
    let type_cell = Cell {
        fg: type_color,
        ..Cell::default()
    };
    let text_cell = Cell {
        fg: TEXT,
        ..Cell::default()
    };
    let muted_cell = Cell {
        fg: TEXT_MUTED,
        ..Cell::default()
    };

    let type_label = if item.entity_type == "issue" {
        format!("#{}", item.iid)
    } else {
        format!("!{}", item.iid)
    };

    let after_type = frame.print_text_clipped(x, y, &type_label, type_cell, max_x);
    let after_space = frame.print_text_clipped(after_type, y, " ", text_cell, max_x);

    // Truncate title to leave room for time.
    let time_str = format_relative_time(item.minutes_ago);
    let time_width = time_str.len() as u16 + 2; // " " + time
    let title_max = max_x.saturating_sub(time_width);

    let after_title = frame.print_text_clipped(after_space, y, &item.title, text_cell, title_max);

    // Right-align time string.
    let time_x = max_x.saturating_sub(time_str.len() as u16 + 1);
    if time_x > after_title {
        frame.print_text_clipped(time_x, y, &time_str, muted_cell, max_x);
    }
}

/// Render a last-sync summary line.
fn render_sync_summary(frame: &mut Frame<'_>, sync: &LastSyncInfo, x: u16, y: u16, max_x: u16) {
    let status_color = if sync.status == "succeeded" {
        GREEN
    } else {
        RED
    };
    let cell = Cell {
        fg: status_color,
        ..Cell::default()
    };
    let muted = Cell {
        fg: TEXT_MUTED,
        ..Cell::default()
    };

    let label_end = frame.print_text_clipped(x, y, "Last sync: ", muted, max_x);
    let status_end = frame.print_text_clipped(label_end, y, &sync.status, cell, max_x);

    if let Some(ref err) = sync.error {
        let err_cell = Cell {
            fg: RED,
            ..Cell::default()
        };
        let after_space = frame.print_text_clipped(status_end, y, " — ", muted, max_x);
        frame.print_text_clipped(after_space, y, err, err_cell, max_x);
    }
}

/// Draw a horizontal rule across a row.
fn render_horizontal_rule(frame: &mut Frame<'_>, x: u16, y: u16, width: u16, color: PackedRgba) {
    let cell = Cell {
        fg: color,
        ..Cell::default()
    };
    let rule = "─".repeat(width as usize);
    frame.print_text_clipped(x, y, &rule, cell, x.saturating_add(width));
}

/// Staleness color: green <60min, yellow <360min, red >360min.
const fn staleness_color(minutes: u64) -> PackedRgba {
    if minutes == u64::MAX {
        RED // Never synced.
    } else if minutes < 60 {
        GREEN
    } else if minutes < 360 {
        YELLOW
    } else {
        RED
    }
}

/// Staleness dot indicator.
fn staleness_indicator(minutes: u64) -> String {
    if minutes == u64::MAX {
        "● never".to_string()
    } else if minutes < 60 {
        format!("● {minutes}m ago")
    } else if minutes < 1440 {
        format!("● {}h ago", minutes / 60)
    } else {
        format!("● {}d ago", minutes / 1440)
    }
}

/// Format relative time for activity feed.
fn format_relative_time(minutes: u64) -> String {
    if minutes == 0 {
        "just now".to_string()
    } else if minutes < 60 {
        format!("{minutes}m ago")
    } else if minutes < 1440 {
        format!("{}h ago", minutes / 60)
    } else {
        format!("{}d ago", minutes / 1440)
    }
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use crate::state::dashboard::{DashboardData, EntityCounts, ProjectSyncInfo};
    use ftui::render::grapheme_pool::GraphemePool;

    macro_rules! with_frame {
        ($width:expr, $height:expr, |$frame:ident| $body:block) => {{
            let mut pool = GraphemePool::new();
            let mut $frame = Frame::new($width, $height, &mut pool);
            $body
        }};
    }

    fn sample_state() -> DashboardState {
        let mut state = DashboardState::default();
        state.update(DashboardData {
            counts: EntityCounts {
                issues_open: 42,
                issues_total: 100,
                mrs_open: 10,
                mrs_total: 50,
                discussions: 200,
                notes_total: 500,
                notes_system_pct: 30,
                documents: 80,
                embeddings: 75,
            },
            projects: vec![
                ProjectSyncInfo {
                    path: "group/alpha".into(),
                    minutes_since_sync: 15,
                },
                ProjectSyncInfo {
                    path: "group/beta".into(),
                    minutes_since_sync: 120,
                },
            ],
            recent: vec![RecentActivityItem {
                entity_type: "issue".into(),
                iid: 42,
                title: "Fix authentication bug".into(),
                state: "opened".into(),
                minutes_ago: 5,
            }],
            last_sync: None,
        });
        state
    }

    #[test]
    fn test_render_dashboard_wide_no_panic() {
        with_frame!(140, 30, |frame| {
            let state = sample_state();
            let area = Rect::new(0, 0, 140, 30);
            render_dashboard(&mut frame, &state, area);
        });
    }

    #[test]
    fn test_render_dashboard_medium_no_panic() {
        with_frame!(100, 24, |frame| {
            let state = sample_state();
            let area = Rect::new(0, 0, 100, 24);
            render_dashboard(&mut frame, &state, area);
        });
    }

    #[test]
    fn test_render_dashboard_narrow_no_panic() {
        with_frame!(60, 20, |frame| {
            let state = sample_state();
            let area = Rect::new(0, 0, 60, 20);
            render_dashboard(&mut frame, &state, area);
        });
    }

    #[test]
    fn test_render_dashboard_tiny_noop() {
        with_frame!(5, 1, |frame| {
            let state = DashboardState::default();
            let area = Rect::new(0, 0, 5, 1);
            render_dashboard(&mut frame, &state, area);
        });
    }

    #[test]
    fn test_render_dashboard_empty_state_no_panic() {
        with_frame!(120, 24, |frame| {
            let state = DashboardState::default();
            let area = Rect::new(0, 0, 120, 24);
            render_dashboard(&mut frame, &state, area);
        });
    }

    #[test]
    fn test_staleness_color_thresholds() {
        assert_eq!(staleness_color(0), GREEN);
        assert_eq!(staleness_color(59), GREEN);
        assert_eq!(staleness_color(60), YELLOW);
        assert_eq!(staleness_color(359), YELLOW);
        assert_eq!(staleness_color(360), RED);
        assert_eq!(staleness_color(u64::MAX), RED);
    }

    #[test]
    fn test_staleness_indicator() {
        assert_eq!(staleness_indicator(15), "● 15m ago");
        assert_eq!(staleness_indicator(120), "● 2h ago");
        assert_eq!(staleness_indicator(2880), "● 2d ago");
        assert_eq!(staleness_indicator(u64::MAX), "● never");
    }

    #[test]
    fn test_format_relative_time() {
        assert_eq!(format_relative_time(0), "just now");
        assert_eq!(format_relative_time(5), "5m ago");
        assert_eq!(format_relative_time(90), "1h ago");
        assert_eq!(format_relative_time(1500), "1d ago");
    }

    #[test]
    fn test_stat_panel_renders_title() {
        with_frame!(40, 10, |frame| {
            let counts = EntityCounts {
                issues_open: 3,
                issues_total: 10,
                ..Default::default()
            };
            render_stat_panel(&mut frame, &counts, Rect::new(0, 0, 40, 10));

            // Check that 'E' from "Entity Counts" is rendered at x=1, y=0.
            let cell = frame.buffer.get(1, 0).unwrap();
            assert_eq!(cell.content.as_char(), Some('E'), "Expected 'E' at (1,0)");
        });
    }
}
