//! Tab bar widget for TUI.
//!
//! Displays session tabs with status indicators.

use ratatui::{
    buffer::Buffer,
    layout::Rect,
    style::{Color, Modifier, Style},
    text::{Line, Span},
    widgets::Widget,
};
use unicode_width::UnicodeWidthStr;

use crate::session::SessionStatus;

/// Status indicator for session state
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StatusIndicator {
    /// Session starting (yellow)
    Starting,
    /// Session running (green)
    Running,
    /// Session terminated (red)
    Terminated,
}

impl From<&SessionStatus> for StatusIndicator {
    fn from(status: &SessionStatus) -> Self {
        match status {
            SessionStatus::Starting => Self::Starting,
            SessionStatus::Running => Self::Running,
            SessionStatus::Terminated { .. } => Self::Terminated,
        }
    }
}

impl StatusIndicator {
    /// Get color for this indicator
    #[must_use]
    pub const fn color(self) -> Color {
        match self {
            Self::Starting => Color::Yellow,
            Self::Running => Color::Green,
            Self::Terminated => Color::Red,
        }
    }

    /// Get symbol for this indicator
    #[must_use]
    pub const fn symbol(self) -> &'static str {
        match self {
            Self::Starting => "○",
            Self::Running => "●",
            Self::Terminated => "×",
        }
    }
}

/// Tab item representing a session
#[derive(Debug, Clone)]
pub struct TabItem {
    /// Tab title (session name)
    pub title: String,
    /// Status indicator
    pub status: StatusIndicator,
}

impl TabItem {
    /// Create new tab item
    #[must_use]
    pub fn new(title: impl Into<String>, status: StatusIndicator) -> Self {
        Self {
            title: title.into(),
            status,
        }
    }
}

/// Tab display width constants
const INDICATOR_WIDTH: u16 = 2; // "● " (symbol + space)
const SEPARATOR_WIDTH: u16 = 3; // " | "

/// Calculate display width of a single tab
///
/// Width = indicator(2) + `title.width()` + separator(3)
#[must_use]
pub fn tab_display_width(tab: &TabItem) -> u16 {
    #[allow(clippy::cast_possible_truncation)]
    let title_width = tab.title.width() as u16;
    INDICATOR_WIDTH + title_width + SEPARATOR_WIDTH
}

/// Find tab index at given column position
///
/// Returns `Some(index)` if column falls within a tab's bounds, `None` otherwise.
#[must_use]
pub fn find_tab_at_column(tabs: &[TabItem], column: u16) -> Option<usize> {
    let mut x = 0u16;
    for (i, tab) in tabs.iter().enumerate() {
        let width = tab_display_width(tab);
        if column < x + width {
            return Some(i);
        }
        x += width;
    }
    None
}

/// Tab bar widget displaying session tabs
#[derive(Debug, Clone)]
pub struct TabBar<'a> {
    /// Tab items
    tabs: &'a [TabItem],
    /// Active tab index
    active_idx: usize,
}

impl<'a> TabBar<'a> {
    /// Create new tab bar
    #[must_use]
    pub fn new(tabs: &'a [TabItem], active_idx: usize) -> Self {
        Self { tabs, active_idx }
    }
}

