#![allow(dead_code)]

//! File History view — renders per-file MR timeline with rename chains.
//!
//! Layout:
//! ```text
//! +-----------------------------------+
//! | Path: [src/lib.rs_]  [R] [M] [D]  |  <- path input + option toggles
//! | Rename chain: a.rs -> b.rs -> ...  |  <- shown when renames followed
//! | 5 merge requests across 2 paths   |  <- summary line
//! +-----------------------------------+
//! | > !42 Fix auth @alice modified ... |  <- MR list (selected = >)
//! |   !39 Refactor  @bob  renamed ... |
//! |     @carol: "This looks off..."    |  <- inline discussion (if toggled)
//! +-----------------------------------+
//! | r:renames m:merged d:discussions   |  <- hint bar
//! +-----------------------------------+
//! ```

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

use super::common::truncate_str;
use crate::state::file_history::{FileHistoryResult, FileHistoryState};
use crate::text_width::cursor_cell_offset;

// ---------------------------------------------------------------------------
// Colors (Flexoki palette)
// ---------------------------------------------------------------------------

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

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

/// Render the File History screen.
pub fn render_file_history(
    frame: &mut Frame<'_>,
    state: &FileHistoryState,
    area: ftui::core::geometry::Rect,
) {
    if area.width < 10 || area.height < 3 {
        return; // Terminal too small.
    }

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

    // --- Path input bar ---
    render_path_input(frame, state, x, y, width);
    y += 1;

    if area.height < 5 {
        return;
    }

    // --- Option toggles indicator ---
    render_toggle_indicators(frame, state, x, y, width);
    y += 1;

    // --- Loading indicator ---
    if state.loading {
        render_loading(frame, x, y, max_x);
        return;
    }

    let Some(result) = &state.result else {
        render_empty_state(frame, x, y, max_x);
        return;
    };

    // --- Rename chain (if followed) ---
    if result.renames_followed && result.rename_chain.len() > 1 {
        render_rename_chain(frame, &result.rename_chain, x, y, max_x);
        y += 1;
    }

    // --- Summary line ---
    render_summary(frame, result, x, y, max_x);
    y += 1;

    if result.merge_requests.is_empty() {
        render_no_results(frame, x, y, max_x);
        return;
    }

    // Reserve 1 row for hint bar at the bottom.
    let hint_y = area.bottom().saturating_sub(1);
    let list_height = hint_y.saturating_sub(y) as usize;

    if list_height == 0 {
        return;
    }

    // --- MR list ---
    render_mr_list(frame, result, state, x, y, width, list_height);

    // --- Hint bar ---
    render_hint_bar(frame, x, hint_y, max_x);
}

// ---------------------------------------------------------------------------
// Components
// ---------------------------------------------------------------------------

fn render_path_input(frame: &mut Frame<'_>, state: &FileHistoryState, x: u16, y: u16, width: u16) {
    let max_x = x + width;
    let label = "Path: ";
    let label_style = Cell {
        fg: TEXT_MUTED,
        ..Cell::default()
    };
    let after_label = frame.print_text_clipped(x, y, label, label_style, max_x);

    // Input text.
    let input_style = Cell {
        fg: if state.path_focused { TEXT } else { TEXT_MUTED },
        ..Cell::default()
    };
    let display_text = if state.path_input.is_empty() && !state.path_focused {
        "type a file path..."
    } else {
        &state.path_input
    };
    frame.print_text_clipped(after_label, y, display_text, input_style, max_x);

    // Cursor indicator.
    if state.path_focused {
        let cursor_x = after_label + cursor_cell_offset(&state.path_input, state.path_cursor);
        if cursor_x < max_x {
            let cursor_cell = Cell {
                fg: PackedRgba::rgb(0x10, 0x0F, 0x0F), // dark bg
                bg: TEXT,
                ..Cell::default()
            };
            let ch = state
                .path_input
                .get(state.path_cursor..)
                .and_then(|s| s.chars().next())
                .unwrap_or(' ');
            frame.print_text_clipped(cursor_x, y, &ch.to_string(), cursor_cell, max_x);
        }
    }
}

