use ratatui::style::{Color, Modifier, Style};
use serde::{Deserialize, Serialize};
use unicode_width::UnicodeWidthStr;

use crate::utils::style::PathStyleIfSome;

use super::tab_raw::{TabThemeCharsRaw, TabThemeColorRaw, TabThemeRaw};

/// Resolved styling for the tab bar: colors, decoration characters, and their inferred widths.
#[derive(Clone, Debug)]
pub struct TabTheme {
    pub styles: TabThemeColors,
    pub chars: TabThemeChars,
    pub inference: TabThemeCharsInference,
}

impl From<TabThemeRaw> for TabTheme {
    fn from(crude: TabThemeRaw) -> Self {
        let chars = TabThemeChars::from(crude.chars);
        Self {
            styles: TabThemeColors::from(crude.styles),
            inference: TabThemeCharsInference::from_chars(&chars),
            chars,
        }
    }
}

/// Resolved decoration characters/strings for the tab bar (dividers, prefixes, scroll markers).
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct TabThemeChars {
    pub divider: String,
    pub prefix_i: String,
    pub postfix_i: String,
    pub prefix_a: String,
    pub postfix_a: String,
    pub scroll_front_prefix: String,
    pub scroll_front_postfix: String,
    pub scroll_front_prestring: String,
    pub scroll_front_poststring: String,
    pub scroll_back_prefix: String,
    pub scroll_back_postfix: String,
    pub scroll_back_prestring: String,
    pub scroll_back_poststring: String,
    pub padding_prefix: char,
    pub padding_postfix: char,
    pub padding_fill: char,
}

impl From<TabThemeCharsRaw> for TabThemeChars {
    fn from(crude: TabThemeCharsRaw) -> Self {
        Self {
            divider: crude.divider.unwrap_or(" ".to_string()),
            prefix_i: crude.inactive_prefix.unwrap_or("[".to_string()),
            postfix_i: crude.inactive_postfix.unwrap_or("]".to_string()),
            prefix_a: crude.active_prefix.unwrap_or(" ".to_string()),
            postfix_a: crude.active_postfix.unwrap_or(" ".to_string()),
            scroll_front_prefix: crude.scroll_front_prefix.unwrap_or("".to_string()),
            scroll_front_postfix: crude.scroll_front_postfix.unwrap_or("".to_string()),
            scroll_front_prestring: crude.scroll_front_prestring.unwrap_or("«".to_string()),
            scroll_front_poststring: crude.scroll_front_poststring.unwrap_or(" ".to_string()),
            scroll_back_prefix: crude.scroll_back_prefix.unwrap_or("".to_string()),
            scroll_back_postfix: crude.scroll_back_postfix.unwrap_or("".to_string()),
            scroll_back_prestring: crude.scroll_back_prestring.unwrap_or(" ".to_string()),
            scroll_back_poststring: crude.scroll_back_poststring.unwrap_or("»".to_string()),
            padding_prefix: crude.padding_prefix.unwrap_or(' '),
            padding_postfix: crude.padding_postfix.unwrap_or(' '),
            padding_fill: crude.padding_fill.unwrap_or(' '),
        }
    }
}

/// Display widths derived from [`TabThemeChars`], precomputed to avoid recalculating per frame.
#[derive(Clone, Debug)]
pub struct TabThemeCharsInference {
    pub tab_divider_length: usize,
    pub _tab_prefix_i_length: usize,
    pub _tab_postfix_i_length: usize,
    pub _tab_prefix_a_length: usize,
    pub _tab_postfix_a_length: usize,
    pub scroll_front_static_length: usize,
    pub scroll_back_static_length: usize,
    pub active_tab_extra_width: usize,
    pub inactive_tab_extra_width: usize,
}

impl TabThemeCharsInference {
    fn from_chars(chars: &TabThemeChars) -> Self {
        Self {
            tab_divider_length: chars.divider.width(),
            _tab_prefix_i_length: chars.prefix_i.width(),
            _tab_prefix_a_length: chars.prefix_a.width(),
            _tab_postfix_i_length: chars.postfix_i.width(),
            _tab_postfix_a_length: chars.postfix_a.width(),
            scroll_front_static_length: chars.scroll_front_prefix.width()
                + chars.scroll_front_postfix.width()
                + chars.scroll_front_prestring.width()
                + chars.scroll_front_poststring.width(),
            scroll_back_static_length: chars.scroll_back_prefix.width()
                + chars.scroll_back_postfix.width()
                + chars.scroll_back_prestring.width()
                + chars.scroll_back_poststring.width(),
            active_tab_extra_width: chars.prefix_a.width() + chars.postfix_a.width(),
            inactive_tab_extra_width: chars.prefix_i.width() + chars.postfix_i.width(),
        }
    }

