#![allow(dead_code)] // Phase 2: consumed by Issue Detail + MR Detail screens

//! Cross-reference widget for entity detail screens.
//!
//! Renders a list of linked entities (closing MRs, related issues, mentions)
//! as navigable items. Used in both Issue Detail and MR Detail views.

use std::fmt;

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

use crate::message::EntityKey;

// ---------------------------------------------------------------------------
// CrossRefKind
// ---------------------------------------------------------------------------

/// The relationship type between two entities.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CrossRefKind {
    /// MR that closes this issue when merged.
    ClosingMr,
    /// Issue related via GitLab link.
    RelatedIssue,
    /// Entity mentioned in a note or description.
    MentionedIn,
}

impl CrossRefKind {
    /// Short icon/prefix for display.
    #[must_use]
    pub const fn icon(&self) -> &str {
        match self {
            Self::ClosingMr => "MR",
            Self::RelatedIssue => "REL",
            Self::MentionedIn => "REF",
        }
    }
}

impl fmt::Display for CrossRefKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::ClosingMr => write!(f, "Closing MR"),
            Self::RelatedIssue => write!(f, "Related Issue"),
            Self::MentionedIn => write!(f, "Mentioned In"),
        }
    }
}

// ---------------------------------------------------------------------------
// CrossRef
// ---------------------------------------------------------------------------

/// A single cross-reference to another entity.
#[derive(Debug, Clone)]
pub struct CrossRef {
    /// Relationship type.
    pub kind: CrossRefKind,
    /// Target entity identity.
    pub entity_key: EntityKey,
    /// Human-readable label (e.g., "Fix authentication flow").
    pub label: String,
    /// Whether this ref points to an entity in the local DB (navigable).
    pub navigable: bool,
}

// ---------------------------------------------------------------------------
// CrossRefState
// ---------------------------------------------------------------------------

/// Rendering state for the cross-reference list.
#[derive(Debug, Clone, Default)]
pub struct CrossRefState {
    /// Index of the selected cross-reference.
    pub selected: usize,
    /// First visible item index.
    pub scroll_offset: usize,
}

impl CrossRefState {
    /// Move selection down.
    pub fn select_next(&mut self, total: usize) {
        if total > 0 && self.selected < total - 1 {
            self.selected += 1;
        }
    }

    /// Move selection up.
    pub fn select_prev(&mut self) {
        self.selected = self.selected.saturating_sub(1);
    }
}

// ---------------------------------------------------------------------------
// Colors
// ---------------------------------------------------------------------------

/// Color scheme for cross-reference rendering.
pub struct CrossRefColors {
    /// Foreground for the kind icon/badge.
    pub kind_fg: PackedRgba,
    /// Foreground for the label text.
    pub label_fg: PackedRgba,
    /// Muted foreground for non-navigable refs.
    pub muted_fg: PackedRgba,
    /// Selected item foreground.
    pub selected_fg: PackedRgba,
    /// Selected item background.
    pub selected_bg: PackedRgba,
}

// ---------------------------------------------------------------------------
// Render
// ---------------------------------------------------------------------------

/// Render a list of cross-references within the given area.
///
/// Returns the number of rows consumed.
///
/// Layout per row:
/// ```text
///   [MR]  !42 Fix authentication flow
///   [REL] #15 Related auth issue
///   [REF] !99 Mentioned in pipeline MR
/// ```
pub fn render_cross_refs(
    frame: &mut Frame<'_>,
    refs: &[CrossRef],
    state: &CrossRefState,
    area: Rect,
    colors: &CrossRefColors,
) -> u16 {
    if refs.is_empty() || area.height == 0 || area.width < 10 {
        return 0;
    }

    let max_x = area.x.saturating_add(area.width);
    let visible_count = (area.height as usize).min(refs.len().saturating_sub(state.scroll_offset));

    for i in 0..visible_count {
        let idx = state.scroll_offset + i;
        let Some(cr) = refs.get(idx) else { break };

        let y = area.y + i as u16;
        let is_selected = idx == state.selected;

        // Background fill for selected row.
        if is_selected {
            frame.draw_rect_filled(
                Rect::new(area.x, y, area.width, 1),
                Cell {
                    fg: colors.selected_fg,
                    bg: colors.selected_bg,
                    ..Cell::default()
                },
            );
        }

        let mut x = area.x;

        // Kind badge: [MR], [REL], [REF]
        let badge = format!("[{}]", cr.kind.icon());
        let badge_style = if is_selected {
            Cell {
                fg: colors.selected_fg,
                bg: colors.selected_bg,
                ..Cell::default()
            }
        } else {
            Cell {
                fg: colors.kind_fg,
                ..Cell::default()
            }
        };
        x = frame.print_text_clipped(x, y, &badge, badge_style, max_x);

        // Spacing
        x = frame.print_text_clipped(x, y, " ", badge_style, max_x);

        // Entity prefix + label — derive sigil from entity kind, not ref kind.
        let prefix = match cr.kind {
            CrossRefKind::ClosingMr => format!("!{} ", cr.entity_key.iid),
            CrossRefKind::RelatedIssue => format!("#{} ", cr.entity_key.iid),
            CrossRefKind::MentionedIn => {
                let sigil = match cr.entity_key.kind {
                    crate::message::EntityKind::MergeRequest => "!",
                    crate::message::EntityKind::Issue => "#",
                };
                format!("{sigil}{} ", cr.entity_key.iid)
            }
        };

        let label_style = if is_selected {
            Cell {
                fg: colors.selected_fg,
                bg: colors.selected_bg,
                ..Cell::default()
            }
        } else if cr.navigable {
            Cell {
                fg: colors.label_fg,
                ..Cell::default()
            }
        } else {
            Cell {
                fg: colors.muted_fg,
                ..Cell::default()
            }
        };

        x = frame.print_text_clipped(x, y, &prefix, label_style, max_x);
        let _ = frame.print_text_clipped(x, y, &cr.label, label_style, max_x);
    }

    visible_count as u16
}

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

