//! Rich terminal output components using rich_rust.
//!
//! This module provides enhanced visual output for TTY terminals
//! while preserving backward compatibility with existing format functions.
//!
//! # Components
//!
//! - [`RichIssueTable`]: Display issues in a formatted table
//! - [`RichIssuePanel`]: Display single issue details in a panel
//! - [`RichDependencyTree`]: Display dependency graphs as trees
//! - [`format_count_badges`]: Display status count badges
//! - [`build_completion_bar`]: Display progress indicator
//!
//! # Integration
//!
//! These components work alongside existing text.rs functions:
//!
//! ```ignore
//! use crate::format::{OutputContext, OutputMode, Theme};
//! use crate::format::rich::RichIssueTable;
//!
//! let ctx = OutputContext::detect();
//! match ctx.mode() {
//!     OutputMode::Rich => {
//!         let table = RichIssueTable::new(&issues, &Theme::default());
//!         console.print_renderable(&table.build_table());
//!     }
//!     OutputMode::Plain => {
//!         // Use existing text.rs functions
//!         for issue in &issues {
//!             println!("{}", format_issue_line(issue));
//!         }
//!     }
//!     _ => { /* JSON/Quiet handled elsewhere */ }
//! }
//! ```

use crate::format::text::{
    format_priority, format_status_icon, format_status_label, format_type_label,
    sanitize_terminal_inline, sanitize_terminal_text, truncate_title,
};
use crate::model::{Issue, Status};
use crate::output::Theme;
use rich_rust::prelude::*;

/// Rich table display for a list of issues.
///
/// Displays issues with columns for status, ID, priority, type, and title.
pub struct RichIssueTable<'a> {
    issues: &'a [Issue],
    theme: &'a Theme,
    show_type: bool,
    max_title_width: Option<usize>,
}

impl<'a> RichIssueTable<'a> {
    /// Create a new issue table.
    #[must_use]
    pub fn new(issues: &'a [Issue], theme: &'a Theme) -> Self {
        Self {
            issues,
            theme,
            show_type: true,
            max_title_width: None,
        }
    }

    /// Set whether to show the issue type column.
    #[must_use]
    pub const fn show_type(mut self, show: bool) -> Self {
        self.show_type = show;
        self
    }

    /// Set maximum title width for truncation.
    #[must_use]
    pub const fn max_title_width(mut self, width: usize) -> Self {
        self.max_title_width = Some(width);
        self
    }

    /// Build a rich_rust Table from the issues.
    #[must_use]
    pub fn build_table(&self) -> Table {
        let mut table = Table::new()
            .with_column(Column::new("").width(2)) // Status icon
            .with_column(Column::new("ID"))
            .with_column(Column::new("Pri").justify(JustifyMethod::Center));

        if self.show_type {
            table = table.with_column(Column::new("Type"));
        }

        table = table.with_column(Column::new("Title"));

        for issue in self.issues {
            let status_icon = format_status_icon(&issue.status);
            let priority = format_priority(&issue.priority);
            let title = self.max_title_width.map_or_else(
                || sanitize_terminal_inline(&issue.title).into_owned(),
                |w| truncate_title(&issue.title, w),
            );

            let mut cells = Vec::new();

            // Status icon with color
            let status_style = self.theme.status_style(&issue.status);
            cells.push(Cell::new(status_icon).style(status_style.clone()));

            // Issue ID
            cells.push(
                Cell::new(sanitize_terminal_inline(&issue.id).into_owned())
                    .style(self.theme.issue_id.clone()),
            );

            // Priority with color
            let priority_style = self.theme.priority_style(issue.priority);
            cells.push(Cell::new(priority).style(priority_style.clone()));

            // Type (if showing)
            if self.show_type {
                let type_str = format_type_label(&issue.issue_type);
                let type_style = self.theme.type_style(&issue.issue_type);
                cells.push(Cell::new(type_str).style(type_style.clone()));
            }

            // Title
            cells.push(Cell::new(sanitize_terminal_inline(&title).into_owned()));

            table = table.with_row(Row::new(cells));
        }

        table
    }
}

