//! Sync screen view — progress bars, summary table, and log.
//!
//! Renders the sync screen in different phases:
//! - **Idle**: prompt to start sync
//! - **Running**: per-lane progress bars with throughput stats
//! - **Complete**: summary table with change counts
//! - **Cancelled/Failed**: status message with retry hint

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

use crate::state::sync::{SyncLane, SyncPhase, SyncState};

use super::{ACCENT, TEXT, TEXT_MUTED};

/// Progress bar fill color.
const PROGRESS_FG: PackedRgba = PackedRgba::rgb(0xDA, 0x70, 0x2C); // orange
/// Progress bar background.
const PROGRESS_BG: PackedRgba = PackedRgba::rgb(0x34, 0x34, 0x30);
/// Success green.
const SUCCESS_FG: PackedRgba = PackedRgba::rgb(0x87, 0x9A, 0x39);
/// Error red.
const ERROR_FG: PackedRgba = PackedRgba::rgb(0xD1, 0x4D, 0x41);

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

/// Render the sync screen.
pub fn render_sync(frame: &mut Frame<'_>, state: &SyncState, area: Rect) {
    if area.width < 10 || area.height < 3 {
        return;
    }

    match &state.phase {
        SyncPhase::Idle => render_idle(frame, area),
        SyncPhase::Running => render_running(frame, state, area),
        SyncPhase::Complete => render_summary(frame, state, area),
        SyncPhase::Cancelled => render_cancelled(frame, area),
        SyncPhase::Failed(err) => render_failed(frame, area, err),
    }
}

// ---------------------------------------------------------------------------
// Idle view
// ---------------------------------------------------------------------------

fn render_idle(frame: &mut Frame<'_>, area: Rect) {
    let max_x = area.right();
    let center_y = area.y + area.height / 2;

    let title = "Sync";
    let title_x = area.x + area.width.saturating_sub(title.len() as u16) / 2;
    frame.print_text_clipped(
        title_x,
        center_y.saturating_sub(1),
        title,
        Cell {
            fg: ACCENT,
            ..Cell::default()
        },
        max_x,
    );

    let hint = "Press Enter to start sync, or run `lore sync` externally.";
    let hint_x = area.x + area.width.saturating_sub(hint.len() as u16) / 2;
    frame.print_text_clipped(
        hint_x,
        center_y + 1,
        hint,
        Cell {
            fg: TEXT_MUTED,
            ..Cell::default()
        },
        max_x,
    );
}

// ---------------------------------------------------------------------------
// Running view — per-lane progress bars
// ---------------------------------------------------------------------------

fn render_running(frame: &mut Frame<'_>, state: &SyncState, area: Rect) {
    let max_x = area.right();

    // Title.
    let title = "Syncing...";
    let title_x = area.x + 2;
    frame.print_text_clipped(
        title_x,
        area.y + 1,
        title,
        Cell {
            fg: ACCENT,
            ..Cell::default()
        },
        max_x,
    );

    // Stage label.
    if !state.stage.is_empty() {
        let stage_cell = Cell {
            fg: TEXT_MUTED,
            ..Cell::default()
        };
        frame.print_text_clipped(title_x, area.y + 2, &state.stage, stage_cell, max_x);
    }

    // Per-lane progress bars.
    let bar_start_y = area.y + 4;
    let label_width = 14u16; // "Discussions  " is the longest
    let bar_x = area.x + 2 + label_width;
    let bar_width = area.width.saturating_sub(4 + label_width + 12); // 12 for count text

    for (i, lane) in SyncLane::ALL.iter().enumerate() {
        let y = bar_start_y + i as u16;
        if y >= area.bottom().saturating_sub(3) {
            break;
        }

        let lane_progress = &state.lanes[i];

        // Lane label.
        let label = format!("{:<12}", lane.label());
        frame.print_text_clipped(
            area.x + 2,
            y,
            &label,
            Cell {
                fg: TEXT,
                ..Cell::default()
            },
            bar_x,
        );

        // Progress bar.
        if bar_width > 2 {
            render_progress_bar(frame, bar_x, y, bar_width, lane_progress.fraction());
        }

        // Count text (e.g., "50/100").
        let count_x = bar_x + bar_width + 1;
        let count_text = if lane_progress.total > 0 {
            format!("{}/{}", lane_progress.current, lane_progress.total)
        } else if lane_progress.current > 0 {
            format!("{}", lane_progress.current)
        } else {
            "--".to_string()
        };
        frame.print_text_clipped(
            count_x,
            y,
            &count_text,
            Cell {
                fg: TEXT_MUTED,
                ..Cell::default()
            },
            max_x,
        );
    }

    // Throughput stats.
    let stats_y = bar_start_y + SyncLane::ALL.len() as u16 + 1;
    if stats_y < area.bottom().saturating_sub(2) && state.items_synced > 0 {
        let stats = format!(
            "{} items synced ({:.0} items/sec)",
            state.items_synced, state.items_per_sec
        );
        frame.print_text_clipped(
            area.x + 2,
            stats_y,
            &stats,
            Cell {
                fg: TEXT_MUTED,
                ..Cell::default()
            },
            max_x,
        );
    }

    // Cancel hint at bottom.
    let hint_y = area.bottom().saturating_sub(1);
    frame.print_text_clipped(
        area.x + 2,
        hint_y,
        "Esc: cancel sync",
        Cell {
            fg: TEXT_MUTED,
            ..Cell::default()
        },
        max_x,
    );
}

