use egui::epaint::text::{FontInsert, FontPriority, InsertFontFamily};
use egui::style::Selection;
use egui::{Color32, CornerRadius, FontData, FontFamily, Stroke, Style, Theme, Vec2};
use serde::{Deserialize, Serialize};
use std::sync::LazyLock;
use toml::value::Datetime;
use toml::{Table, Value};
use tracing::debug;

const TOKENS_STR: &'static str = include_str!("../data/tokens.toml");
const THEME_STR: &'static str = include_str!("../data/theme.toml");

pub struct DesignTokens {
    merged_table: Table,
}

fn recursive_toml_merge(src: &mut Table, dst: &mut Table) {
    for (key, val) in src {
        match val {
            Value::Table(tbl) => {
                if dst.contains_key(key) {
                    recursive_toml_merge(tbl, dst.get_mut(key).unwrap().as_table_mut().unwrap());
                } else {
                    dst.insert(key.clone(), val.clone());
                }
            }
            _ => {
                dst.insert(key.clone(), val.clone());
            }
        }
    }
}

fn recursive_toml_resolve(into: &mut Table, root: &Table) {
    for (_, val) in into {
        match val {
            Value::String(s) => {
                if s.starts_with("@alias $") {
                    let key = s.strip_prefix("@alias $").unwrap();
                    *val = get_key(root, key).unwrap().clone();
                }
            }
            Value::Table(t) => {
                recursive_toml_resolve(t, root);
            }
            _ => {}
        }
    }
}

fn get_key<'a>(table: &'a Table, key: &str) -> Option<&'a Value> {
    let segments = key.split(".").enumerate();
    let segment_count = key.split(".").collect::<Vec<_>>().len();
    let mut table: &Table = table;

    for (num, segment) in segments {
        let last_segment = num == segment_count - 1;
        let val = table.get(segment)?;
        if last_segment {
            return Some(val);
        }
        match val {
            Value::Table(t) => {
                table = t;
                continue;
            }
            _ => {
                return None;
            }
        }
    }

    None
}

macro_rules! color {
    ($k:expr,$s:expr,$t:expr) => {
        $s.get_color($k, $t).unwrap()
    };
}

impl DesignTokens {
    pub fn new() -> Self {
        let mut base_tokens = TOKENS_STR.parse::<Table>().unwrap();
        let mut theme_tokens = THEME_STR.parse::<Table>().unwrap();

        recursive_toml_merge(&mut theme_tokens, &mut base_tokens);

        let base_memoized = base_tokens.clone();
        recursive_toml_resolve(&mut base_tokens, &base_memoized);

        Self {
            merged_table: base_tokens,
        }
    }
    pub fn get_value(&self, key: &str) -> Option<&Value> {
        let segments = key.split(".").enumerate();
        let segment_count = key.split(".").collect::<Vec<_>>().len();
        let mut table: &Table = &self.merged_table;

        for (num, segment) in segments {
            let last_segment = num == segment_count - 1;
            let val = table.get(segment)?;
            match val {
                Value::Table(t) => {
                    table = t;
                    continue;
                }
                _ => return if last_segment { Some(val) } else { None },
            }
        }

        None
    }

    pub fn get_string(&self, key: &str) -> Option<&String> {
        let val = self.get_value(key)?;
        match val {
            Value::String(s) => Some(s),
            _ => None,
        }
    }
    pub fn get_integer(&self, key: &str) -> Option<&i64> {
        let val = self.get_value(key)?;
        match val {
            Value::Integer(s) => Some(s),
            _ => None,
        }
    }
    pub fn get_float(&self, key: &str) -> Option<&f64> {
        let val = self.get_value(key)?;
        match val {
            Value::Float(s) => Some(s),
            _ => None,
        }
    }
    pub fn get_boolean(&self, key: &str) -> Option<&bool> {
        let val = self.get_value(key)?;
        match val {
            Value::Boolean(s) => Some(s),
            _ => None,
        }
    }
    pub fn get_datetime(&self, key: &str) -> Option<&Datetime> {
        let val = self.get_value(key)?;
        match val {
            Value::Datetime(s) => Some(s),
            _ => None,
        }
    }

