use {
    crate::{
        config::{
            Theme,
            context::Context,
            extractor::{Extractor, ExtractorError, bol, fltorint, opt, recover, s32, str, val},
            parser::{DataType, ParseResult, Parser, UnexpectedDataType},
            parsers::color::ColorParser,
        },
        toml::{
            toml_span::{DespanExt, Span, Spanned},
            toml_value::Value,
        },
    },
    indexmap::IndexMap,
    jay_config::theme::BarPosition,
    thiserror::Error,
};

pub struct ThemeParser<'a>(pub &'a Context<'a>);

#[derive(Debug, Error)]
pub enum ThemeParserError {
    #[error(transparent)]
    Expected(#[from] UnexpectedDataType),
    #[error(transparent)]
    Extractor(#[from] ExtractorError),
}

impl Parser for ThemeParser<'_> {
    type Value = Theme;
    type Error = ThemeParserError;
    const EXPECTED: &'static [DataType] = &[DataType::Table];

    fn parse_table(
        &mut self,
        span: Span,
        table: &IndexMap<Spanned<String>, Spanned<Value>>,
    ) -> ParseResult<Self> {
        let mut ext = Extractor::new(self.0, span, table);
        let (
            (
                attention_requested_bg_color,
                bg_color,
                bar_bg_color,
                bar_status_text_color,
                border_color,
                captured_focused_title_bg_color,
                captured_unfocused_title_bg_color,
                focused_inactive_title_bg_color,
                focused_inactive_title_text_color,
                focused_title_bg_color,
            ),
            (
                focused_title_text_color,
                separator_color,
                unfocused_title_bg_color,
                unfocused_title_text_color,
                highlight_color,
                border_width,
                title_height,
                bar_height,
                font,
                title_font,
            ),
            (
                bar_font,
                bar_position_val,
                bar_separator_width,
                gap,
                floating_titles,
                active_border_color,
            ),
        ) = ext.extract((
            (
                opt(val("attention-requested-bg-color")),
                opt(val("bg-color")),
                opt(val("bar-bg-color")),
                opt(val("bar-status-text-color")),
                opt(val("border-color")),
                opt(val("captured-focused-title-bg-color")),
                opt(val("captured-unfocused-title-bg-color")),
                opt(val("focused-inactive-title-bg-color")),
                opt(val("focused-inactive-title-text-color")),
                opt(val("focused-title-bg-color")),
            ),
            (
                opt(val("focused-title-text-color")),
                opt(val("separator-color")),
                opt(val("unfocused-title-bg-color")),
                opt(val("unfocused-title-text-color")),
                opt(val("highlight-color")),
                recover(opt(s32("border-width"))),
                recover(opt(s32("title-height"))),
                recover(opt(s32("bar-height"))),
                recover(opt(str("font"))),
                recover(opt(str("title-font"))),
            ),
            (
                recover(opt(str("bar-font"))),
                recover(opt(str("bar-position"))),
                recover(opt(s32("bar-separator-width"))),
                recover(opt(s32("gap"))),
                recover(opt(bol("floating-titles"))),
                opt(val("active-border-color")),
            ),
        ))?;
        let (title_gap, corner_radius) = ext.extract((
            recover(opt(s32("title-gap"))),
            recover(opt(fltorint("corner-radius"))),
        ))?;
        let (
            (
                tab_active_bg_color,
                tab_active_border_color,
                tab_inactive_bg_color,
                tab_inactive_border_color,
                tab_active_text_color,
                tab_inactive_text_color,
                tab_bar_bg_color,
                tab_attention_bg_color,
                tab_bar_height,
                tab_bar_padding,
            ),
            (
                tab_bar_radius,
                tab_bar_border_width,
                tab_bar_text_padding,
                tab_bar_gap,
                tab_title_align_val,
            ),
        ) = ext.extract((
            (
                opt(val("tab-active-bg-color")),
                opt(val("tab-active-border-color")),
                opt(val("tab-inactive-bg-color")),
                opt(val("tab-inactive-border-color")),
                opt(val("tab-active-text-color")),
                opt(val("tab-inactive-text-color")),
                opt(val("tab-bar-bg-color")),
                opt(val("tab-attention-bg-color")),
                recover(opt(s32("tab-bar-height"))),
                recover(opt(s32("tab-bar-padding"))),
            ),
            (
                recover(opt(s32("tab-bar-radius"))),
                recover(opt(s32("tab-bar-border-width"))),
                recover(opt(s32("tab-bar-text-padding"))),
                recover(opt(s32("tab-bar-gap"))),
                recover(opt(str("tab-title-align"))),
            ),
        ))?;
        macro_rules! color {
            ($e:expr) => {
                match $e {
                    None => None,
                    Some(v) => match v.parse(&mut ColorParser) {
                        Ok(v) => Some(v),
                        Err(e) => {
                            log::warn!("Could not parse a color: {}", self.0.error(e));
                            None
                        }
                    },
                }
            };
        }
        let bar_position =
            bar_position_val.and_then(|value| match value.value.to_lowercase().as_str() {
                "top" => Some(BarPosition::Top),
                "bottom" => Some(BarPosition::Bottom),
                _ => {
                    log::warn!(
                        "Unknown bar position '{}': {}",
                        value.value,
                        self.0.error3(value.span)
                    );
                    None
                }
            });
        Ok(Theme {
            attention_requested_bg_color: color!(attention_requested_bg_color),
            bg_color: color!(bg_color),
            bar_bg_color: color!(bar_bg_color),
            bar_status_text_color: color!(bar_status_text_color),
            border_color: color!(border_color),
            active_border_color: color!(active_border_color),
            captured_focused_title_bg_color: color!(captured_focused_title_bg_color),
            captured_unfocused_title_bg_color: color!(captured_unfocused_title_bg_color),
            focused_inactive_title_bg_color: color!(focused_inactive_title_bg_color),
            focused_inactive_title_text_color: color!(focused_inactive_title_text_color),
            focused_title_bg_color: color!(focused_title_bg_color),
            focused_title_text_color: color!(focused_title_text_color),
            separator_color: color!(separator_color),
            unfocused_title_bg_color: color!(unfocused_title_bg_color),
            unfocused_title_text_color: color!(unfocused_title_text_color),
            highlight_color: color!(highlight_color),
            border_width: border_width.despan(),
            title_height: title_height.despan(),
            bar_height: bar_height.despan(),
            font: font.map(|f| f.value.to_string()),
            title_font: title_font.map(|f| f.value.to_string()),
            bar_font: bar_font.map(|f| f.value.to_string()),
            bar_position,
            bar_separator_width: bar_separator_width.despan(),
            gap: gap.despan(),
            floating_titles: floating_titles.despan(),
            title_gap: title_gap.despan(),
            corner_radius: corner_radius.map(|v| v.value as f32),
            tab_active_bg_color: color!(tab_active_bg_color),
            tab_active_border_color: color!(tab_active_border_color),
            tab_inactive_bg_color: color!(tab_inactive_bg_color),
            tab_inactive_border_color: color!(tab_inactive_border_color),
            tab_active_text_color: color!(tab_active_text_color),
            tab_inactive_text_color: color!(tab_inactive_text_color),
            tab_bar_bg_color: color!(tab_bar_bg_color),
            tab_attention_bg_color: color!(tab_attention_bg_color),
            tab_bar_height: tab_bar_height.despan(),
            tab_bar_padding: tab_bar_padding.despan(),
            tab_bar_radius: tab_bar_radius.despan(),
            tab_bar_border_width: tab_bar_border_width.despan(),
            tab_bar_text_padding: tab_bar_text_padding.despan(),
            tab_bar_gap: tab_bar_gap.despan(),
            tab_title_align: tab_title_align_val.map(|v| v.value.to_string()),
        })
    }
}