/// Render a horizontal progress bar.
fn render_progress_bar(frame: &mut Frame<'_>, x: u16, y: u16, width: u16, fraction: f64) {
    let filled = ((width as f64) * fraction).round() as u16;
    let max_x = x + width;

    for col in x..max_x {
        let is_filled = col < x + filled;
        let cell = Cell {
            fg: if is_filled { PROGRESS_FG } else { PROGRESS_BG },
            bg: if is_filled { PROGRESS_FG } else { PROGRESS_BG },
            ..Cell::default()
        };
        frame.buffer.set(col, y, cell);
    }
}

// ---------------------------------------------------------------------------
// Summary view
// ---------------------------------------------------------------------------

fn render_summary(frame: &mut Frame<'_>, state: &SyncState, area: Rect) {
    let max_x = area.right();

    // Title.
    let title = "Sync Complete";
    let title_x = area.x + 2;
    frame.print_text_clipped(
        title_x,
        area.y + 1,
        title,
        Cell {
            fg: SUCCESS_FG,
            ..Cell::default()
        },
        max_x,
    );

    if let Some(ref summary) = state.summary {
        // Duration.
        let duration = format_duration(summary.elapsed_ms);
        frame.print_text_clipped(
            title_x,
            area.y + 2,
            &format!("Duration: {duration}"),
            Cell {
                fg: TEXT_MUTED,
                ..Cell::default()
            },
            max_x,
        );

        // Summary table header.
        let table_y = area.y + 4;
        let header = format!("{:<16} {:>6} {:>8}", "Entity", "New", "Updated");
        frame.print_text_clipped(
            area.x + 2,
            table_y,
            &header,
            Cell {
                fg: TEXT_MUTED,
                ..Cell::default()
            },
            max_x,
        );

        // Summary rows.
        let rows = [
            ("Issues", summary.issues.new, summary.issues.updated),
            (
                "MRs",
                summary.merge_requests.new,
                summary.merge_requests.updated,
            ),
            (
                "Discussions",
                summary.discussions.new,
                summary.discussions.updated,
            ),
            ("Notes", summary.notes.new, summary.notes.updated),
        ];

        for (i, (label, new, updated)) in rows.iter().enumerate() {
            let row_y = table_y + 1 + i as u16;
            if row_y >= area.bottom().saturating_sub(3) {
                break;
            }

            let row = format!("{label:<16} {new:>6} {updated:>8}");
            let fg = if *new > 0 || *updated > 0 {
                TEXT
            } else {
                TEXT_MUTED
            };
            frame.print_text_clipped(
                area.x + 2,
                row_y,
                &row,
                Cell {
                    fg,
                    ..Cell::default()
                },
                max_x,
            );
        }

        // Total.
        let total_y = table_y + 1 + rows.len() as u16;
        if total_y < area.bottom().saturating_sub(2) {
            let total = format!("Total changes: {}", summary.total_changes());
            frame.print_text_clipped(
                area.x + 2,
                total_y,
                &total,
                Cell {
                    fg: ACCENT,
                    ..Cell::default()
                },
                max_x,
            );
        }

        // Per-project errors.
        if summary.has_errors() {
            let err_y = total_y + 2;
            if err_y < area.bottom().saturating_sub(1) {
                frame.print_text_clipped(
                    area.x + 2,
                    err_y,
                    "Errors:",
                    Cell {
                        fg: ERROR_FG,
                        ..Cell::default()
                    },
                    max_x,
                );
                for (i, (project, err)) in summary.project_errors.iter().enumerate() {
                    let y = err_y + 1 + i as u16;
                    if y >= area.bottom().saturating_sub(1) {
                        break;
                    }
                    let line = format!("  {project}: {err}");
                    frame.print_text_clipped(
                        area.x + 2,
                        y,
                        &line,
                        Cell {
                            fg: ERROR_FG,
                            ..Cell::default()
                        },
                        max_x,
                    );
                }
            }
        }
    }

    // Navigation hint at bottom.
    let hint_y = area.bottom().saturating_sub(1);
    frame.print_text_clipped(
        area.x + 2,
        hint_y,
        "Esc: back  |  Enter: sync again",
        Cell {
            fg: TEXT_MUTED,
            ..Cell::default()
        },
        max_x,
    );
}

// ---------------------------------------------------------------------------
// Cancelled / Failed views
// ---------------------------------------------------------------------------