    pub fn get_color32(&self, key: &str) -> Option<Color32> {
        let val = self.get_string(key)?;
        Color32::from_hex(val).ok()
    }
    pub fn get_color(&self, key: &str, theme: Theme) -> Option<Color32> {
        match theme {
            Theme::Dark => self.get_color32(&format!("{key}.dark")),
            Theme::Light => self.get_color32(&format!("{key}.light")),
        }
    }

    pub fn apply(&self, ctx: &egui::Context) {
        let mut fonts = egui::FontDefinitions::default();

        egui_phosphor::add_to_fonts(&mut fonts, egui_phosphor::Variant::Regular);

        fonts.font_data.insert(
            "Inter-SemiBold".into(),
            FontData::from_static(include_bytes!(
                "../data/Inter-4.1/extras/ttf/Inter-SemiBold.ttf"
            ))
            .into(),
        );
        fonts.font_data.insert(
            "phosphor-regular".into(),
            egui_phosphor::Variant::Regular.font_data().into(),
        );
        fonts.families.insert(
            egui::FontFamily::Name("semibold".into()),
            vec!["Inter-SemiBold".into(), "phosphor-regular".into()],
        );

        fonts.font_data.insert(
            "Inter-Regular".into(),
            FontData::from_static(include_bytes!(
                "../data/Inter-4.1/extras/ttf/Inter-Regular.ttf"
            ))
            .into(),
        );
        fonts.font_data.insert(
            "phosphor-regular".into(),
            egui_phosphor::Variant::Regular.font_data().into(),
        );
        fonts.families.insert(
            egui::FontFamily::Proportional,
            vec!["Inter-Regular".into(), "phosphor-regular".into()],
        );

        fonts.families.insert(
            egui::FontFamily::Name("icon-only".into()),
            vec!["phosphor-regular".into()],
        );

        ctx.set_fonts(fonts);

        self.set_color(ctx);
    }
    pub fn set_color(&self, ctx: &egui::Context) {
        ctx.style_mut_of(Theme::Light, |style| self._apply(style, Theme::Light));
        ctx.style_mut_of(Theme::Dark, |style| self._apply(style, Theme::Dark));
    }

    fn _apply(&self, style: &mut Style, theme: Theme) {
        style.visuals.widgets.noninteractive.fg_stroke.color = color!("colors.text", self, theme);
        style.visuals.widgets.inactive.fg_stroke.color = color!("colors.text", self, theme);

        style.visuals.panel_fill = color!("colors.bg", self, theme);
        style.visuals.widgets.noninteractive.bg_fill = color!("colors.bg", self, theme);
        style.visuals.widgets.noninteractive.bg_stroke.color = color!("colors.base.4", self, theme);

        style.visuals.faint_bg_color = color!("colors.faint_bg", self, theme);
        style.visuals.extreme_bg_color = color!("colors.extreme_bg", self, theme);

        style.visuals.warn_fg_color = color!("colors.warn", self, theme);
        style.visuals.error_fg_color = color!("colors.error", self, theme);

        // Accent
        style.visuals.hyperlink_color = color!("colors.accent.10", self, theme);
        style.visuals.text_cursor.stroke.color = color!("colors.accent.12", self, theme);
        style.visuals.selection = Selection {
            bg_fill: color!("colors.accent.5", self, theme),
            stroke: Stroke::new(1.0, color!("colors.accent.12", self, theme)),
        };

        style.visuals.window_fill = color!("colors.faint_bg", self, theme);

        style.visuals.window_stroke = Stroke::NONE;

        style.visuals.widgets.inactive.bg_fill = color!("colors.base.4", self, theme);
        style.visuals.widgets.inactive.fg_stroke.color = color!("colors.base.12", self, theme);
        style.visuals.widgets.inactive.bg_stroke.color = color!("colors.base.6", self, theme);
        style.visuals.widgets.inactive.weak_bg_fill = color!("colors.base.4", self, theme);
    }
}

pub const DESIGN_TOKENS: LazyLock<DesignTokens> = LazyLock::new(|| DesignTokens::new());
