use std::collections::HashMap;

use ::serde::{Deserialize, Serialize};
use anyhow::{Context, Result, bail};
use cava::{CavaTheme, CavaThemeFile};
use itertools::Itertools;
use level_styles::{LevelStyles, LevelStylesFile};
use properties::{SongFormat, SongFormatFile};
use ratatui::style::{Color, Style};
use rmpc_mpd::commands::metadata_tag::MetadataTag;
use rmpc_shared::paths::utils::{env_var_expand, tilde_expand};

use self::{
    header::{HeaderConfig, HeaderConfigFile},
    lyrics::{LyricsConfig, LyricsConfigFile},
    progress_bar::{ProgressBarConfig, ProgressBarConfigFile},
    queue_table::{QueueTableColumns, QueueTableColumnsFile},
    scrollbar::ScrollbarConfig,
    style::{StringColor, ToConfigOr},
};
use crate::config::theme::borders::{BorderSetLib, BorderSetLibFile};

pub mod borders;
pub mod cava;
mod header;
pub mod level_styles;
mod lyrics;
mod progress_bar;
pub mod properties;
pub mod queue_table;
mod scrollbar;
pub mod style;
pub mod volume_slider;

pub use style::{ConfigColor, Modifiers, StyleFile};

pub use self::{
    queue_table::{PercentOrLength, SongTableColumn},
    scrollbar::ScrollbarConfigFile,
};
use super::{
    defaults,
    tabs::{PaneConversionError, PaneOrSplitFile, SizedPaneOrSplit},
};

const DEFAULT_ART: &[u8; 58599] = include_bytes!("../../../../assets/default.jpg");

#[derive(derive_more::Debug, Clone)]
pub struct UiConfig {
    pub draw_borders: bool,
    pub background_color: Option<Color>,
    pub header_background_color: Option<Color>,
    pub modal_background_color: Option<Color>,
    pub modal_backdrop: bool,
    pub text_color: Option<Color>,
    pub preview_label_style: Style,
    pub preview_metadata_group_style: Style,
    pub borders_style: Style,
    pub highlighted_item_style: Style,
    pub current_item_style: Style,
    pub highlight_border_style: Style,
    pub column_widths: [u16; 3],
    pub browser_song_format: SongFormat,
    pub symbols: SymbolsConfig,
    pub progress_bar: ProgressBarConfig,
    pub tab_bar: TabBar,
    pub scrollbar: Option<ScrollbarConfig>,
    pub song_table_format: Vec<SongTableColumn>,
    pub song_table_album_separator: AlbumSeparator,
    pub header_column_widths: [u16; 3],
    pub header: HeaderConfig,
    #[debug("{}", default_album_art.len())]
    pub default_album_art: &'static [u8],
    pub layout: SizedPaneOrSplit,
    pub components: HashMap<String, SizedPaneOrSplit>,
    pub format_tag_separator: String,
    pub multiple_tag_resolution_strategy: TagResolutionStrategy,
    pub level_styles: LevelStyles,
    pub lyrics: LyricsConfig,
    pub cava: CavaTheme,
    pub border_symbol_sets: BorderSetLib,
}

impl Default for UiConfig {
    fn default() -> Self {
        UiConfigFile::default()
            .try_into()
            .expect("Default UiConfigFile should convert successfully")
    }
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(default)]
pub struct UiConfigFile {
    pub(super) draw_borders: bool,
    pub(super) symbols: SymbolsFile,
    pub(super) tab_bar: TabBarFile,
    pub(super) progress_bar: ProgressBarConfigFile,
    pub(super) scrollbar: Option<ScrollbarConfigFile>,
    pub(super) browser_column_widths: Vec<u16>,
    pub(super) browser_song_format: SongFormatFile,
    pub(super) background_color: Option<String>,
    pub(super) text_color: Option<String>,
    pub(super) preview_label_style: StyleFile,
    pub(super) preview_metadata_group_style: StyleFile,
    #[deprecated]
    pub(super) header_background_color: Option<String>,
    pub(super) modal_background_color: Option<String>,
    pub(super) modal_backdrop: bool,
    pub(super) borders_style: Option<StyleFile>,
    pub(super) highlighted_item_style: Option<StyleFile>,
    pub(super) current_item_style: Option<StyleFile>,
    pub(super) highlight_border_style: Option<StyleFile>,
    #[deprecated]
    pub(super) show_song_table_header: bool,
    pub(super) song_table_format: QueueTableColumnsFile,
    pub(super) song_table_album_separator: AlbumSeparator,
    pub(super) header_column_widths: Vec<u16>,
    pub(super) header: HeaderConfigFile,
    pub(super) default_album_art_path: Option<String>,
    pub(super) layout: PaneOrSplitFile,
    pub(super) components: HashMap<String, PaneOrSplitFile>,
    pub(super) format_tag_separator: String,
    pub(super) multiple_tag_resolution_strategy: TagResolutionStrategy,
    pub(super) level_styles: LevelStylesFile,
    pub(super) lyrics: LyricsConfigFile,
    pub(super) cava: CavaThemeFile,
    pub border_symbol_sets: BorderSetLibFile,
}

