//! Módulo de tema central do TUI (T-01, T-02): tokens semânticos de cor,
//! detecção de capacidade do terminal e helpers de estilo/layout reutilizáveis.
//! Puro, sem dependências de outros módulos do crate — testável com `TestBackend`.

#![allow(dead_code)]

use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Widget};
use ratatui::Frame;

// ---------------------------------------------------------------------------
// Profundidade de cor
// ---------------------------------------------------------------------------

/// Capacidade de cor detectada no terminal.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ColorDepth {
    /// 24 bits (RGB completo) — terminais modernos.
    TrueColor,
    /// 256 cores indexadas (xterm-256color).
    Ansi256,
    /// 16 cores ANSI básicas — fallback seguro.
    Ansi16,
}

/// Detecta a profundidade de cor a partir das variáveis de ambiente comuns.
///
/// Regras (em ordem de prioridade):
/// 1. `COLORTERM` contendo "truecolor" ou "24bit" (case-insensitive) → [`ColorDepth::TrueColor`].
/// 2. `TERM` contendo "256color" → [`ColorDepth::Ansi256`].
/// 3. Qualquer outra coisa (incluindo `TERM=dumb` e `None`) → [`ColorDepth::Ansi16`].
pub fn detect(colorterm: Option<&str>, term: Option<&str>) -> ColorDepth {
    if let Some(ct) = colorterm {
        let lower = ct.to_lowercase();
        if lower.contains("truecolor") || lower.contains("24bit") {
            return ColorDepth::TrueColor;
        }
    }
    if let Some(t) = term {
        if t.contains("256color") {
            return ColorDepth::Ansi256;
        }
    }
    ColorDepth::Ansi16
}

// ---------------------------------------------------------------------------
// Tokens semânticos de cor (estrutura Theme)
// ---------------------------------------------------------------------------

/// Conjunto de tokens semânticos de cor para o TUI.
///
/// Obtido via [`theme`]; não construa diretamente — use os construtores fornecidos.
#[derive(Clone, Copy, Debug)]
pub struct Theme {
    /// Cor de destaque principal (pêssego/laranja, evocando o OpenCode).
    pub primary: Color,
    /// Texto sobre fundo `primary` (contraste).
    pub on_primary: Color,
    /// Texto de alta ênfase (branco/cinza claro).
    pub text_strong: Color,
    /// Texto de baixa ênfase / legendas (cinza médio).
    pub text_muted: Color,
    /// Feedback de sucesso (verde).
    pub success: Color,
    /// Feedback de aviso (amarelo).
    pub warning: Color,
    /// Feedback de erro (vermelho).
    pub error: Color,
    /// Sobreposição escura para fundo de modais.
    pub overlay_dim: Color,
    /// Cabeçalho de seção (mesmo tom do `primary`).
    pub section_header: Color,
}

/// Retorna o tema calibrado para a profundidade de cor informada.
pub fn theme(depth: ColorDepth) -> Theme {
    match depth {
        ColorDepth::TrueColor => Theme {
            primary: Color::Rgb(235, 160, 110), // pêssego/laranja — acento OpenCode
            on_primary: Color::Rgb(30, 20, 10),
            text_strong: Color::Rgb(230, 230, 230),
            text_muted: Color::Rgb(140, 140, 140),
            success: Color::Rgb(100, 200, 120),
            warning: Color::Rgb(240, 200, 80),
            error: Color::Rgb(220, 80, 80),
            overlay_dim: Color::Rgb(15, 15, 20),
            section_header: Color::Rgb(235, 160, 110),
        },
        ColorDepth::Ansi256 => Theme {
            primary: Color::Indexed(216),     // #ffaf87 — pêssego
            on_primary: Color::Indexed(232),  // quase preto
            text_strong: Color::Indexed(255), // branco brilhante
            text_muted: Color::Indexed(245),  // cinza médio
            success: Color::Indexed(114),     // verde suave
            warning: Color::Indexed(220),     // amarelo
            error: Color::Indexed(160),       // vermelho
            overlay_dim: Color::Indexed(233), // cinza muito escuro
            section_header: Color::Indexed(216),
        },
        ColorDepth::Ansi16 => Theme {
            primary: Color::Yellow,
            on_primary: Color::Black,
            text_strong: Color::White,
            text_muted: Color::DarkGray,
            success: Color::Green,
            warning: Color::Yellow,
            error: Color::Red,
            overlay_dim: Color::Black,
            section_header: Color::Yellow,
        },
    }
}