fn render_toggle_indicators(
    frame: &mut Frame<'_>,
    state: &FileHistoryState,
    x: u16,
    y: u16,
    width: u16,
) {
    let max_x = x + width;

    let on_style = Cell {
        fg: GREEN,
        ..Cell::default()
    };
    let off_style = Cell {
        fg: TEXT_MUTED,
        ..Cell::default()
    };

    let renames_tag = if state.follow_renames {
        "[renames:on]"
    } else {
        "[renames:off]"
    };
    let merged_tag = if state.merged_only {
        "[merged:on]"
    } else {
        "[merged:off]"
    };
    let disc_tag = if state.show_discussions {
        "[disc:on]"
    } else {
        "[disc:off]"
    };

    let renames_style = if state.follow_renames {
        on_style
    } else {
        off_style
    };
    let merged_style = if state.merged_only {
        on_style
    } else {
        off_style
    };
    let disc_style = if state.show_discussions {
        on_style
    } else {
        off_style
    };

    let after_r = frame.print_text_clipped(x + 1, y, renames_tag, renames_style, max_x);
    let after_m = frame.print_text_clipped(after_r + 1, y, merged_tag, merged_style, max_x);
    frame.print_text_clipped(after_m + 1, y, disc_tag, disc_style, max_x);
}

fn render_rename_chain(frame: &mut Frame<'_>, chain: &[String], x: u16, y: u16, max_x: u16) {
    let label_style = Cell {
        fg: TEXT_MUTED,
        ..Cell::default()
    };
    let chain_style = Cell {
        fg: CYAN,
        ..Cell::default()
    };

    let after_label = frame.print_text_clipped(x + 1, y, "Renames: ", label_style, max_x);
    let chain_str = chain.join(" -> ");
    frame.print_text_clipped(after_label, y, &chain_str, chain_style, max_x);
}

fn render_summary(frame: &mut Frame<'_>, result: &FileHistoryResult, x: u16, y: u16, max_x: u16) {
    let summary = if result.paths_searched > 1 {
        format!(
            "{} merge request{} across {} paths",
            result.total_mrs,
            if result.total_mrs == 1 { "" } else { "s" },
            result.paths_searched,
        )
    } else {
        format!(
            "{} merge request{}",
            result.total_mrs,
            if result.total_mrs == 1 { "" } else { "s" },
        )
    };

    let style = Cell {
        fg: TEXT_MUTED,
        ..Cell::default()
    };
    frame.print_text_clipped(x + 1, y, &summary, style, max_x);
}