    /// Returns the total display width of the scroll-position tags shown when there are more
    /// tabs than fit on screen, given `num_tabs` total tabs.
    pub fn calc_scroll_tags_width(&self, num_tabs: usize) -> usize {
        let max_num_width = num_tabs.checked_ilog10().unwrap_or(0) as usize + 1;
        2 * max_num_width + self.scroll_front_static_length + self.scroll_back_static_length
    }
}

/// Resolved styles for every element of the tab bar, with per-element overrides already
/// patched onto their base active/inactive/divider/scroll styles.
#[derive(Clone, Debug)]
pub struct TabThemeColors {
    pub prefix_a: Style,
    pub postfix_a: Style,
    pub tab_a: Style,
    pub prefix_i: Style,
    pub postfix_i: Style,
    pub tab_i: Style,
    pub divider_ii: Style,
    pub divider_ia: Style,
    pub divider_ai: Style,
    pub scroll_front_prefix: Style,
    pub scroll_front_postfix: Style,
    pub scroll_front: Style,
    pub scroll_back_prefix: Style,
    pub scroll_back_postfix: Style,
    pub scroll_back: Style,
    pub padding_prefix: Style,
    pub padding_postfix: Style,
    pub padding_fill: Style,
}

impl From<TabThemeColorRaw> for TabThemeColors {
    fn from(crude: TabThemeColorRaw) -> Self {
        let tab_a = crude.active.map(|s| s.as_style()).unwrap_or(
            Style::new()
                .bg(Color::LightBlue)
                .fg(Color::Black)
                .add_modifier(Modifier::BOLD),
        );
        let prefix_a = tab_a.patch_optionally(crude.active_prefix.map(|s| s.as_style()));
        let postfix_a = prefix_a.patch_optionally(crude.active_postfix.map(|s| s.as_style()));

        let tab_i = crude.inactive.map(|s| s.as_style()).unwrap_or_default();
        let prefix_i = tab_i.patch_optionally(crude.inactive_prefix.map(|s| s.as_style()));
        let postfix_i = prefix_i.patch_optionally(crude.inactive_postfix.map(|s| s.as_style()));

        let divider_ii = crude.divider_ii.map(|s| s.as_style()).unwrap_or_default();
        let divider_ia = divider_ii.patch_optionally(crude.divider_ia.map(|s| s.as_style()));
        let divider_ai = divider_ia.patch_optionally(crude.divider_ai.map(|s| s.as_style()));

        let scroll_front = crude
            .scroll_front
            .map(|s| s.as_style())
            .unwrap_or(Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD));
        let scroll_front_prefix =
            scroll_front.patch_optionally(crude.scroll_front_prefix.map(|s| s.as_style()));
        let scroll_front_postfix =
            scroll_front_prefix.patch_optionally(crude.scroll_front_postfix.map(|s| s.as_style()));

        let scroll_back = crude
            .scroll_back
            .map(|s| s.as_style())
            .unwrap_or(Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD));
        let scroll_back_prefix =
            scroll_back.patch_optionally(crude.scroll_back_prefix.map(|s| s.as_style()));
        let scroll_back_postfix =
            scroll_back_prefix.patch_optionally(crude.scroll_back_postfix.map(|s| s.as_style()));

        let padding_fill = crude.padding_fill.map(|s| s.as_style()).unwrap_or_default();
        let padding_prefix =
            padding_fill.patch_optionally(crude.padding_prefix.map(|s| s.as_style()));
        let padding_postfix =
            padding_prefix.patch_optionally(crude.padding_postfix.map(|s| s.as_style()));
        Self {
            prefix_a,
            postfix_a,
            tab_a,
            prefix_i,
            postfix_i,
            tab_i,
            divider_ii,
            divider_ia,
            divider_ai,
            scroll_front_prefix,
            scroll_front_postfix,
            scroll_front,
            scroll_back_prefix,
            scroll_back_postfix,
            scroll_back,
            padding_prefix,
            padding_postfix,
            padding_fill,
        }
    }
}