// ---------------------------------------------------------------------------
// Helpers de estilo e layout em `impl Theme`
// ---------------------------------------------------------------------------

impl Theme {
    /// Estilo de item selecionado (barra full-width): fundo no acento, texto contrastante, negrito.
    pub fn selection_style(&self) -> Style {
        Style::default()
            .bg(self.primary)
            .fg(self.on_primary)
            .add_modifier(Modifier::BOLD)
    }

    /// Linha de cabeçalho de seção: rótulo no acento, negrito.
    pub fn section_header(&self, label: &str) -> Line<'static> {
        Line::from(vec![Span::styled(
            label.to_owned(),
            Style::default()
                .fg(self.section_header)
                .add_modifier(Modifier::BOLD),
        )])
    }

    /// Linha com par "rótulo" (muted) + " " + "valor" (strong).
    pub fn label_value(&self, label: &str, value: &str) -> Line<'static> {
        Line::from(vec![
            Span::styled(label.to_owned(), Style::default().fg(self.text_muted)),
            Span::raw(" "),
            Span::styled(value.to_owned(), Style::default().fg(self.text_strong)),
        ])
    }

    /// Linha de rodapé: hint à esquerda (muted) + separador + rota à direita (strong).
    pub fn footer_line(&self, hint: &str, route: &str) -> Line<'static> {
        Line::from(vec![
            Span::styled(hint.to_owned(), Style::default().fg(self.text_muted)),
            Span::raw("  ·  "),
            Span::styled(route.to_owned(), Style::default().fg(self.text_strong)),
        ])
    }

    /// Retorna um `Rect` centralizado dentro de `area`, com dimensões calculadas
    /// em porcentagem (`pct_x`, `pct_y`). Valores são clampados para não ultrapassar `area`.
    pub fn centered_rect(&self, pct_x: u16, pct_y: u16, area: Rect) -> Rect {
        // Clamp: garante que o percentual nunca exceda 100.
        let pct_x = pct_x.min(100);
        let pct_y = pct_y.min(100);

        let margem_v = (100 - pct_y) / 2;
        let margem_h = (100 - pct_x) / 2;

        // Divisão vertical: margem topo / conteúdo / margem base.
        let vertical = Layout::default()
            .direction(Direction::Vertical)
            .constraints([
                Constraint::Percentage(margem_v),
                Constraint::Percentage(pct_y),
                Constraint::Percentage(margem_v),
            ])
            .split(area);

        // Divisão horizontal sobre a fatia central.
        let horizontal = Layout::default()
            .direction(Direction::Horizontal)
            .constraints([
                Constraint::Percentage(margem_h),
                Constraint::Percentage(pct_x),
                Constraint::Percentage(margem_h),
            ])
            .split(vertical[1]);

        horizontal[1]
    }

    /// Preenche `area` com um bloco de fundo `overlay_dim`, escurecendo o conteúdo atrás de modais.
    pub fn overlay_dim(&self, frame: &mut Frame, area: Rect) {
        let block = Block::default().style(Style::default().bg(self.overlay_dim));
        block.render(area, frame.buffer_mut());
    }

    /// Banner de marca para o texto "SDD".
    ///
    /// - `width >= 48`: banner ASCII multi-linha (3–5 linhas) estilizado no acento.
    /// - `width < 48`: linha compacta com nome e subtítulo resumido.
    ///
    /// Nunca causa panic; não depende de estado externo.
    pub fn figlet_banner(&self, width: u16) -> Vec<Line<'static>> {
        let accent = Style::default()
            .fg(self.primary)
            .add_modifier(Modifier::BOLD);
        let muted = Style::default().fg(self.text_muted);

        if width >= 48 {
            // Banner ASCII de blocos — desenhado à mão para "SDD".
            // Largura máxima da arte: ~42 colunas (cabe em 48+).
            vec![
                Line::from(Span::styled(" ███████╗██████╗ ██████╗ ", accent)),
                Line::from(Span::styled(" ██╔════╝██╔══██╗██╔══██╗", accent)),
                Line::from(Span::styled(" ███████╗██║  ██║██║  ██║", accent)),
                Line::from(Span::styled(" ╚════██║██║  ██║██║  ██║", accent)),
                Line::from(vec![
                    Span::styled(" ███████║██████╔╝██████╔╝", accent),
                    Span::styled("  Spec-Driven Development", muted),
                ]),
            ]
        } else {
            // Versão compacta: uma única linha.
            vec![Line::from(vec![
                Span::styled(" SDD", accent),
                Span::styled(" · Spec-Driven Development ", muted),
            ])]
        }
    }
}