impl Default for UiConfigFile {
    fn default() -> Self {
        Self {
            layout: PaneOrSplitFile::default(),
            default_album_art_path: None,
            draw_borders: true,
            background_color: None,
            text_color: None,
            header_background_color: None,
            show_song_table_header: false,
            header_column_widths: vec![30, 40, 30],
            header: HeaderConfigFile::default(),
            modal_background_color: None,
            modal_backdrop: false,
            borders_style: Some(StyleFile {
                fg: Some("blue".to_string()),
                bg: None,
                modifiers: None,
            }),
            highlighted_item_style: Some(StyleFile {
                fg: Some("blue".to_string()),
                bg: None,
                modifiers: Some(Modifiers::Bold),
            }),
            current_item_style: Some(StyleFile {
                fg: Some("black".to_string()),
                bg: Some("blue".to_string()),
                modifiers: Some(Modifiers::Bold),
            }),
            highlight_border_style: Some(StyleFile {
                fg: Some("blue".to_string()),
                bg: None,
                modifiers: None,
            }),
            tab_bar: TabBarFile {
                enabled: false,
                active_style: Some(StyleFile {
                    fg: Some("black".to_string()),
                    bg: Some("blue".to_string()),
                    modifiers: Some(Modifiers::Bold),
                }),
                inactive_style: Some(StyleFile { fg: None, bg: None, modifiers: None }),
            },
            browser_column_widths: vec![20, 38, 42],
            progress_bar: ProgressBarConfigFile::default(),
            scrollbar: Some(ScrollbarConfigFile::default()),
            symbols: SymbolsFile {
                song: "S".to_owned(),
                dir: "D".to_owned(),
                playlist: defaults::playlist_symbol(),
                marker: "M".to_owned(),
                ellipsis: Some("...".to_owned()),
                song_style: None,
                dir_style: None,
                playlist_style: None,
                marker_style: None,
                song_highlighted_style: None,
                dir_highlighted_style: None,
                playlist_highlighted_style: None,
                marker_highlighted_style: None,
                song_current_style: None,
                dir_current_style: None,
                playlist_current_style: None,
                marker_current_style: None,
            },
            song_table_format: QueueTableColumnsFile::default(),
            song_table_album_separator: AlbumSeparator::default(),
            browser_song_format: SongFormatFile::default(),
            format_tag_separator: " | ".to_owned(),
            multiple_tag_resolution_strategy: TagResolutionStrategy::default(),
            preview_label_style: StyleFile {
                fg: Some("yellow".to_string()),
                bg: None,
                modifiers: None,
            },
            preview_metadata_group_style: StyleFile {
                fg: Some("yellow".to_string()),
                bg: None,
                modifiers: Some(Modifiers::Bold),
            },
            level_styles: LevelStylesFile::default(),
            components: defaults::components(),
            lyrics: LyricsConfigFile::default(),
            cava: CavaThemeFile::default(),
            border_symbol_sets: BorderSetLibFile::default(),
        }
    }
}

#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum AlbumSeparator {
    #[default]
    None,
    Underline,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct TabBarFile {
    // deprecated
    #[serde(default = "defaults::bool::<false>")]
    pub(super) enabled: bool,
    pub(super) active_style: Option<StyleFile>,
    pub(super) inactive_style: Option<StyleFile>,
}