fn render_cancelled(frame: &mut Frame<'_>, area: Rect) {
    let max_x = area.right();
    let center_y = area.y + area.height / 2;

    frame.print_text_clipped(
        area.x + 2,
        center_y.saturating_sub(1),
        "Sync Cancelled",
        Cell {
            fg: ACCENT,
            ..Cell::default()
        },
        max_x,
    );
    frame.print_text_clipped(
        area.x + 2,
        center_y + 1,
        "Press Enter to retry, or Esc to go back.",
        Cell {
            fg: TEXT_MUTED,
            ..Cell::default()
        },
        max_x,
    );
}

fn render_failed(frame: &mut Frame<'_>, area: Rect, error: &str) {
    let max_x = area.right();
    let center_y = area.y + area.height / 2;

    frame.print_text_clipped(
        area.x + 2,
        center_y.saturating_sub(2),
        "Sync Failed",
        Cell {
            fg: ERROR_FG,
            ..Cell::default()
        },
        max_x,
    );

    // Truncate error to fit screen.
    let max_len = area.width.saturating_sub(4) as usize;
    let display_err = if error.len() > max_len {
        format!(
            "{}...",
            &error[..error.floor_char_boundary(max_len.saturating_sub(3))]
        )
    } else {
        error.to_string()
    };
    frame.print_text_clipped(
        area.x + 2,
        center_y,
        &display_err,
        Cell {
            fg: TEXT,
            ..Cell::default()
        },
        max_x,
    );

    frame.print_text_clipped(
        area.x + 2,
        center_y + 2,
        "Press Enter to retry, or Esc to go back.",
        Cell {
            fg: TEXT_MUTED,
            ..Cell::default()
        },
        max_x,
    );
}

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

fn format_duration(ms: u64) -> String {
    let secs = ms / 1000;
    let mins = secs / 60;
    let remaining_secs = secs % 60;
    if mins > 0 {
        format!("{mins}m {remaining_secs}s")
    } else {
        format!("{secs}s")
    }
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::state::sync::{EntityChangeCounts, SyncSummary};
    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
        }};
    }

    #[test]
    fn test_render_idle_no_panic() {
        with_frame!(80, 24, |frame| {
            let state = SyncState::default();
            let area = frame.bounds();
            render_sync(&mut frame, &state, area);
        });
    }

    #[test]
    fn test_render_running_no_panic() {
        with_frame!(80, 24, |frame| {
            let mut state = SyncState::default();
            state.start();
            state.update_progress("issues", 25, 100);
            let area = frame.bounds();
            render_sync(&mut frame, &state, area);
        });
    }

    #[test]
    fn test_render_complete_no_panic() {
        with_frame!(80, 24, |frame| {
            let mut state = SyncState::default();
            state.start();
            state.complete(5000);
            state.summary = Some(SyncSummary {
                issues: EntityChangeCounts { new: 5, updated: 3 },
                merge_requests: EntityChangeCounts { new: 2, updated: 1 },
                elapsed_ms: 5000,
                ..Default::default()
            });
            let area = frame.bounds();
            render_sync(&mut frame, &state, area);
        });
    }

    #[test]
    fn test_render_cancelled_no_panic() {
        with_frame!(80, 24, |frame| {
            let mut state = SyncState::default();
            state.start();
            state.cancel();
            let area = frame.bounds();
            render_sync(&mut frame, &state, area);
        });
    }

    #[test]
    fn test_render_failed_no_panic() {
        with_frame!(80, 24, |frame| {
            let mut state = SyncState::default();
            state.start();
            state.fail("network timeout".into());
            let area = frame.bounds();
            render_sync(&mut frame, &state, area);
        });
    }

    #[test]
    fn test_render_tiny_terminal() {
        with_frame!(8, 2, |frame| {
            let state = SyncState::default();
            let area = frame.bounds();
            render_sync(&mut frame, &state, area);
            // Should not panic.
        });
    }

    #[test]
    fn test_render_complete_with_errors() {
        with_frame!(80, 24, |frame| {
            let mut state = SyncState::default();
            state.start();
            state.complete(3000);
            state.summary = Some(SyncSummary {
                elapsed_ms: 3000,
                project_errors: vec![("grp/repo".into(), "timeout".into())],
                ..Default::default()
            });
            let area = frame.bounds();
            render_sync(&mut frame, &state, area);
        });
    }

    #[test]
    fn test_format_duration_seconds() {
        assert_eq!(format_duration(3500), "3s");
    }

    #[test]
    fn test_format_duration_minutes() {
        assert_eq!(format_duration(125_000), "2m 5s");
    }

    #[test]
    fn test_render_running_with_stats() {
        with_frame!(80, 24, |frame| {
            let mut state = SyncState::default();
            state.start();
            state.update_progress("issues", 50, 200);
            state.update_stream_stats(1024, 50);
            let area = frame.bounds();
            render_sync(&mut frame, &state, area);
        });
    }
}