fn render_mr_list(
    frame: &mut Frame<'_>,
    result: &FileHistoryResult,
    state: &FileHistoryState,
    x: u16,
    start_y: u16,
    width: u16,
    height: usize,
) {
    let max_x = x + width;
    let offset = state.scroll_offset as usize;

    for (i, mr) in result
        .merge_requests
        .iter()
        .skip(offset)
        .enumerate()
        .take(height)
    {
        let y = start_y + i as u16;
        let row_idx = offset + i;
        let selected = row_idx == state.selected_mr_index;

        // Selection background.
        if selected {
            let bg_cell = Cell {
                bg: SELECTION_BG,
                ..Cell::default()
            };
            for col in x..max_x {
                frame.buffer.set(col, y, bg_cell);
            }
        }

        // State icon.
        let (icon, icon_color) = match mr.state.as_str() {
            "merged" => ("M", GREEN),
            "opened" => ("O", YELLOW),
            "closed" => ("C", RED),
            _ => ("?", TEXT_MUTED),
        };
        let prefix = if selected { "> " } else { "  " };
        let sel_bg = if selected { SELECTION_BG } else { BG_SURFACE };

        let prefix_style = Cell {
            fg: ACCENT,
            bg: sel_bg,
            ..Cell::default()
        };
        let after_prefix = frame.print_text_clipped(x, y, prefix, prefix_style, max_x);

        let icon_style = Cell {
            fg: icon_color,
            bg: sel_bg,
            ..Cell::default()
        };
        let after_icon = frame.print_text_clipped(after_prefix, y, icon, icon_style, max_x);

        // !iid
        let iid_str = format!(" !{}", mr.iid);
        let ref_style = Cell {
            fg: ACCENT,
            bg: sel_bg,
            ..Cell::default()
        };
        let after_iid = frame.print_text_clipped(after_icon, y, &iid_str, ref_style, max_x);

        // Title (truncated).
        let title = truncate_str(&mr.title, 35);
        let title_style = Cell {
            fg: TEXT,
            bg: sel_bg,
            ..Cell::default()
        };
        let after_title = frame.print_text_clipped(after_iid + 1, y, &title, title_style, max_x);

        // @author + change_type
        let meta = format!(
            "@{} {}",
            truncate_str(&mr.author_username, 12),
            mr.change_type
        );
        let meta_style = Cell {
            fg: TEXT_MUTED,
            bg: sel_bg,
            ..Cell::default()
        };
        frame.print_text_clipped(after_title + 1, y, &meta, meta_style, max_x);
    }

    // Inline discussion snippets (rendered beneath MRs when toggled on).
    // For simplicity, discussions are shown as a separate block after the MR list
    // in this initial implementation. Full inline rendering (grouped by MR) is
    // a follow-up enhancement.
    if state.show_discussions && !result.discussions.is_empty() {
        let disc_start_y = start_y + result.merge_requests.len().min(height) as u16;
        let remaining = height.saturating_sub(result.merge_requests.len().min(height));
        render_discussions(frame, result, x, disc_start_y, max_x, remaining);
    }
}

fn render_discussions(
    frame: &mut Frame<'_>,
    result: &FileHistoryResult,
    x: u16,
    start_y: u16,
    max_x: u16,
    max_rows: usize,
) {
    if max_rows == 0 {
        return;
    }

    let sep_style = Cell {
        fg: TEXT_MUTED,
        ..Cell::default()
    };
    frame.print_text_clipped(x + 1, start_y, "-- discussions --", sep_style, max_x);

    let disc_style = Cell {
        fg: TEXT_MUTED,
        ..Cell::default()
    };
    let author_style = Cell {
        fg: CYAN,
        ..Cell::default()
    };

    for (i, disc) in result
        .discussions
        .iter()
        .enumerate()
        .take(max_rows.saturating_sub(1))
    {
        let y = start_y + 1 + i as u16;
        let after_author = frame.print_text_clipped(
            x + 2,
            y,
            &format!("@{}: ", disc.author_username),
            author_style,
            max_x,
        );
        let snippet = truncate_str(&disc.body_snippet, 60);
        frame.print_text_clipped(after_author, y, &snippet, disc_style, max_x);
    }
}

fn render_loading(frame: &mut Frame<'_>, x: u16, y: u16, max_x: u16) {
    let style = Cell {
        fg: ACCENT,
        ..Cell::default()
    };
    frame.print_text_clipped(x + 1, y, "Loading file history...", style, max_x);
}

fn render_empty_state(frame: &mut Frame<'_>, x: u16, y: u16, max_x: u16) {
    let style = Cell {
        fg: TEXT_MUTED,
        ..Cell::default()
    };
    frame.print_text_clipped(
        x + 1,
        y,
        "Enter a file path and press Enter to search.",
        style,
        max_x,
    );
}