#[cfg(test)]
mod tests {
    use super::*;
    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_refs() -> Vec<CrossRef> {
        vec![
            CrossRef {
                kind: CrossRefKind::ClosingMr,
                entity_key: EntityKey::mr(1, 42),
                label: "Fix authentication flow".into(),
                navigable: true,
            },
            CrossRef {
                kind: CrossRefKind::RelatedIssue,
                entity_key: EntityKey::issue(1, 15),
                label: "Related auth issue".into(),
                navigable: true,
            },
            CrossRef {
                kind: CrossRefKind::MentionedIn,
                entity_key: EntityKey::mr(2, 99),
                label: "Pipeline improvements".into(),
                navigable: false,
            },
        ]
    }

    fn test_colors() -> CrossRefColors {
        CrossRefColors {
            kind_fg: PackedRgba::rgb(0xDA, 0x70, 0x2C),
            label_fg: PackedRgba::rgb(0xCE, 0xCD, 0xC3),
            muted_fg: PackedRgba::rgb(0x87, 0x87, 0x80),
            selected_fg: PackedRgba::rgb(0x10, 0x0F, 0x0F),
            selected_bg: PackedRgba::rgb(0xCE, 0xCD, 0xC3),
        }
    }

    // TDD anchor test from bead spec.
    #[test]
    fn test_cross_ref_entity_key() {
        let cr = CrossRef {
            kind: CrossRefKind::ClosingMr,
            entity_key: EntityKey::mr(1, 42),
            label: "Fix auth".into(),
            navigable: true,
        };
        assert_eq!(cr.kind, CrossRefKind::ClosingMr);
        assert_eq!(cr.entity_key, EntityKey::mr(1, 42));
    }

    #[test]
    fn test_cross_ref_kind_display() {
        assert_eq!(CrossRefKind::ClosingMr.to_string(), "Closing MR");
        assert_eq!(CrossRefKind::RelatedIssue.to_string(), "Related Issue");
        assert_eq!(CrossRefKind::MentionedIn.to_string(), "Mentioned In");
    }

    #[test]
    fn test_cross_ref_kind_icon() {
        assert_eq!(CrossRefKind::ClosingMr.icon(), "MR");
        assert_eq!(CrossRefKind::RelatedIssue.icon(), "REL");
        assert_eq!(CrossRefKind::MentionedIn.icon(), "REF");
    }

    #[test]
    fn test_cross_ref_state_navigation() {
        let mut state = CrossRefState::default();
        assert_eq!(state.selected, 0);

        state.select_next(3);
        assert_eq!(state.selected, 1);

        state.select_next(3);
        assert_eq!(state.selected, 2);

        // Can't go past end.
        state.select_next(3);
        assert_eq!(state.selected, 2);

        state.select_prev();
        assert_eq!(state.selected, 1);

        state.select_prev();
        assert_eq!(state.selected, 0);

        // Can't go before start.
        state.select_prev();
        assert_eq!(state.selected, 0);
    }

    #[test]
    fn test_render_cross_refs_no_panic() {
        with_frame!(80, 10, |frame| {
            let refs = sample_refs();
            let state = CrossRefState::default();
            let rows = render_cross_refs(
                &mut frame,
                &refs,
                &state,
                Rect::new(0, 0, 80, 10),
                &test_colors(),
            );
            assert_eq!(rows, 3);
        });
    }

    #[test]
    fn test_render_cross_refs_empty() {
        with_frame!(80, 10, |frame| {
            let state = CrossRefState::default();
            let rows = render_cross_refs(
                &mut frame,
                &[],
                &state,
                Rect::new(0, 0, 80, 10),
                &test_colors(),
            );
            assert_eq!(rows, 0);
        });
    }

    #[test]
    fn test_render_cross_refs_tiny_area() {
        with_frame!(5, 1, |frame| {
            let refs = sample_refs();
            let state = CrossRefState::default();
            let rows = render_cross_refs(
                &mut frame,
                &refs,
                &state,
                Rect::new(0, 0, 5, 1),
                &test_colors(),
            );
            // Too narrow (< 10), should bail.
            assert_eq!(rows, 0);
        });
    }

    #[test]
    fn test_render_cross_refs_with_scroll() {
        with_frame!(80, 2, |frame| {
            let refs = sample_refs();
            let state = CrossRefState {
                selected: 2,
                scroll_offset: 1,
            };
            let rows = render_cross_refs(
                &mut frame,
                &refs,
                &state,
                Rect::new(0, 0, 80, 2),
                &test_colors(),
            );
            // 2 visible (indices 1 and 2).
            assert_eq!(rows, 2);
        });
    }

    #[test]
    fn test_render_cross_refs_non_navigable() {
        with_frame!(80, 5, |frame| {
            let refs = vec![CrossRef {
                kind: CrossRefKind::MentionedIn,
                entity_key: EntityKey::mr(2, 99),
                label: "Non-local entity".into(),
                navigable: false,
            }];
            let state = CrossRefState::default();
            let rows = render_cross_refs(
                &mut frame,
                &refs,
                &state,
                Rect::new(0, 0, 80, 5),
                &test_colors(),
            );
            assert_eq!(rows, 1);
        });
    }
}
