use std::collections::{HashMap, HashSet};

use chrono::format::SecondsFormat;
use chrono::{DateTime, Local, NaiveDate, Utc};
use iced::Color;
use serde::{Deserialize, Deserializer};

pub use self::channel::{Channel, ChannelNameCasing};
pub use self::hide_consecutive::{HideConsecutive, HideConsecutiveEnabled};
pub use self::redaction::Redaction;
pub use self::typing::{Animation, Style, Typing};
pub use crate::appearance::theme::{alpha_color, alpha_color_calculate};
use crate::buffer::{
    BacklogSeparator, DateSeparators, SkinTone, StatusMessagePrefix, Timestamp,
};
use crate::config::buffer::nickname::Nickname;
use crate::config::buffer::text_input::TextInput;
use crate::config::inclusivities::{
    Inclusivities, is_target_channel_included, is_target_query_included,
};
use crate::message::source;
use crate::target::TargetRef;
use crate::user::Nick;
use crate::{Server, isupport};

pub mod channel;
pub mod hide_consecutive;
pub mod nickname;
pub mod redaction;
pub mod text_input;
pub mod typing;

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default)]
pub struct Buffer {
    pub timestamp: Timestamp,
    pub nickname: Nickname,
    pub text_input: TextInput,
    pub channel: Channel,
    pub server_messages: ServerMessages,
    pub internal_messages: InternalMessages,
    pub status_message_prefix: StatusMessagePrefix,
    pub chathistory: ChatHistory,
    pub backlog_separator: BacklogSeparator,
    pub date_separators: DateSeparators,
    pub commands: Commands,
    pub emojis: Emojis,
    pub mark_as_read: MarkAsRead,
    pub url: Url,
    pub line_spacing: u32,
    pub scroll_position_on_open: ScrollPosition,
    pub typing: Typing,
    pub redaction: Redaction,
    pub close: Close,
    pub reply: Reply,
}

#[derive(Debug, Clone, Deserialize, Default)]
#[serde(default)]
pub struct Close {
    pub query: CloseQuery,
}

#[derive(Debug, Clone, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
pub enum CloseQuery {
    #[default]
    Keep,
    Close,
}

impl CloseQuery {
    pub fn close(&self) -> bool {
        matches!(self, Self::Close)
    }
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct Reply {
    pub enabled: bool,
    pub show_icon: bool,
    pub icon_size: f32,
    pub insert_nick: bool,
    pub hide_redundant_nicks: bool,
    pub highlight_hovered_message: bool,
    pub tooltip: ReplyTooltip,
}

impl Default for Reply {
    fn default() -> Self {
        Self {
            enabled: true,
            show_icon: false,
            icon_size: 10.0,
            insert_nick: true,
            hide_redundant_nicks: true,
            highlight_hovered_message: true,
            tooltip: ReplyTooltip::default(),
        }
    }
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ReplyTooltip {
    pub enabled: bool,
    pub delay: u64,
    pub max_width: f32,
    pub max_chars: usize,
}

impl Default for ReplyTooltip {
    fn default() -> Self {
        Self {
            enabled: true,
            delay: 500,
            max_width: 600.0,
            max_chars: 1024,
        }
    }
}

#[derive(Debug, Clone, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
pub enum NicknameClickAction {
    #[default]
    OpenQuery,
    InsertNickname,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct Emojis {
    pub show_picker: bool,
    pub skin_tone: SkinTone,
    pub auto_replace: bool,
    pub characters_to_trigger_picker: usize,
}

impl Default for Emojis {
    fn default() -> Self {
        Self {
            show_picker: true,
            skin_tone: SkinTone::default(),
            auto_replace: true,
            characters_to_trigger_picker: 2,
        }
    }
}

#[derive(Debug, Clone, Deserialize, Default)]
#[serde(default)]
pub struct Url {
    pub prompt_before_open: bool,
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ScrollPosition {
    #[default]
    OldestUnread,
    Newest,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct MarkAsRead {
    pub on_application_exit: bool,
    pub on_buffer_close: OnBufferClose,
    pub on_scroll_to_bottom: bool,
    pub on_message_sent: bool,
    pub on_message: OnMessage,
}

impl Default for MarkAsRead {
    fn default() -> Self {
        Self {
            on_application_exit: false,
            on_buffer_close: OnBufferClose::default(),
            on_scroll_to_bottom: true,
            on_message_sent: true,
            on_message: OnMessage::default(),
        }
    }
}

#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum OnBufferClose {
    Bool(bool),
    Condition(OnBufferCloseCondition),
}

impl Default for OnBufferClose {
    fn default() -> Self {
        Self::Condition(OnBufferCloseCondition::ScrolledToBottom)
    }
}

impl OnBufferClose {
    pub fn mark_as_read(&self, is_scrolled_to_bottom: Option<bool>) -> bool {
        match self {
            OnBufferClose::Bool(mark) => *mark,
            OnBufferClose::Condition(_) => {
                is_scrolled_to_bottom.unwrap_or(false)
            }
        }
    }
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum OnBufferCloseCondition {
    ScrolledToBottom,
}

#[derive(Debug, Default, Clone)]
pub enum OnMessage {
    #[default]
    Focused,
    Open,
    None,
}

impl<'de> Deserialize<'de> for OnMessage {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        #[derive(Deserialize)]
        #[serde(rename_all = "kebab-case")]
        pub enum Enum {
            Focused,
            Open,
            None,
        }