// ---------------------------------------------------------------------------
// Gradiente de cor
// ---------------------------------------------------------------------------

/// Interpola linearmente em RGB entre os `stops` fornecidos, retornando `steps` cores.
///
/// # Regras de degradação
/// - Se qualquer `stop` não for `Color::Rgb` (ambiente sem truecolor), ou se
///   `stops.len() < 2`, ou se `steps == 0`, retorna um vetor sólido com o
///   primeiro stop (ou `Color::Indexed(216)` como fallback) repetido `steps.max(1)` vezes.
///
/// # Parâmetros
/// - `stops`: fatia de cores RGB de ancoragem (mínimo 2).
/// - `steps`: número de cores na saída.
///
/// # Exemplo
/// ```
/// use ratatui::style::Color;
/// use sdd_layer::tui::theme::gradient;
/// let cores = gradient(&[Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)], 5);
/// assert_eq!(cores.len(), 5);
/// ```
pub fn gradient(stops: &[Color], steps: usize) -> Vec<Color> {
    // Valida pré-condições: todos os stops devem ser Rgb, mínimo 2 stops, steps > 0.
    let fallback_color = stops.first().copied().unwrap_or(Color::Indexed(216));
    let solid_count = steps.max(1);

    if steps == 0 || stops.len() < 2 {
        return vec![fallback_color; solid_count];
    }

    // Verifica que todos os stops são Color::Rgb; caso contrário, colapsa para sólido.
    let rgb_stops: Vec<(u8, u8, u8)> = match stops
        .iter()
        .map(|c| match c {
            Color::Rgb(r, g, b) => Some((*r, *g, *b)),
            _ => None,
        })
        .collect::<Option<Vec<_>>>()
    {
        Some(v) => v,
        None => return vec![fallback_color; solid_count],
    };

    // Interpolação linear distribuída entre os segmentos de stops.
    let n_segments = rgb_stops.len() - 1;
    let mut result = Vec::with_capacity(steps);

    for i in 0..steps {
        // Posição normalizada [0.0, 1.0] ao longo de toda a faixa.
        let t = if steps == 1 {
            0.0_f32
        } else {
            i as f32 / (steps - 1) as f32
        };

        // Índice do segmento e posição local dentro dele.
        let seg_f = t * n_segments as f32;
        let seg = (seg_f as usize).min(n_segments - 1);
        let local_t = seg_f - seg as f32;

        let (r0, g0, b0) = rgb_stops[seg];
        let (r1, g1, b1) = rgb_stops[seg + 1];

        let r = (r0 as f32 + (r1 as f32 - r0 as f32) * local_t).round() as u8;
        let g = (g0 as f32 + (g1 as f32 - g0 as f32) * local_t).round() as u8;
        let b = (b0 as f32 + (b1 as f32 - b0 as f32) * local_t).round() as u8;

        result.push(Color::Rgb(r, g, b));
    }

    result
}