/// Rich panel display for a single issue with full details.
pub struct RichIssuePanel<'a> {
    issue: &'a Issue,
    theme: &'a Theme,
    show_description: bool,
}

impl<'a> RichIssuePanel<'a> {
    /// Create a new issue panel.
    #[must_use]
    pub const fn new(issue: &'a Issue, theme: &'a Theme) -> Self {
        Self {
            issue,
            theme,
            show_description: true,
        }
    }

    /// Set whether to show the description.
    #[must_use]
    pub const fn show_description(mut self, show: bool) -> Self {
        self.show_description = show;
        self
    }

    /// Build a rich_rust Panel from the issue.
    #[must_use]
    pub fn build_panel(&self) -> Panel<'static> {
        let mut content = String::new();

        // Header line: status icon + title
        let status_icon = format_status_icon(&self.issue.status);
        content.push_str(&format!(
            "{} {}\n",
            status_icon,
            sanitize_terminal_inline(&self.issue.title)
        ));

        // Metadata line
        let priority = format_priority(&self.issue.priority);
        let type_str = format_type_label(&self.issue.issue_type);
        let status_str = format_status_label(&self.issue.status, false);
        content.push_str(&format!("[{priority}] [{type_str}] {status_str}\n"));

        // Description if present and enabled
        if self.show_description
            && let Some(desc) = &self.issue.description
        {
            content.push('\n');
            content.push_str(&sanitize_terminal_text(desc));
        }

        let status_style = self.theme.status_style(&self.issue.status);

        // Build content lines for Panel
        let content_lines: Vec<Vec<Segment<'static>>> = content
            .lines()
            .map(|line| vec![Segment::new(line.to_owned(), None)])
            .collect();

        Panel::new(content_lines)
            .title(sanitize_terminal_inline(&self.issue.id).into_owned())
            .border_style(status_style)
    }
}

/// Rich tree display for dependency relationships.
pub struct RichDependencyTree<'a> {
    root_issue: &'a Issue,
    dependencies: &'a [(String, String)], // (from_id, to_id)
    issues_by_id: &'a std::collections::HashMap<String, &'a Issue>,
    theme: &'a Theme,
}

impl<'a> RichDependencyTree<'a> {
    /// Create a new dependency tree.
    #[must_use]
    pub fn new(
        root_issue: &'a Issue,
        dependencies: &'a [(String, String)],
        issues_by_id: &'a std::collections::HashMap<String, &'a Issue>,
        theme: &'a Theme,
    ) -> Self {
        Self {
            root_issue,
            dependencies,
            issues_by_id,
            theme,
        }
    }

    /// Build a rich_rust Tree from the dependencies.
    #[must_use]
    pub fn build_tree(&self) -> Tree {
        let mut root = TreeNode::new(self.issue_label(self.root_issue, 40));

        // Find direct dependencies of root
        for (from_id, to_id) in self.dependencies {
            if from_id == &self.root_issue.id {
                if let Some(dep_issue) = self.issues_by_id.get(to_id) {
                    root = root.child(TreeNode::new(self.issue_label(dep_issue, 35)));
                } else {
                    root = root.child(TreeNode::new(self.unknown_issue_label(to_id)));
                }
            }
        }

        Tree::new(root).guide_style(self.theme.dimmed.clone())
    }

    fn issue_label(&self, issue: &Issue, title_width: usize) -> Text {
        let status_icon = format_status_icon(&issue.status);
        let title = truncate_title(&issue.title, title_width);
        let mut label = Text::new("");
        label.append_styled(status_icon, self.theme.status_style(&issue.status));
        label.append(" ");
        label.append_styled(
            sanitize_terminal_inline(&issue.id).as_ref(),
            self.theme.issue_id.clone(),
        );
        label.append_styled(" - ", self.theme.dimmed.clone());
        label.append_styled(
            sanitize_terminal_inline(&title).as_ref(),
            self.theme.issue_title.clone(),
        );
        label
    }

    fn unknown_issue_label(&self, issue_id: &str) -> Text {
        let mut label = Text::new("");
        label.append_styled("?", self.theme.warning.clone());
        label.append(" ");
        label.append_styled(
            sanitize_terminal_inline(issue_id).as_ref(),
            self.theme.issue_id.clone(),
        );
        label.append_styled(" - (unknown)", self.theme.dimmed.clone());
        label
    }
}