        #[derive(Deserialize)]
        #[serde(untagged)]
        enum OnMessageCondition {
            Enum(Enum),
            Bool(bool),
        }

        match OnMessageCondition::deserialize(deserializer)? {
            OnMessageCondition::Enum(on_message) => Ok(match on_message {
                Enum::Focused => Self::Focused,
                Enum::Open => Self::Open,
                Enum::None => Self::None,
            }),
            OnMessageCondition::Bool(boolean) => {
                if boolean {
                    Ok(Self::Open)
                } else {
                    Ok(Self::None)
                }
            }
        }
    }
}

#[derive(Debug, Clone, Deserialize, Default)]
#[serde(default)]
pub struct Quit {
    #[serde(
        deserialize_with = "crate::serde::deserialize_empty_string_as_none"
    )]
    pub default_reason: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Default)]
#[serde(default)]
pub struct Part {
    #[serde(
        deserialize_with = "crate::serde::deserialize_empty_string_as_none"
    )]
    pub default_reason: Option<String>,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct Exec {
    pub enabled: bool,
    pub timeout: u64,
    pub max_output_bytes: usize,
}

impl Default for Exec {
    fn default() -> Self {
        Self {
            enabled: false,
            timeout: 5,
            max_output_bytes: 4096,
        }
    }
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct SysInfo {
    pub cpu: bool,
    pub memory: bool,
    pub gpu: bool,
    pub os: bool,
    pub uptime: bool,
}

impl Default for SysInfo {
    fn default() -> Self {
        Self {
            cpu: true,
            memory: true,
            gpu: true,
            os: true,
            uptime: true,
        }
    }
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct Commands {
    pub show_description: bool,
    pub exec: Exec,
    pub sysinfo: SysInfo,
    pub quit: Quit,
    pub part: Part,
    #[serde(default, deserialize_with = "deserialize_aliases")]
    pub aliases: HashMap<String, String>,
}

impl Default for Commands {
    fn default() -> Self {
        Self {
            show_description: true,
            exec: Exec::default(),
            sysinfo: SysInfo::default(),
            quit: Quit::default(),
            part: Part::default(),
            aliases: HashMap::default(),
        }
    }
}

fn deserialize_aliases<'de, D>(
    deserializer: D,
) -> Result<HashMap<String, String>, D::Error>
where
    D: Deserializer<'de>,
{
    let aliases = HashMap::<String, String>::deserialize(deserializer)?;

    aliases.into_iter().try_fold(
        HashMap::new(),
        |mut normalized, (alias, command)| {
            let alias =
                alias.trim().trim_start_matches('/').to_ascii_lowercase();

            if alias.is_empty() {
                return Err(serde::de::Error::custom(
                    "alias names cannot be empty",
                ));
            }

            let command = command.trim().to_string();

            if command.is_empty() {
                return Err(serde::de::Error::custom(format!(
                    "alias '{alias}' cannot map to an empty command"
                )));
            }

            if !command.starts_with('/') {
                log::warn!(
                    "ignoring alias '{alias}' as it doesnt start with '/'"
                );

                return Ok(normalized);
            }

            if !crate::command::alias::has_trailing_optional_args(&command) {
                return Err(serde::de::Error::custom(format!(
                    "alias '{alias}' has a required argument after an optional one"
                )));
            }

            if normalized.contains_key(&alias) {
                return Err(serde::de::Error::custom(format!(
                    "duplicate alias name after normalization: '{alias}'"
                )));
            }

            normalized.insert(alias, command);

            Ok(normalized)
        },
    )
}

#[derive(Debug, Clone, Copy)]
pub enum Away {
    Dimmed(Dimmed),
    None,
}

impl Away {
    pub fn is_away(&self, is_user_away: bool) -> Option<Away> {
        is_user_away.then_some(*self)
    }
}

impl Default for Away {
    fn default() -> Self {
        Away::Dimmed(Dimmed::default())
    }
}

impl<'de> Deserialize<'de> for Away {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        #[serde(untagged)]
        enum AppearanceRepr {
            String(String),
            Struct(DimmedStruct),
        }