fn render_no_results(frame: &mut Frame<'_>, x: u16, y: u16, max_x: u16) {
    let style = Cell {
        fg: TEXT_MUTED,
        ..Cell::default()
    };
    frame.print_text_clipped(x + 1, y, "No MRs found for this file.", style, max_x);
    frame.print_text_clipped(
        x + 1,
        y + 1,
        "Hint: Ensure 'lore sync' has fetched MR file changes.",
        style,
        max_x,
    );
}

fn render_hint_bar(frame: &mut Frame<'_>, x: u16, y: u16, max_x: u16) {
    let style = Cell {
        fg: TEXT_MUTED,
        bg: BG_SURFACE,
        ..Cell::default()
    };

    // Fill background.
    for col in x..max_x {
        frame.buffer.set(col, y, style);
    }

    let hints = "/:path  r:renames  m:merged  d:discussions  Enter:open MR  q:back";
    frame.print_text_clipped(x + 1, y, hints, style, max_x);
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::state::file_history::{FileHistoryMr, FileHistoryResult, FileHistoryState};
    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 test_area(w: u16, h: u16) -> ftui::core::geometry::Rect {
        ftui::core::geometry::Rect {
            x: 0,
            y: 0,
            width: w,
            height: h,
        }
    }

    #[test]
    fn test_render_empty_no_panic() {
        with_frame!(80, 24, |frame| {
            let state = FileHistoryState::default();
            render_file_history(&mut frame, &state, test_area(80, 24));
        });
    }

    #[test]
    fn test_render_tiny_terminal_noop() {
        with_frame!(5, 2, |frame| {
            let state = FileHistoryState::default();
            render_file_history(&mut frame, &state, test_area(5, 2));
        });
    }

    #[test]
    fn test_render_loading() {
        with_frame!(80, 24, |frame| {
            let state = FileHistoryState {
                loading: true,
                ..FileHistoryState::default()
            };
            render_file_history(&mut frame, &state, test_area(80, 24));
        });
    }

    #[test]
    fn test_render_with_results() {
        with_frame!(100, 30, |frame| {
            let state = FileHistoryState {
                result: Some(FileHistoryResult {
                    path: "src/lib.rs".into(),
                    rename_chain: vec!["src/lib.rs".into()],
                    renames_followed: false,
                    merge_requests: vec![
                        FileHistoryMr {
                            iid: 42,
                            title: "Fix authentication flow".into(),
                            state: "merged".into(),
                            author_username: "alice".into(),
                            change_type: "modified".into(),
                            merged_at_ms: Some(1_700_000_000_000),
                            updated_at_ms: 1_700_000_000_000,
                            merge_commit_sha: Some("abc123".into()),
                        },
                        FileHistoryMr {
                            iid: 39,
                            title: "Refactor module structure".into(),
                            state: "opened".into(),
                            author_username: "bob".into(),
                            change_type: "renamed".into(),
                            merged_at_ms: None,
                            updated_at_ms: 1_699_000_000_000,
                            merge_commit_sha: None,
                        },
                    ],
                    discussions: vec![],
                    total_mrs: 2,
                    paths_searched: 1,
                }),
                ..FileHistoryState::default()
            };
            render_file_history(&mut frame, &state, test_area(100, 30));
        });
    }

    #[test]
    fn test_render_with_rename_chain() {
        with_frame!(80, 24, |frame| {
            let state = FileHistoryState {
                result: Some(FileHistoryResult {
                    path: "src/old.rs".into(),
                    rename_chain: vec!["src/old.rs".into(), "src/new.rs".into()],
                    renames_followed: true,
                    merge_requests: vec![],
                    discussions: vec![],
                    total_mrs: 0,
                    paths_searched: 2,
                }),
                ..FileHistoryState::default()
            };
            render_file_history(&mut frame, &state, test_area(80, 24));
        });
    }

    #[test]
    fn test_truncate_str() {
        assert_eq!(truncate_str("hello", 10), "hello");
        assert_eq!(truncate_str("hello world", 5), "hell…");
        assert_eq!(truncate_str("", 5), "");
    }
}