#[derive(Debug, Default, Clone, Copy)]
pub struct TabBar {
    pub active_style: Style,
    pub inactive_style: Style,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct SymbolsFile {
    pub(super) song: String,
    pub(super) dir: String,
    #[serde(default = "defaults::playlist_symbol")]
    pub(super) playlist: String,
    pub(super) marker: String,
    pub(super) ellipsis: Option<String>,
    pub(super) song_style: Option<StyleFile>,
    pub(super) dir_style: Option<StyleFile>,
    pub(super) playlist_style: Option<StyleFile>,
    pub(super) marker_style: Option<StyleFile>,
    pub(super) song_highlighted_style: Option<StyleFile>,
    pub(super) dir_highlighted_style: Option<StyleFile>,
    pub(super) playlist_highlighted_style: Option<StyleFile>,
    pub(super) marker_highlighted_style: Option<StyleFile>,
    pub(super) song_current_style: Option<StyleFile>,
    pub(super) dir_current_style: Option<StyleFile>,
    pub(super) playlist_current_style: Option<StyleFile>,
    pub(super) marker_current_style: Option<StyleFile>,
}

#[derive(Debug, Default, Clone)]
pub struct SymbolsConfig {
    pub song: String,
    pub dir: String,
    pub playlist: String,
    pub marker: String,
    pub ellipsis: String,
    pub song_style: Option<Style>,
    pub dir_style: Option<Style>,
    pub playlist_style: Option<Style>,
    pub marker_style: Option<Style>,
    pub song_highlighted_style: Option<Style>,
    pub dir_highlighted_style: Option<Style>,
    pub playlist_highlighted_style: Option<Style>,
    pub marker_highlighted_style: Option<Style>,
    pub song_current_style: Option<Style>,
    pub dir_current_style: Option<Style>,
    pub playlist_current_style: Option<Style>,
    pub marker_current_style: Option<Style>,
}

impl From<SymbolsFile> for SymbolsConfig {
    fn from(value: SymbolsFile) -> Self {
        Self {
            song: value.song,
            dir: value.dir,
            playlist: value.playlist,
            marker: value.marker,
            ellipsis: value.ellipsis.unwrap_or_else(|| "...".to_string()),
            song_style: value
                .song_style
                .map(|s| s.to_config_or(None, None))
                .transpose()
                .unwrap_or_default(),
            dir_style: value
                .dir_style
                .map(|s| s.to_config_or(None, None))
                .transpose()
                .unwrap_or_default(),
            playlist_style: value
                .playlist_style
                .map(|s| s.to_config_or(None, None))
                .transpose()
                .unwrap_or_default(),
            marker_style: value
                .marker_style
                .map(|s| s.to_config_or(None, None))
                .transpose()
                .unwrap_or_default(),
            song_highlighted_style: value
                .song_highlighted_style
                .map(|s| s.to_config_or(None, None))
                .transpose()
                .unwrap_or_default(),
            dir_highlighted_style: value
                .dir_highlighted_style
                .map(|s| s.to_config_or(None, None))
                .transpose()
                .unwrap_or_default(),
            playlist_highlighted_style: value
                .playlist_highlighted_style
                .map(|s| s.to_config_or(None, None))
                .transpose()
                .unwrap_or_default(),
            marker_highlighted_style: value
                .marker_highlighted_style
                .map(|s| s.to_config_or(None, None))
                .transpose()
                .unwrap_or_default(),
            song_current_style: value
                .song_current_style
                .map(|s| s.to_config_or(None, None))
                .transpose()
                .unwrap_or_default(),
            dir_current_style: value
                .dir_current_style
                .map(|s| s.to_config_or(None, None))
                .transpose()
                .unwrap_or_default(),
            playlist_current_style: value
                .playlist_current_style
                .map(|s| s.to_config_or(None, None))
                .transpose()
                .unwrap_or_default(),
            marker_current_style: value
                .marker_current_style
                .map(|s| s.to_config_or(None, None))
                .transpose()
                .unwrap_or_default(),
        }
    }
}
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum TagResolutionStrategy {
    First,
    Last,
    #[default]
    All,
    Nth(usize),
}

impl TagResolutionStrategy {
    pub fn resolve<'a>(self, tag: &'a MetadataTag, separator: &str) -> std::borrow::Cow<'a, str> {
        match self {
            TagResolutionStrategy::First => tag.first().into(),
            TagResolutionStrategy::Last => tag.last().into(),
            TagResolutionStrategy::All => tag.join(separator),
            TagResolutionStrategy::Nth(idx) => tag.nth(idx).into(),
        }
    }
}