        #[derive(Deserialize)]
        struct DimmedStruct {
            dimmed: Option<f32>,
        }

        let repr = AppearanceRepr::deserialize(deserializer)?;
        match repr {
            AppearanceRepr::String(s) => match s.as_str() {
                "dimmed" => Ok(Away::Dimmed(Dimmed {
                    enabled: true,
                    alpha: None,
                })),
                "solid" | "none" => Ok(Away::None),
                _ => Err(serde::de::Error::custom(format!(
                    "unknown appearance: {s}",
                ))),
            },
            AppearanceRepr::Struct(s) => Ok(Away::Dimmed(Dimmed {
                enabled: true,
                alpha: s.dimmed,
            })),
        }
    }
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ServerMessages {
    pub condense: Condensation,
    pub join_topic: ServerMessage,
    pub join: ServerMessage,
    pub part: ServerMessage,
    pub quit: ServerMessage,
    pub change_host: ServerMessage,
    pub change_mode: ServerMessage,
    pub change_nick: ServerMessage,
    pub monitored_online: ServerMessage,
    pub monitored_offline: ServerMessage,
    pub standard_reply_fail: ServerMessage,
    pub standard_reply_warn: ServerMessage,
    pub standard_reply_note: ServerMessage,
    pub wallops: ServerMessage,
    pub kick: ServerMessage,
    pub change_topic: ServerMessage,
    pub request_topic: ServerMessage,
    pub away: ServerMessage,
    pub invite: ServerMessage,
    pub default: ServerMessageDefault,
}

impl Default for ServerMessages {
    fn default() -> Self {
        Self {
            condense: Condensation::default(),
            join_topic: ServerMessage {
                enabled: Some(ServerMessageEnabled::Drop),
                ..ServerMessage::default()
            },
            join: ServerMessage::default(),
            part: ServerMessage::default(),
            quit: ServerMessage::default(),
            change_host: ServerMessage::default(),
            change_mode: ServerMessage::default(),
            change_nick: ServerMessage::default(),
            monitored_online: ServerMessage::default(),
            monitored_offline: ServerMessage::default(),
            standard_reply_fail: ServerMessage::default(),
            standard_reply_warn: ServerMessage::default(),
            standard_reply_note: ServerMessage::default(),
            wallops: ServerMessage::default(),
            kick: ServerMessage::default(),
            change_topic: ServerMessage::default(),
            request_topic: ServerMessage::default(),
            away: ServerMessage::default(),
            invite: ServerMessage::default(),
            default: ServerMessageDefault::default(),
        }
    }
}

impl ServerMessages {
    pub fn get(&self, kind: source::server::Kind) -> &ServerMessage {
        match kind {
            source::server::Kind::JoinTopic => &self.join_topic,
            source::server::Kind::Join => &self.join,
            source::server::Kind::Part => &self.part,
            source::server::Kind::Quit => &self.quit,
            source::server::Kind::ChangeHost => &self.change_host,
            source::server::Kind::ChangeMode => &self.change_mode,
            source::server::Kind::ChangeNick => &self.change_nick,
            source::server::Kind::MonitoredOnline => &self.monitored_online,
            source::server::Kind::MonitoredOffline => &self.monitored_offline,
            source::server::Kind::StandardReply(
                source::server::StandardReply::Fail,
            ) => &self.standard_reply_fail,
            source::server::Kind::StandardReply(
                source::server::StandardReply::Warn,
            ) => &self.standard_reply_warn,
            source::server::Kind::StandardReply(
                source::server::StandardReply::Note,
            ) => &self.standard_reply_note,
            source::server::Kind::WAllOps => &self.wallops,
            source::server::Kind::Kick => &self.kick,
            source::server::Kind::ChangeTopic => &self.change_topic,
            source::server::Kind::RequestTopic => &self.request_topic,
            source::server::Kind::Away => &self.away,
            source::server::Kind::Invite => &self.invite,
        }
    }