/// Format a status badge with rich styling.
#[must_use]
pub fn format_status_badge(status: &Status, theme: &Theme) -> Text {
    let icon = format_status_icon(status);
    let label = format_status_label(status, false).to_uppercase();
    let style = theme.status_style(status);

    let mut text = Text::new("");
    text.append_styled(&format!("{icon} {label}"), style);
    text
}

/// Format a count badge (e.g., "3 open, 2 blocked").
#[must_use]
pub fn format_count_badges(
    open: usize,
    in_progress: usize,
    blocked: usize,
    closed: usize,
    theme: &Theme,
) -> Text {
    let mut text = Text::new("");
    let mut needs_space = false;

    if open > 0 {
        text.append_styled(&format!("{open} open"), theme.status_open.clone());
        needs_space = true;
    }
    if in_progress > 0 {
        if needs_space {
            text.append(" ");
        }
        text.append_styled(
            &format!("{in_progress} in progress"),
            theme.status_in_progress.clone(),
        );
        needs_space = true;
    }
    if blocked > 0 {
        if needs_space {
            text.append(" ");
        }
        text.append_styled(&format!("{blocked} blocked"), theme.status_blocked.clone());
        needs_space = true;
    }
    if closed > 0 {
        if needs_space {
            text.append(" ");
        }
        text.append_styled(&format!("{closed} closed"), theme.status_closed.clone());
    }

    text
}

