//! Navigation breadcrumb trail ("Dashboard > Issues > #42").

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

use crate::navigation::NavigationStack;

/// Render the navigation breadcrumb trail.
///
/// Shows "Dashboard > Issues > Issue" with " > " separators. When the
/// trail exceeds the available width, entries are truncated from the left
/// with a leading "...".
pub fn render_breadcrumb(
    frame: &mut Frame<'_>,
    area: Rect,
    nav: &NavigationStack,
    text_color: PackedRgba,
    muted_color: PackedRgba,
) {
    if area.height == 0 || area.width < 3 {
        return;
    }

    let crumbs = nav.breadcrumbs();
    let separator = " > ";

    // Build the full breadcrumb string and calculate width.
    let full: String = crumbs.join(separator);
    let max_width = area.width as usize;

    let display = if full.len() <= max_width {
        full
    } else {
        // Truncate from the left: show "... > last_crumbs"
        truncate_breadcrumb_left(&crumbs, separator, max_width)
    };

    let base = Cell {
        fg: text_color,
        ..Cell::default()
    };
    let muted = Cell {
        fg: muted_color,
        ..Cell::default()
    };

    // Render each segment with separators in muted color.
    let mut x = area.x;
    let max_x = area.x.saturating_add(area.width);

    if let Some(rest) = display.strip_prefix("...") {
        // Render ellipsis in muted, then the rest
        x = frame.print_text_clipped(x, area.y, "...", muted, max_x);
        if !rest.is_empty() {
            render_crumb_segments(frame, x, area.y, rest, separator, base, muted, max_x);
        }
    } else {
        render_crumb_segments(frame, x, area.y, &display, separator, base, muted, max_x);
    }
}

/// Render breadcrumb text with separators in muted color.
#[allow(clippy::too_many_arguments)]
fn render_crumb_segments(
    frame: &mut Frame<'_>,
    start_x: u16,
    y: u16,
    text: &str,
    separator: &str,
    base: Cell,
    muted: Cell,
    max_x: u16,
) {
    let mut x = start_x;
    let parts: Vec<&str> = text.split(separator).collect();

    for (i, part) in parts.iter().enumerate() {
        if i > 0 {
            x = frame.print_text_clipped(x, y, separator, muted, max_x);
        }
        x = frame.print_text_clipped(x, y, part, base, max_x);
        if x >= max_x {
            break;
        }
    }
}

/// Truncate breadcrumb from the left to fit within max_width.
fn truncate_breadcrumb_left(crumbs: &[&str], separator: &str, max_width: usize) -> String {
    let ellipsis = "...";

    // Try showing progressively fewer crumbs from the right.
    for skip in 1..crumbs.len() {
        let tail = &crumbs[skip..];
        let tail_str: String = tail.join(separator);
        let candidate = format!("{ellipsis}{separator}{tail_str}");
        if candidate.len() <= max_width {
            return candidate;
        }
    }

    // Last resort: just the current screen truncated.
    let last = crumbs.last().unwrap_or(&"");
    if last.len() + ellipsis.len() <= max_width {
        return format!("{ellipsis}{last}");
    }

    // Truly tiny terminal: just ellipsis.
    ellipsis.to_string()
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::message::Screen;
    use crate::navigation::NavigationStack;
    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 white() -> PackedRgba {
        PackedRgba::rgb(0xFF, 0xFF, 0xFF)
    }

    fn gray() -> PackedRgba {
        PackedRgba::rgb(0x80, 0x80, 0x80)
    }

    #[test]
    fn test_breadcrumb_single_screen() {
        with_frame!(80, 1, |frame| {
            let nav = NavigationStack::new();
            render_breadcrumb(&mut frame, Rect::new(0, 0, 80, 1), &nav, white(), gray());

            let cell = frame.buffer.get(0, 0).unwrap();
            assert!(
                cell.content.as_char() == Some('D'),
                "Expected 'D' at (0,0), got {:?}",
                cell.content.as_char()
            );
        });
    }

    #[test]
    fn test_breadcrumb_multi_screen() {
        with_frame!(80, 1, |frame| {
            let mut nav = NavigationStack::new();
            nav.push(Screen::IssueList);
            render_breadcrumb(&mut frame, Rect::new(0, 0, 80, 1), &nav, white(), gray());

            let d = frame.buffer.get(0, 0).unwrap();
            assert_eq!(d.content.as_char(), Some('D'));

            // "Dashboard > Issues" = 'I' at 12
            let i_cell = frame.buffer.get(12, 0).unwrap();
            assert_eq!(i_cell.content.as_char(), Some('I'));
        });
    }

    #[test]
    fn test_breadcrumb_truncation() {
        let crumbs = vec!["Dashboard", "Issues", "Issue"];
        let result = truncate_breadcrumb_left(&crumbs, " > ", 20);
        assert!(
            result.starts_with("..."),
            "Expected ellipsis prefix, got: {result}"
        );
        assert!(result.len() <= 20, "Result too long: {result}");
    }

    #[test]
    fn test_breadcrumb_zero_height_noop() {
        with_frame!(80, 1, |frame| {
            let nav = NavigationStack::new();
            render_breadcrumb(&mut frame, Rect::new(0, 0, 80, 0), &nav, white(), gray());
        });
    }

    #[test]
    fn test_truncate_breadcrumb_fits() {
        let crumbs = vec!["A", "B"];
        let result = truncate_breadcrumb_left(&crumbs, " > ", 100);
        assert!(result.contains("..."), "Should always add ellipsis");
    }

    #[test]
    fn test_truncate_breadcrumb_single_entry() {
        let crumbs = vec!["Dashboard"];
        let result = truncate_breadcrumb_left(&crumbs, " > ", 5);
        assert_eq!(result, "...");
    }

    #[test]
    fn test_truncate_breadcrumb_shows_last_entries() {
        let crumbs = vec!["Dashboard", "Issues", "Issue Detail"];
        let result = truncate_breadcrumb_left(&crumbs, " > ", 30);
        assert!(result.starts_with("..."));
        assert!(result.contains("Issue Detail"));
    }
}