    pub fn dimmed(
        &self,
        kind: Option<source::server::Kind>,
    ) -> Option<&Dimmed> {
        kind.and_then(|kind| self.get(kind).dimmed.as_ref()).or(
            if kind.is_none_or(|kind| kind.is_action()) {
                self.default.actions_dimmed.as_ref()
            } else {
                self.default.passive_dimmed.as_ref()
            },
        )
    }

    pub fn enabled(&self, kind: Option<source::server::Kind>) -> bool {
        if let Some(kind) = kind
            && let Some(enabled) = self.get(kind).is_enabled()
        {
            enabled
        } else {
            self.default.enabled
        }
    }

    pub fn should_drop(&self, kind: Option<source::server::Kind>) -> bool {
        if let Some(kind) = kind
            && let Some(should_drop) = self.get(kind).should_drop()
        {
            should_drop
        } else {
            false
        }
    }

    pub fn exclude(
        &self,
        kind: Option<source::server::Kind>,
    ) -> Option<&Inclusivities> {
        kind.and_then(|kind| self.get(kind).exclude.as_ref())
            .or(self.default.exclude.as_ref())
    }

    pub fn include(
        &self,
        kind: Option<source::server::Kind>,
    ) -> Option<&Inclusivities> {
        kind.and_then(|kind| self.get(kind).include.as_ref())
            .or(self.default.include.as_ref())
    }

    pub fn should_send_message(
        &self,
        source: Option<&source::Server>,
        target_ref: TargetRef,
        server: &Server,
        casemapping: isupport::CaseMap,
    ) -> bool {
        let kind = source.map(source::server::Server::kind);

        // Server Message is not enabled.
        if !self.enabled(kind) {
            return false;
        }

        match target_ref {
            TargetRef::Channel(channel) => is_target_channel_included(
                self.include(kind),
                self.exclude(kind),
                source.and_then(|source| source.nick().map(Nick::as_nickref)),
                channel,
                server,
                casemapping,
            ),
            TargetRef::Query(query) => is_target_query_included(
                self.include(kind),
                self.exclude(kind),
                query,
                server,
                casemapping,
            ),
        }
    }

    pub fn smart(&self, kind: Option<source::server::Kind>) -> Option<i64> {
        kind.and_then(|kind| self.get(kind).smart)
            .or(self.default.smart)
    }

    pub fn username_format(
        &self,
        kind: Option<source::server::Kind>,
    ) -> UsernameFormat {
        kind.and_then(|kind| self.get(kind).username_format)
            .unwrap_or(self.default.username_format)
    }
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct Condensation {
    pub messages: HashSet<CondensationMessage>,
    pub format: CondensationFormat,
    pub icon: CondensationIcon,
    #[serde(deserialize_with = "deserialize_dimmed_maybe")]
    pub dimmed: Option<Dimmed>,
    pub max: Option<u16>,
}

impl Default for Condensation {
    fn default() -> Self {
        Self {
            messages: HashSet::from([
                CondensationMessage::ChangeHost,
                CondensationMessage::ChangeNick,
                CondensationMessage::Join,
                CondensationMessage::Part,
                CondensationMessage::Quit,
            ]),
            format: CondensationFormat::default(),
            icon: CondensationIcon::default(),
            dimmed: Some(Dimmed::default()),
            max: None,
        }
    }
}

impl Condensation {
    pub fn kind(&self, server: &source::Server) -> bool {
        match server.kind() {
            source::server::Kind::Join => {
                self.messages.contains(&CondensationMessage::Join)
            }
            source::server::Kind::Part => {
                self.messages.contains(&CondensationMessage::Part)
            }
            source::server::Kind::Quit => {
                self.messages.contains(&CondensationMessage::Quit)
            }
            source::server::Kind::ChangeNick => {
                self.messages.contains(&CondensationMessage::ChangeNick)
            }
            source::server::Kind::ChangeHost => {
                self.messages.contains(&CondensationMessage::ChangeHost)
            }
            source::server::Kind::Kick => {
                self.messages.contains(&CondensationMessage::Kick)
            }
            source::server::Kind::JoinTopic
            | source::server::Kind::ChangeMode
            | source::server::Kind::MonitoredOnline
            | source::server::Kind::MonitoredOffline
            | source::server::Kind::StandardReply(_)
            | source::server::Kind::WAllOps
            | source::server::Kind::ChangeTopic
            | source::server::Kind::RequestTopic
            | source::server::Kind::Away
            | source::server::Kind::Invite => false,
        }
    }