// ---------------------------------------------------------------------------
// Testes
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use ratatui::backend::TestBackend;
    use ratatui::Terminal;

    // --- detect ---

    #[test]
    fn detect_truecolor_via_colorterm() {
        assert_eq!(
            detect(Some("truecolor"), Some("xterm-256color")),
            ColorDepth::TrueColor
        );
    }

    #[test]
    fn detect_truecolor_via_24bit() {
        assert_eq!(detect(Some("24bit"), Some("xterm")), ColorDepth::TrueColor);
    }

    #[test]
    fn detect_truecolor_case_insensitive() {
        assert_eq!(detect(Some("TRUECOLOR"), None), ColorDepth::TrueColor);
        assert_eq!(detect(Some("24BIT"), Some("dumb")), ColorDepth::TrueColor);
    }

    #[test]
    fn detect_ansi256_via_term() {
        assert_eq!(detect(None, Some("xterm-256color")), ColorDepth::Ansi256);
        assert_eq!(
            detect(Some(""), Some("screen-256color")),
            ColorDepth::Ansi256
        );
    }

    #[test]
    fn detect_ansi16_on_dumb_term() {
        assert_eq!(detect(None, Some("dumb")), ColorDepth::Ansi16);
    }

    #[test]
    fn detect_ansi16_on_none() {
        assert_eq!(detect(None, None), ColorDepth::Ansi16);
    }

    #[test]
    fn detect_ansi16_on_plain_xterm() {
        assert_eq!(detect(None, Some("xterm")), ColorDepth::Ansi16);
    }

    // --- theme() ---

    #[test]
    fn theme_truecolor_primary_is_rgb() {
        let t = theme(ColorDepth::TrueColor);
        assert_eq!(t.primary, Color::Rgb(235, 160, 110));
    }

    #[test]
    fn theme_ansi256_primary_is_indexed() {
        let t = theme(ColorDepth::Ansi256);
        assert_eq!(t.primary, Color::Indexed(216));
    }

    #[test]
    fn theme_ansi16_primary_is_yellow() {
        let t = theme(ColorDepth::Ansi16);
        assert_eq!(t.primary, Color::Yellow);
    }

    #[test]
    fn theme_all_depths_produce_theme() {
        // Apenas garante que nenhum depth causa panic.
        let _ = theme(ColorDepth::TrueColor);
        let _ = theme(ColorDepth::Ansi256);
        let _ = theme(ColorDepth::Ansi16);
    }

    // --- selection_style ---

    #[test]
    fn selection_style_bg_is_primary() {
        let t = theme(ColorDepth::TrueColor);
        let style = t.selection_style();
        assert_eq!(style.bg, Some(t.primary));
    }

    #[test]
    fn selection_style_is_bold() {
        let t = theme(ColorDepth::Ansi16);
        let style = t.selection_style();
        assert!(style.add_modifier.contains(Modifier::BOLD));
    }

    // --- centered_rect ---

    fn dummy_area() -> Rect {
        Rect::new(0, 0, 100, 40)
    }

    #[test]
    fn centered_rect_within_area_standard() {
        let t = theme(ColorDepth::Ansi16);
        let area = dummy_area();
        let r = t.centered_rect(60, 50, area);

        assert!(r.x >= area.x);
        assert!(r.y >= area.y);
        assert!(r.x + r.width <= area.x + area.width);
        assert!(r.y + r.height <= area.y + area.height);
    }

    #[test]
    fn centered_rect_100_percent_covers_area() {
        let t = theme(ColorDepth::Ansi16);
        let area = dummy_area();
        let r = t.centered_rect(100, 100, area);

        assert!(r.x + r.width <= area.x + area.width);
        assert!(r.y + r.height <= area.y + area.height);
    }

    #[test]
    fn centered_rect_small_area() {
        let t = theme(ColorDepth::Ansi16);
        let area = Rect::new(5, 3, 20, 10);
        let r = t.centered_rect(80, 80, area);

        assert!(r.x >= area.x);
        assert!(r.y >= area.y);
        assert!(r.x + r.width <= area.x + area.width);
        assert!(r.y + r.height <= area.y + area.height);
    }

    // --- figlet_banner ---

    #[test]
    fn figlet_banner_wide_returns_multi_line() {
        let t = theme(ColorDepth::TrueColor);
        let lines = t.figlet_banner(80);
        assert!(lines.len() > 1, "width=80 deve retornar banner multi-linha");
    }

    #[test]
    fn figlet_banner_narrow_returns_single_line() {
        let t = theme(ColorDepth::TrueColor);
        let lines = t.figlet_banner(30);
        assert_eq!(
            lines.len(),
            1,
            "width=30 deve retornar linha compacta única"
        );
    }

    #[test]
    fn figlet_banner_wide_is_not_empty() {
        let t = theme(ColorDepth::Ansi256);
        let lines = t.figlet_banner(80);
        assert!(!lines.is_empty());
    }

    #[test]
    fn figlet_banner_narrow_is_not_empty() {
        let t = theme(ColorDepth::Ansi16);
        let lines = t.figlet_banner(30);
        assert!(!lines.is_empty());
    }

    #[test]
    fn figlet_banner_boundary_48_is_wide() {
        let t = theme(ColorDepth::TrueColor);
        let lines = t.figlet_banner(48);
        assert!(lines.len() > 1);
    }

    #[test]
    fn figlet_banner_boundary_47_is_compact() {
        let t = theme(ColorDepth::TrueColor);
        let lines = t.figlet_banner(47);
        assert_eq!(lines.len(), 1);
    }

    // --- overlay_dim (smoke test com TestBackend) ---

    #[test]
    fn overlay_dim_smoke_test() {
        let backend = TestBackend::new(40, 10);
        let mut terminal = Terminal::new(backend).unwrap();
        let t = theme(ColorDepth::TrueColor);

        terminal
            .draw(|f| {
                let area = Rect::new(0, 0, 20, 5);
                t.overlay_dim(f, area);
            })
            .unwrap();
        // Se chegou aqui sem panic, o helper funciona corretamente.
    }

    // --- section_header e label_value (smoke tests) ---

    #[test]
    fn section_header_contains_label() {
        let t = theme(ColorDepth::TrueColor);
        let line = t.section_header("Etapas");
        let text: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
        assert!(text.contains("Etapas"));
    }

    #[test]
    fn label_value_contains_both() {
        let t = theme(ColorDepth::Ansi256);
        let line = t.label_value("Arquivo:", "03-techspec.md");
        let text: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
        assert!(text.contains("Arquivo:"));
        assert!(text.contains("03-techspec.md"));
    }

    #[test]
    fn footer_line_contains_hint_and_route() {
        let t = theme(ColorDepth::Ansi16);
        let line = t.footer_line("↑↓ navegar", "/techspec");
        let text: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
        assert!(text.contains("↑↓ navegar"));
        assert!(text.contains("/techspec"));
    }

    // --- gradient ---

    #[test]
    fn gradient_retorna_quantidade_correta_de_cores() {
        let cores = gradient(&[Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)], 8);
        assert_eq!(
            cores.len(),
            8,
            "gradient deve retornar exatamente `steps` cores"
        );
    }

    #[test]
    fn gradient_single_step_retorna_um_elemento() {
        let cores = gradient(&[Color::Rgb(100, 100, 100), Color::Rgb(200, 200, 200)], 1);
        assert_eq!(cores.len(), 1);
    }

    #[test]
    fn gradient_com_color_indexed_colapsa_para_solido() {
        // Stop não-Rgb deve colapsar para sólido.
        let stop = Color::Indexed(216);
        let cores = gradient(&[stop, Color::Indexed(220)], 5);
        assert_eq!(
            cores.len(),
            5,
            "deve retornar `steps.max(1)` cores mesmo no fallback"
        );
        // Todas as cores devem ser o fallback (primeiro stop).
        assert!(cores.iter().all(|&c| c == stop));
    }

    #[test]
    fn gradient_com_stops_insuficientes_colapsa_para_solido() {
        let stop = Color::Rgb(10, 20, 30);
        let cores = gradient(&[stop], 4);
        assert_eq!(cores.len(), 4);
        assert!(cores.iter().all(|&c| c == stop));
    }

    #[test]
    fn gradient_com_steps_zero_retorna_um_elemento() {
        let stop = Color::Rgb(50, 60, 70);
        let cores = gradient(&[stop, Color::Rgb(100, 120, 140)], 0);
        // steps == 0 → colapsa; retorna vec![fallback; 1].
        assert_eq!(cores.len(), 1);
    }

    #[test]
    fn gradient_interpola_extremos_corretamente() {
        let inicio = Color::Rgb(0, 0, 0);
        let fim = Color::Rgb(100, 200, 50);
        let cores = gradient(&[inicio, fim], 2);
        assert_eq!(cores[0], inicio);
        assert_eq!(cores[1], fim);
    }

    #[test]
    fn gradient_tres_stops_produz_steps_corretos() {
        let cores = gradient(
            &[
                Color::Rgb(0, 0, 0),
                Color::Rgb(128, 128, 128),
                Color::Rgb(255, 255, 255),
            ],
            6,
        );
        assert_eq!(cores.len(), 6);
        // Primeiro deve ser preto, último deve ser branco.
        assert_eq!(cores[0], Color::Rgb(0, 0, 0));
        assert_eq!(cores[5], Color::Rgb(255, 255, 255));
    }
}