/// Build a rich progress indicator for issue completion.
#[must_use]
pub fn build_completion_bar(completed: usize, total: usize, _theme: &Theme) -> ProgressBar {
    let progress = if total > 0 {
        completed as f64 / total as f64
    } else {
        0.0
    };

    let mut bar = ProgressBar::new().width(20).bar_style(BarStyle::Block);
    bar.set_progress(progress);
    bar
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::model::{IssueType, Priority};
    use chrono::Utc;

    fn make_test_issue(id: &str, title: &str) -> Issue {
        Issue {
            id: id.to_string(),
            title: title.to_string(),
            description: Some("Test description".to_string()),
            issue_type: IssueType::Task,
            status: Status::Open,
            priority: Priority::MEDIUM,
            assignee: None,
            labels: vec![],
            created_at: Utc::now(),
            updated_at: Utc::now(),
            content_hash: None,
            design: None,
            acceptance_criteria: None,
            notes: None,
            owner: None,
            estimated_minutes: None,
            created_by: None,
            closed_at: None,
            close_reason: None,
            closed_by_session: None,
            due_at: None,
            defer_until: None,
            external_ref: None,
            source_system: None,
            source_repo: None,
            source_repo_path: None,
            deleted_at: None,
            deleted_by: None,
            delete_reason: None,
            original_type: None,
            compaction_level: None,
            compacted_at: None,
            compacted_at_commit: None,
            original_size: None,
            sender: None,
            ephemeral: false,
            pinned: false,
            is_template: false,
            dependencies: vec![],
            comments: vec![],
        }
    }

    #[test]
    fn test_rich_issue_table() {
        let issues = vec![
            make_test_issue("test-1", "First issue"),
            make_test_issue("test-2", "Second issue"),
        ];
        let theme = Theme::default();
        let table = RichIssueTable::new(&issues, &theme);
        let rendered = table.build_table().render_plain(120);

        assert!(rendered.contains("test-1"));
        assert!(rendered.contains("First issue"));
        assert!(rendered.contains("test-2"));
        assert!(rendered.contains("Second issue"));
        assert!(rendered.contains("task"));
    }

    #[test]
    fn test_rich_issue_panel() {
        let issue = make_test_issue("test-1", "Test issue");
        let theme = Theme::default();
        let panel = RichIssuePanel::new(&issue, &theme);
        let rendered = panel.build_panel().render_plain(80);

        assert!(rendered.contains("test-1"));
        assert!(rendered.contains("Test issue"));
        assert!(rendered.contains("Test description"));
        assert!(rendered.contains("task"));
        assert!(rendered.contains("open"));
    }

    #[test]
    fn rich_table_and_panel_sanitize_custom_status_and_type() {
        let mut issue = make_test_issue("test-1", "Test issue");
        issue.id = "test-1\x1b]52;c;bad\x07".to_string();
        issue.status = Status::Custom("state\x1b[2J".to_string());
        issue.issue_type = IssueType::Custom("kind\x07bell".to_string());
        let theme = Theme::default();

        let table = RichIssueTable::new(std::slice::from_ref(&issue), &theme);
        let table_output = table.build_table().render_plain(120);
        let panel_output = RichIssuePanel::new(&issue, &theme)
            .build_panel()
            .render_plain(80);

        for rendered in [table_output, panel_output] {
            assert!(!rendered.contains('\x1b'));
            assert!(!rendered.contains('\x07'));
            assert!(rendered.contains("\\u{1b}]52;c;bad\\u{7}"));
            assert!(rendered.contains("\\u{7}bell"));
        }
    }

    #[test]
    fn test_rich_dependency_tree_uses_theme_styles() {
        let root_issue = make_test_issue("test-root", "Root issue");
        let dep_issue = make_test_issue("test-child", "Child issue");
        let dependencies = vec![
            (root_issue.id.clone(), dep_issue.id.clone()),
            (root_issue.id.clone(), "test-missing".to_string()),
        ];
        let issues_by_id = std::collections::HashMap::from([(dep_issue.id.clone(), &dep_issue)]);
        let theme = Theme::default();
        let tree =
            RichDependencyTree::new(&root_issue, &dependencies, &issues_by_id, &theme).build_tree();

        assert_eq!(
            tree.render_plain(),
            "○ test-root - Root issue\n├── ○ test-child - Child issue\n└── ? test-missing - (unknown)\n"
        );

        let segments = tree.render();
        assert!(segments.iter().any(|segment| {
            segment.text.as_ref() == "test-root" && segment.style == Some(theme.issue_id.clone())
        }));
        assert!(segments.iter().any(|segment| {
            segment.text.as_ref() == "Root issue"
                && segment.style == Some(theme.issue_title.clone())
        }));
        assert!(segments.iter().any(|segment| {
            segment.text.as_ref() == "?" && segment.style == Some(theme.warning.clone())
        }));
    }

    #[test]
    fn test_format_status_badge() {
        let theme = Theme::default();
        let open = format_status_badge(&Status::Open, &theme);
        let blocked = format_status_badge(&Status::Blocked, &theme);

        assert!(open.plain().contains("OPEN"));
        assert!(!open.spans().is_empty());
        assert!(blocked.plain().contains("BLOCKED"));
        assert!(!blocked.spans().is_empty());
    }

    #[test]
    fn test_format_count_badges() {
        let theme = Theme::default();
        let badges = format_count_badges(5, 2, 1, 3, &theme);

        assert_eq!(badges.plain(), "5 open 2 in progress 1 blocked 3 closed");
        assert_eq!(badges.spans().len(), 4);
    }

    #[test]
    fn test_build_completion_bar() {
        let theme = Theme::default();
        let bar = build_completion_bar(7, 10, &theme);

        assert!((bar.progress() - 0.7).abs() < f64::EPSILON);
        assert!(!bar.is_finished());
        assert!(!bar.render_plain(20).is_empty());
    }

    #[test]
    fn test_table_without_type_column() {
        let issues = vec![make_test_issue("test-1", "Issue")];
        let theme = Theme::default();
        let table = RichIssueTable::new(&issues, &theme).show_type(false);
        let rendered = table.build_table().render_plain(80);

        assert!(rendered.contains("test-1"));
        assert!(rendered.contains("Issue"));
        assert!(!rendered.contains("Type"));
    }

    #[test]
    fn test_table_with_title_truncation() {
        let issues = vec![make_test_issue(
            "test-1",
            "A very long title that should be truncated",
        )];
        let theme = Theme::default();
        let table = RichIssueTable::new(&issues, &theme).max_title_width(20);
        let rendered = table.build_table().render_plain(80);

        assert!(rendered.contains("test-1"));
        assert!(rendered.contains("A very long title..."));
        assert!(!rendered.contains("A very long title that should be truncated"));
    }
}