    pub fn any(&self) -> bool {
        !self.messages.is_empty()
    }
}

#[derive(Debug, Clone, Deserialize, Eq, Hash, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum CondensationMessage {
    Join,
    Part,
    Quit,
    #[serde(alias = "change_nick")]
    ChangeNick,
    #[serde(alias = "change_host")]
    ChangeHost,
    Kick,
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum CondensationFormat {
    #[default]
    Brief,
    Detailed,
    Full,
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum CondensationIcon {
    #[default]
    None,
    Chevron,
    Dot,
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default)]
pub struct ServerMessage {
    pub enabled: Option<ServerMessageEnabled>,
    pub smart: Option<i64>,
    pub username_format: Option<UsernameFormat>,
    pub exclude: Option<Inclusivities>,
    pub include: Option<Inclusivities>,
    #[serde(deserialize_with = "deserialize_dimmed_maybe")]
    pub dimmed: Option<Dimmed>,
}

impl ServerMessage {
    pub fn is_enabled(&self) -> Option<bool> {
        self.enabled
            .map(|enabled| matches!(enabled, ServerMessageEnabled::Enabled))
    }

    pub fn should_drop(&self) -> Option<bool> {
        self.enabled
            .map(|enabled| matches!(enabled, ServerMessageEnabled::Drop))
    }
}

#[derive(Debug, Clone, Copy)]
pub enum ServerMessageEnabled {
    Enabled,
    Disabled,
    Drop,
}

impl<'de> Deserialize<'de> for ServerMessageEnabled {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        #[serde(rename_all = "kebab-case")]
        pub enum Enabled {
            Drop,
        }

        #[derive(Deserialize)]
        #[serde(untagged)]
        enum Inner {
            Boolean(bool),
            Enum(Enabled),
        }

        match Inner::deserialize(deserializer)? {
            Inner::Boolean(b) => {
                if b {
                    Ok(ServerMessageEnabled::Enabled)
                } else {
                    Ok(ServerMessageEnabled::Disabled)
                }
            }
            Inner::Enum(_enabled) => Ok(ServerMessageEnabled::Drop),
        }
    }
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ServerMessageDefault {
    pub enabled: bool,
    pub smart: Option<i64>,
    pub username_format: UsernameFormat,
    pub exclude: Option<Inclusivities>,
    pub include: Option<Inclusivities>,
    #[serde(alias = "dimmed", deserialize_with = "deserialize_dimmed_maybe")]
    pub passive_dimmed: Option<Dimmed>,
    #[serde(deserialize_with = "deserialize_dimmed_maybe")]
    pub actions_dimmed: Option<Dimmed>,
}

impl Default for ServerMessageDefault {
    fn default() -> Self {
        Self {
            enabled: true,
            smart: None,
            username_format: UsernameFormat::default(),
            exclude: None,
            include: None,
            passive_dimmed: Some(Dimmed::default()),
            actions_dimmed: None,
        }
    }
}

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct InternalMessages {
    pub success: InternalMessage,
    pub error: InternalMessage,
    pub default: InternalMessageDefault,
}

impl InternalMessages {
    pub fn get(&self, status: &source::Status) -> &InternalMessage {
        match status {
            source::Status::Success => &self.success,
            source::Status::Error => &self.error,
        }
    }

    pub fn enabled(&self, status: &source::Status) -> bool {
        if let Some(enabled) = self.get(status).enabled {
            enabled
        } else {
            self.default.enabled
        }
    }

    pub fn smart(&self, status: &source::Status) -> Option<i64> {
        self.get(status).smart.or(self.default.smart)
    }
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default)]
pub struct InternalMessage {
    pub enabled: Option<bool>,
    pub smart: Option<i64>,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct InternalMessageDefault {
    pub enabled: bool,
    pub smart: Option<i64>,
}

impl Default for InternalMessageDefault {
    fn default() -> Self {
        Self {
            enabled: true,
            smart: None,
        }
    }
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum LevelFilter {
    Off,
    Error,
    Warn,
    #[default]
    Info,
    Debug,
    Trace,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ChatHistory {
    pub infinite_scroll: bool,
}

impl Default for ChatHistory {
    fn default() -> Self {
        Self {
            infinite_scroll: true,
        }
    }
}

#[derive(Debug, Copy, Clone, Default, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum UsernameFormat {
    Short,
    #[default]
    Full,
    #[serde(skip)]
    Mask,
}

#[derive(Debug, Copy, Clone, Default)]
pub enum AccessLevelFormat {
    All,
    #[default]
    Highest,
    None,
}

impl<'de> Deserialize<'de> for AccessLevelFormat {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        #[serde(rename_all = "kebab-case")]
        pub enum Format {
            All,
            Highest,
            None,
        }