impl Widget for TabBar<'_> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        if area.height == 0 || self.tabs.is_empty() {
            return;
        }

        let mut x = area.x;
        for (i, tab) in self.tabs.iter().enumerate() {
            let is_active = i == self.active_idx;

            // Status indicator
            let indicator = Span::styled(
                format!("{} ", tab.status.symbol()),
                Style::default().fg(tab.status.color()),
            );

            // Tab title
            let title_style = if is_active {
                Style::default()
                    .fg(Color::White)
                    .add_modifier(Modifier::BOLD)
            } else {
                Style::default().fg(Color::Gray)
            };
            let title = Span::styled(&tab.title, title_style);

            // Separator
            let sep = Span::styled(" | ", Style::default().fg(Color::DarkGray));

            let spans = vec![indicator, title, sep];
            let line = Line::from(spans);
            #[allow(clippy::cast_possible_truncation)]
            let width = line.width() as u16;

            if x + width <= area.x + area.width {
                buf.set_line(x, area.y, &line, width);
                x += width;
            } else {
                break;
            }
        }
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use super::*;
    use crate::tui::test_utils::buffer_to_text;
    use rstest::rstest;

    // TDD: tab_display_width tests
    mod tab_display_width_tests {
        use super::*;

        #[test]
        fn ascii_title() {
            // "● session-1 | " = indicator(2) + title(9) + sep(3) = 14
            let tab = TabItem::new("session-1", StatusIndicator::Running);
            assert_eq!(tab_display_width(&tab), 14);
        }

        #[test]
        fn cjk_title() {
            // "● 日本語 | " = indicator(2) + title(6: each CJK = 2 cells) + sep(3) = 11
            let tab = TabItem::new("日本語", StatusIndicator::Running);
            assert_eq!(tab_display_width(&tab), 11);
        }

        #[test]
        fn emoji_title() {
            // "● 🎉test | " = indicator(2) + title(2+4=6) + sep(3) = 11
            let tab = TabItem::new("🎉test", StatusIndicator::Running);
            assert_eq!(tab_display_width(&tab), 11);
        }

        #[test]
        fn empty_title() {
            // "●  | " = indicator(2) + title(0) + sep(3) = 5
            let tab = TabItem::new("", StatusIndicator::Running);
            assert_eq!(tab_display_width(&tab), 5);
        }
    }

    // TDD: find_tab_at_column tests
    mod find_tab_at_column_tests {
        use super::*;

        #[test]
        fn single_tab_in_bounds() {
            let tabs = vec![TabItem::new("test", StatusIndicator::Running)];
            // "● test | " = 2 + 4 + 3 = 9
            assert_eq!(find_tab_at_column(&tabs, 0), Some(0));
            assert_eq!(find_tab_at_column(&tabs, 8), Some(0));
        }

        #[test]
        fn single_tab_out_of_bounds() {
            let tabs = vec![TabItem::new("test", StatusIndicator::Running)];
            // width = 9, so column 9+ should return None
            assert_eq!(find_tab_at_column(&tabs, 9), None);
            assert_eq!(find_tab_at_column(&tabs, 100), None);
        }

        #[test]
        fn multiple_tabs_variable_width() {
            let tabs = vec![
                TabItem::new("a", StatusIndicator::Running), // width = 2+1+3 = 6
                TabItem::new("日本語", StatusIndicator::Running), // width = 2+6+3 = 11
                TabItem::new("test", StatusIndicator::Running), // width = 2+4+3 = 9
            ];
            // tab0: [0, 5]
            assert_eq!(find_tab_at_column(&tabs, 0), Some(0));
            assert_eq!(find_tab_at_column(&tabs, 5), Some(0));
            // tab1: [6, 16]
            assert_eq!(find_tab_at_column(&tabs, 6), Some(1));
            assert_eq!(find_tab_at_column(&tabs, 16), Some(1));
            // tab2: [17, 25]
            assert_eq!(find_tab_at_column(&tabs, 17), Some(2));
            assert_eq!(find_tab_at_column(&tabs, 25), Some(2));
            // out of bounds
            assert_eq!(find_tab_at_column(&tabs, 26), None);
        }

        #[test]
        fn empty_tabs() {
            let tabs: Vec<TabItem> = vec![];
            assert_eq!(find_tab_at_column(&tabs, 0), None);
        }

        #[test]
        fn boundary_between_tabs() {
            let tabs = vec![
                TabItem::new("ab", StatusIndicator::Running), // width = 2+2+3 = 7
                TabItem::new("cd", StatusIndicator::Running), // width = 2+2+3 = 7
            ];
            // tab0: [0, 6], tab1: [7, 13]
            assert_eq!(find_tab_at_column(&tabs, 6), Some(0));
            assert_eq!(find_tab_at_column(&tabs, 7), Some(1));
        }
    }

    #[test]
    fn test_tab_bar_empty() {
        let tabs: Vec<TabItem> = vec![];
        let tab_bar = TabBar::new(&tabs, 0);
        let area = Rect::new(0, 0, 40, 1);
        let mut buf = Buffer::empty(area);

        // Should not panic
        tab_bar.render(area, &mut buf);
    }

    #[test]
    fn test_tab_bar_renders_tabs() {
        let tabs = vec![
            TabItem::new("session-1", StatusIndicator::Running),
            TabItem::new("session-2", StatusIndicator::Starting),
        ];
        let tab_bar = TabBar::new(&tabs, 0);
        let area = Rect::new(0, 0, 60, 1);
        let mut buf = Buffer::empty(area);

        tab_bar.render(area, &mut buf);
        let output = buffer_to_text(&buf);

        assert!(output.contains("session-1"));
        assert!(output.contains("session-2"));
    }

    #[test]
    fn test_tab_bar_highlights_active() {
        let tabs = vec![
            TabItem::new("tab-a", StatusIndicator::Running),
            TabItem::new("tab-b", StatusIndicator::Running),
        ];
        let tab_bar = TabBar::new(&tabs, 1);
        let area = Rect::new(0, 0, 60, 1);
        let mut buf = Buffer::empty(area);

        tab_bar.render(area, &mut buf);

        // Check that active tab (tab-b) has bold modifier
        // Find position of "tab-b" and check style
        let mut found_active = false;
        for x in 0..buf.area.width {
            let cell = &buf[(x, 0)];
            if cell.symbol() == "t" {
                // Check next cells for "ab-b"
                let mut s = String::new();
                for dx in 0..5 {
                    if x + dx < buf.area.width {
                        s.push_str(buf[(x + dx, 0)].symbol());
                    }
                }
                if s == "tab-b" && cell.modifier.contains(Modifier::BOLD) {
                    found_active = true;
                    break;
                }
            }
        }
        assert!(found_active, "Active tab should have BOLD modifier");
    }

    #[test]
    fn test_status_indicator_from_session_status() {
        assert_eq!(
            StatusIndicator::from(&SessionStatus::Starting),
            StatusIndicator::Starting
        );
        assert_eq!(
            StatusIndicator::from(&SessionStatus::Running),
            StatusIndicator::Running
        );
        assert_eq!(
            StatusIndicator::from(&SessionStatus::Terminated { exit_code: Some(0) }),
            StatusIndicator::Terminated
        );
    }

    #[rstest]
    #[case(StatusIndicator::Starting, Color::Yellow, "○")]
    #[case(StatusIndicator::Running, Color::Green, "●")]
    #[case(StatusIndicator::Terminated, Color::Red, "×")]
    fn status_indicator_variants(
        #[case] indicator: StatusIndicator,
        #[case] expected_color: Color,
        #[case] expected_symbol: &str,
    ) {
        assert_eq!(indicator.color(), expected_color);
        assert_eq!(indicator.symbol(), expected_symbol);
    }

    mod snapshots {
        use super::*;
        use crate::tui::test_utils::render_to_snapshot;
        use insta::assert_snapshot;

        #[test]
        fn single_tab_running() {
            let tabs = vec![TabItem::new("session-1", StatusIndicator::Running)];
            let tab_bar = TabBar::new(&tabs, 0);
            assert_snapshot!(render_to_snapshot(tab_bar, 30, 1));
        }

        #[test]
        fn multi_tab_active_highlight() {
            let tabs = vec![
                TabItem::new("tab-a", StatusIndicator::Running),
                TabItem::new("tab-b", StatusIndicator::Running),
            ];
            let tab_bar = TabBar::new(&tabs, 1); // tab-b active
            assert_snapshot!(render_to_snapshot(tab_bar, 40, 1));
        }

        #[test]
        fn mixed_status_indicators() {
            let tabs = vec![
                TabItem::new("running", StatusIndicator::Running),
                TabItem::new("starting", StatusIndicator::Starting),
                TabItem::new("terminated", StatusIndicator::Terminated),
            ];
            let tab_bar = TabBar::new(&tabs, 0);
            assert_snapshot!(render_to_snapshot(tab_bar, 60, 1));
        }

        #[test]
        fn truncation_at_boundary() {
            let tabs = vec![
                TabItem::new("very-long-session-name", StatusIndicator::Running),
                TabItem::new("another-session", StatusIndicator::Starting),
            ];
            let tab_bar = TabBar::new(&tabs, 0);
            // Width too small to fit second tab
            assert_snapshot!(render_to_snapshot(tab_bar, 30, 1));
        }
    }
}
