travelagent 1.11.0

Agent-first TUI code review tool
use ratatui::style::{Color, Modifier, Style};
use travelagent_core::risk::RiskBand;
use travelagent_core::style::{ColorHint, StyleHint};

use crate::theme::Theme;

/// Convert a `core::style::ColorHint` to a ratatui `Color`.
#[must_use]
pub fn color_hint_to_ratatui(hint: ColorHint) -> Color {
    Color::Rgb(hint.r, hint.g, hint.b)
}

/// Convert a `core::style::StyleHint` to a ratatui `Style`.
/// Lossless — `StyleHint` carries every attribute ratatui cares about for
/// the syntax-highlighting path.
#[must_use]
pub fn style_hint_to_ratatui(hint: StyleHint) -> Style {
    let mut s = Style::default();
    if let Some(fg) = hint.fg {
        s = s.fg(color_hint_to_ratatui(fg));
    }
    if let Some(bg) = hint.bg {
        s = s.bg(color_hint_to_ratatui(bg));
    }
    if hint.bold {
        s = s.add_modifier(Modifier::BOLD);
    }
    if hint.italic {
        s = s.add_modifier(Modifier::ITALIC);
    }
    if hint.underlined {
        s = s.add_modifier(Modifier::UNDERLINED);
    }
    s
}

pub fn header_style(theme: &Theme) -> Style {
    Style::default()
        .bg(theme.panel_bg)
        .fg(theme.fg_primary)
        .add_modifier(Modifier::BOLD)
}

pub fn selected_style(theme: &Theme) -> Style {
    Style::default().bg(theme.bg_highlight).fg(theme.fg_primary)
}

pub fn dim_style(theme: &Theme) -> Style {
    Style::default().fg(theme.fg_dim)
}

/// Style for the line-number gutter column in diff views. Uses a tinted
/// background so leading whitespace in the adjacent content is visually
/// obvious.
pub fn gutter_style(theme: &Theme) -> Style {
    Style::default().fg(theme.fg_dim).bg(theme.gutter_bg())
}

/// Gutter variant for expanded-context rows (dimmer fg).
pub fn gutter_expanded_style(theme: &Theme) -> Style {
    Style::default()
        .fg(theme.expanded_context_fg)
        .bg(theme.gutter_bg())
}

pub fn diff_add_style(theme: &Theme) -> Style {
    Style::default().fg(theme.diff_add).bg(theme.diff_add_bg)
}

pub fn diff_del_style(theme: &Theme) -> Style {
    Style::default().fg(theme.diff_del).bg(theme.diff_del_bg)
}

pub fn diff_context_style(theme: &Theme) -> Style {
    Style::default().fg(theme.diff_context)
}

pub fn expanded_context_style(theme: &Theme) -> Style {
    Style::default().fg(theme.expanded_context_fg)
}

pub fn diff_hunk_header_style(theme: &Theme) -> Style {
    Style::default()
        .fg(theme.diff_hunk_header)
        .add_modifier(Modifier::BOLD)
}

pub fn file_header_style(theme: &Theme) -> Style {
    Style::default()
        .fg(theme.fg_primary)
        .add_modifier(Modifier::BOLD)
}

fn risk_band_color(theme: &Theme, band: RiskBand) -> Color {
    match band {
        RiskBand::Low => theme.risk_low,
        RiskBand::Medium => theme.risk_medium,
        RiskBand::High => theme.risk_high,
    }
}

/// Tint a file header with the per-file risk band. `band = None` means the
/// risk-border feature is disabled (monochrome opt-out) and the caller should
/// keep the existing [`file_header_style`].
pub fn file_header_style_with_risk(theme: &Theme, band: Option<RiskBand>) -> Style {
    match band {
        Some(b) => Style::default()
            .fg(risk_band_color(theme, b))
            .add_modifier(Modifier::BOLD),
        None => file_header_style(theme),
    }
}

/// Same as [`diff_hunk_header_style`] but tinted by hunk-level risk. `None`
/// means the feature is disabled; falls back to the default hunk header hue.
pub fn diff_hunk_header_style_with_risk(theme: &Theme, band: Option<RiskBand>) -> Style {
    match band {
        Some(b) => Style::default()
            .fg(risk_band_color(theme, b))
            .add_modifier(Modifier::BOLD),
        None => diff_hunk_header_style(theme),
    }
}

pub fn reviewed_style(theme: &Theme) -> Style {
    Style::default().fg(theme.reviewed)
}

pub fn pending_style(theme: &Theme) -> Style {
    Style::default().fg(theme.pending)
}

pub fn border_style(theme: &Theme, focused: bool) -> Style {
    if focused {
        Style::default().fg(theme.border_focused)
    } else {
        Style::default().fg(theme.border_unfocused)
    }
}

pub fn panel_style(theme: &Theme) -> Style {
    Style::default().bg(theme.panel_bg).fg(theme.fg_primary)
}

pub fn popup_style(theme: &Theme) -> Style {
    panel_style(theme)
}

pub fn status_bar_style(theme: &Theme) -> Style {
    Style::default()
        .bg(theme.status_bar_bg)
        .fg(theme.fg_primary)
}

pub fn mode_style(theme: &Theme) -> Style {
    Style::default()
        .fg(theme.mode_fg)
        .bg(theme.mode_bg)
        .add_modifier(Modifier::BOLD)
}

pub fn file_status_style(theme: &Theme, status: char) -> Style {
    let color = match status {
        'A' => theme.file_added,
        'M' => theme.file_modified,
        'D' => theme.file_deleted,
        'R' => theme.file_renamed,
        _ => theme.fg_secondary,
    };
    Style::default().fg(color)
}

pub fn current_line_indicator_style(theme: &Theme) -> Style {
    Style::default().fg(theme.border_focused)
}

pub fn hash_style(theme: &Theme) -> Style {
    Style::default().fg(theme.cursor_color)
}

pub fn branch_style(theme: &Theme) -> Style {
    Style::default().fg(theme.branch_name)
}

pub fn dir_icon_style(theme: &Theme) -> Style {
    Style::default().fg(theme.diff_hunk_header)
}

pub fn comment_type_style(_theme: &Theme, color: Color) -> Style {
    Style::default().fg(color).add_modifier(Modifier::BOLD)
}

pub fn comment_border_style(_theme: &Theme, color: Color) -> Style {
    Style::default().fg(color)
}

pub fn visual_selection_style(theme: &Theme) -> Style {
    Style::default().bg(theme.bg_highlight)
}

pub fn help_indicator_style(theme: &Theme) -> Style {
    Style::default().fg(theme.help_indicator).bg(theme.panel_bg)
}