        #[derive(Deserialize)]
        #[serde(untagged)]
        enum Inner {
            Boolean(bool),
            Enum(Format),
        }

        match Inner::deserialize(deserializer)? {
            Inner::Boolean(b) => {
                if b {
                    Ok(AccessLevelFormat::Highest)
                } else {
                    Ok(AccessLevelFormat::None)
                }
            }
            Inner::Enum(format) => match format {
                Format::All => Ok(AccessLevelFormat::All),
                Format::Highest => Ok(AccessLevelFormat::Highest),
                Format::None => Ok(AccessLevelFormat::None),
            },
        }
    }
}

impl Buffer {
    pub fn format_timestamp(
        &self,
        date_time: &DateTime<Utc>,
    ) -> Option<String> {
        if self.timestamp.format.is_empty() {
            return None;
        }

        Some(self.timestamp.brackets.format(
            date_time.with_timezone(&Local).format_localized(
                &self.timestamp.format,
                self.timestamp.locale,
            ),
        ))
    }

    pub fn format_range_end_timestamp(
        &self,
        end_date_time: &DateTime<Utc>,
    ) -> Option<(String, String)> {
        if self.timestamp.format.is_empty() {
            return None;
        }

        Some((
            "\u{2013} ".to_string(),
            self.timestamp
                .brackets
                .format(format!(
                    "{}",
                    end_date_time.with_timezone(&Local).format_localized(
                        &self.timestamp.format,
                        self.timestamp.locale
                    )
                ))
                .to_string(),
        ))
    }

    pub fn format_context_menu_timestamp(
        &self,
        date_time: &DateTime<Utc>,
    ) -> String {
        date_time
            .with_timezone(&Local)
            .format_localized(
                &self.timestamp.context_menu_format,
                self.timestamp.locale,
            )
            .to_string()
    }

    pub fn format_copy_timestamp(&self, date_time: &DateTime<Utc>) -> String {
        if let Some(copy_format) = &self.timestamp.copy_format {
            date_time
                .with_timezone(&Local)
                .format_localized(copy_format, self.timestamp.locale)
                .to_string()
        } else {
            date_time
                .with_timezone(&Local)
                .to_rfc3339_opts(SecondsFormat::Millis, true)
        }
    }

    pub fn format_date_separator(&self, date: &NaiveDate) -> String {
        date.and_hms_opt(0, 0, 0)
            .and_then(|date_time| date_time.and_local_timezone(Local).single())
            .map_or(
                // in the event of timezone weirdness,
                // revert to default format
                date.format_localized(
                    &DateSeparators::default().format,
                    self.timestamp.locale,
                ),
                |date_time| {
                    date_time.format_localized(
                        &self.date_separators.format,
                        self.timestamp.locale,
                    )
                },
            )
            .to_string()
    }
}

#[derive(Debug, Clone, Copy, Deserialize)]
pub struct Dimmed {
    enabled: bool,
    alpha: Option<f32>,
}

impl Default for Dimmed {
    fn default() -> Self {
        Self {
            enabled: true,
            alpha: None,
        }
    }
}

impl Dimmed {
    pub fn new(alpha: Option<f32>) -> Self {
        Dimmed {
            enabled: true,
            alpha,
        }
    }

    pub fn transform_color(&self, color: Color, background: Color) -> Color {
        if self.enabled {
            match self.alpha {
                // Calculate alpha based on background and foreground.
                None => alpha_color_calculate(0.20, 0.61, background, color),
                // Calculate alpha based on user defined alpha value.
                Some(a) => alpha_color(color, a),
            }
        } else {
            color
        }
    }
}

pub fn deserialize_dimmed_maybe<'de, D>(
    deserializer: D,
) -> Result<Option<Dimmed>, D::Error>
where
    D: Deserializer<'de>,
{
    #[derive(Deserialize)]
    #[serde(untagged)]
    enum Data {
        Boolean(bool),
        Float(f32),
    }

    let dimmed_maybe: Option<Data> = Deserialize::deserialize(deserializer)?;

    Ok(dimmed_maybe.map(|dimmed| match dimmed {
        Data::Boolean(dim) => Dimmed {
            enabled: dim,
            alpha: None,
        },
        Data::Float(dim) => Dimmed {
            enabled: true,
            alpha: Some(dim),
        },
    }))
}