// Converts all components while also resolving dependencies between them. If a
// component is missing but is present in the source map it will be skipped and
// the resolution will be retried in the next loop over. Only when component is
// truly missing or a different kind of error occurs will the conversion fail.
fn convert_components(
    value: HashMap<String, PaneOrSplitFile>,
    border_set_lib: &BorderSetLib,
) -> Result<HashMap<String, SizedPaneOrSplit>> {
    let mut result = HashMap::new();
    let mut components = value.into_iter().collect_vec();

    let mut i = 0usize;
    let mut last_size = components.len();
    let mut same_size_count = 0;
    loop {
        let current = components.get(i.checked_rem(components.len()).unwrap_or(0));
        match current {
            Some((name, pane)) => match pane.convert(&result, border_set_lib) {
                Ok(v) => {
                    result.insert(name.to_owned(), v);
                    components.remove(i % components.len());
                }
                Err(PaneConversionError::MissingComponent(missing_name))
                    if components.iter().any(|(n, _)| n == &missing_name) =>
                {
                    i += 1;
                }
                err @ Err(_) => {
                    err?;
                }
            },
            None => break,
        }

        let remaining = components.len();
        if last_size == remaining {
            same_size_count += 1;
        } else {
            same_size_count = 0;
        }

        if same_size_count > remaining {
            bail!(
                "Failed to resolve components. Circular dependency detected. Components: {:?}",
                components.iter().map(|(name, _)| name).collect::<Vec<_>>()
            );
        }

        last_size = remaining;
    }

    log::trace!(result:?; "Converted components");

    Ok(result)
}

impl TryFrom<UiConfigFile> for UiConfig {
    type Error = anyhow::Error;

    #[allow(clippy::similar_names)]
    fn try_from(value: UiConfigFile) -> Result<Self, Self::Error> {
        let fg_color = StringColor(value.text_color.clone()).to_color()?;
        let bg_color = StringColor(value.background_color).to_color()?;
        let header_bg_color = StringColor(value.header_background_color).to_color()?.or(bg_color);
        let border = value.borders_style.to_config_or(None, None)?;
        let border_set_lib: BorderSetLib = value.border_symbol_sets.try_into()?;
        let components = convert_components(value.components, &border_set_lib)?;
        let current_style = value.current_item_style.to_config_or(None, None)?;
        let highlighted_style = value.highlighted_item_style.to_config_or(None, None)?;

        Ok(Self {
            layout: value.layout.convert(&components, &border_set_lib)?,
            components,
            cava: value.cava.into_config(bg_color)?,
            background_color: bg_color,
            draw_borders: value.draw_borders,
            format_tag_separator: value.format_tag_separator,
            multiple_tag_resolution_strategy: value.multiple_tag_resolution_strategy,
            modal_background_color: StringColor(value.modal_background_color)
                .to_color()?
                .or(bg_color),
            modal_backdrop: value.modal_backdrop,
            text_color: fg_color,
            header_background_color: header_bg_color,
            borders_style: border,
            highlight_border_style: value
                .highlight_border_style
                .to_config_or(border.fg, border.bg)?,
            symbols: value.symbols.into(),
            scrollbar: value
                .scrollbar
                .map(|sc| sc.into_config(border.fg.unwrap_or_default()))
                .transpose()?,
            progress_bar: value.progress_bar.into_config()?,
            song_table_format: TryInto::<QueueTableColumns>::try_into(value.song_table_format)?.0,
            song_table_album_separator: value.song_table_album_separator,
            header_column_widths: [
                value.header_column_widths[0],
                value.header_column_widths[1],
                value.header_column_widths[2],
            ],
            header: value.header.try_into()?,
            column_widths: [
                value.browser_column_widths[0],
                value.browser_column_widths[1],
                value.browser_column_widths[2],
            ],
            tab_bar: TabBar {
                active_style: value
                    .tab_bar
                    .active_style
                    .to_config_or(current_style.fg.or(fg_color), current_style.bg)?,
                inactive_style: value
                    .tab_bar
                    .inactive_style
                    .to_config_or(fg_color, header_bg_color)?,
            },
            highlighted_item_style: highlighted_style,
            current_item_style: current_style,
            default_album_art: value
                .default_album_art_path
                .map_or(Ok(DEFAULT_ART as &'static [u8]), |path| -> Result<_> {
                    let path = env_var_expand(&path);
                    let path = tilde_expand(&path);
                    Ok(std::fs::read(path.as_ref())?.leak())
                })
                .context("Failed to read 'default_album_art_path'")?,
            browser_song_format: TryInto::<SongFormat>::try_into(value.browser_song_format)?,
            preview_label_style: value.preview_label_style.to_config_or(None, None)?,
            preview_metadata_group_style: value
                .preview_metadata_group_style
                .to_config_or(None, None)?,
            level_styles: value.level_styles.try_into()?,
            lyrics: value.lyrics.into(),
            border_symbol_sets: border_set_lib,
        })
    }
}
