// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0

//! Contains stylesheet implementations for widgets native to iced.

use crate::theme::{CosmicComponent, TRANSPARENT_COMPONENT, Theme};
use cosmic_theme::composite::over;
use iced::overlay::menu;
use iced::theme::Base;
use iced::widget::slider::{self, Rail};
use iced::widget::{
    button as iced_button, checkbox as iced_checkbox, combo_box, container as iced_container,
    pane_grid, pick_list, progress_bar, radio, rule, scrollable, svg, toggler,
};
use iced_core::{Background, Border, Color, Shadow, Vector};
use iced_widget::pane_grid::Highlight;
use iced_widget::scrollable::AutoScroll;
use iced_widget::{text_editor, text_input};
use palette::WithAlpha;
use std::rc::Rc;

pub mod application {
    use crate::Theme;
    use iced_runtime::Appearance;

    #[derive(Default)]
    pub enum Application {
        #[default]
        Default,
        Custom(Box<dyn Fn(&Theme) -> Appearance>),
    }

    impl Application {
        pub fn custom<F: Fn(&Theme) -> Appearance + 'static>(f: F) -> Self {
            Self::Custom(Box::new(f))
        }
    }

    pub fn style(theme: &Theme) -> iced::theme::Style {
        let cosmic = theme.cosmic();

        iced::theme::Style {
            background_color: cosmic.bg_color().into(),
            text_color: cosmic.on_bg_color().into(),
            icon_color: cosmic.on_bg_color().into(),
        }
    }
}

/// Styles for the button widget from iced-rs.
#[derive(Default)]
pub enum Button {
    Deactivated,
    Destructive,
    Positive,
    #[default]
    Primary,
    Secondary,
    Text,
    Link,
    LinkActive,
    Transparent,
    Card,
    Custom(Box<dyn Fn(&Theme, iced_button::Status) -> iced_button::Style>),
}

impl iced_button::Catalog for Theme {
    type Class<'a> = Button;

    fn default<'a>() -> Self::Class<'a> {
        Button::default()
    }

    fn style(&self, class: &Self::Class<'_>, status: iced_button::Status) -> iced_button::Style {
        if let Button::Custom(f) = class {
            return f(self, status);
        }
        let cosmic = self.cosmic();
        let corner_radii = &cosmic.corner_radii;
        let component = class.cosmic(self);

        let mut appearance = iced_button::Style {
            border_radius: match class {
                Button::Link => corner_radii.radius_0.into(),
                Button::Card => corner_radii.radius_xs.into(),
                _ => corner_radii.radius_xl.into(),
            },
            border: Border {
                radius: match class {
                    Button::Link => corner_radii.radius_0.into(),
                    Button::Card => corner_radii.radius_xs.into(),
                    _ => corner_radii.radius_xl.into(),
                },
                ..Default::default()
            },
            background: match class {
                Button::Link | Button::Text => None,
                Button::LinkActive => Some(Background::Color(component.divider.into())),
                _ => Some(Background::Color(component.base.into())),
            },
            text_color: match class {
                Button::Link | Button::LinkActive => component.base.into(),
                _ => component.on.into(),
            },
            ..iced_button::Style::default()
        };

        match status {
            iced_button::Status::Active => {}
            iced_button::Status::Hovered => {
                appearance.background = match class {
                    Button::Link => None,
                    Button::LinkActive => Some(Background::Color(component.divider.into())),
                    _ => Some(Background::Color(component.hover.into())),
                };
            }
            iced_button::Status::Pressed => {
                appearance.background = match class {
                    Button::Link => None,
                    Button::LinkActive => Some(Background::Color(component.divider.into())),
                    _ => Some(Background::Color(component.pressed.into())),
                };
            }
            iced_button::Status::Disabled => {
                // Card color is not transparent when it isn't clickable
                if matches!(class, Button::Card) {
                    return appearance;
                }
                appearance.background = appearance.background.map(|background| match background {
                    Background::Color(color) => Background::Color(Color {
                        a: color.a * 0.5,
                        ..color
                    }),
                    Background::Gradient(gradient) => {
                        Background::Gradient(gradient.scale_alpha(0.5))
                    }
                });
                appearance.text_color = Color {
                    a: appearance.text_color.a * 0.5,
                    ..appearance.text_color
                };
            }
        };
        appearance
    }
}

impl Button {
    #[allow(clippy::trivially_copy_pass_by_ref)]
    #[allow(clippy::match_same_arms)]
    fn cosmic<'a>(&'a self, theme: &'a Theme) -> &'a CosmicComponent {
        let cosmic = theme.cosmic();
        match self {
            Self::Primary => &cosmic.accent_button,
            Self::Secondary => &theme.current_container().component,
            Self::Positive => &cosmic.success_button,
            Self::Destructive => &cosmic.destructive_button,
            Self::Text => &cosmic.text_button,
            Self::Link => &cosmic.link_button,
            Self::LinkActive => &cosmic.link_button,
            Self::Transparent => &TRANSPARENT_COMPONENT,
            Self::Deactivated => &theme.current_container().component,
            Self::Card => &theme.current_container().component,
            Self::Custom { .. } => &TRANSPARENT_COMPONENT,
        }
    }
}

/*
 * TODO: Checkbox
 */
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Default)]
pub enum Checkbox {
    #[default]
    Primary,
    Secondary,
    Success,
    Danger,
}


impl iced_checkbox::Catalog for Theme {
    type Class<'a> = Checkbox;

    fn default<'a>() -> Self::Class<'a> {
        Checkbox::default()
    }

    #[allow(clippy::too_many_lines)]
    fn style(
        &self,
        class: &Self::Class<'_>,
        status: iced_checkbox::Status,
    ) -> iced_checkbox::Style {
        let cosmic = self.cosmic();

        let corners = &cosmic.corner_radii;

        let disabled = matches!(status, iced_checkbox::Status::Disabled { .. });
        match status {
            iced_checkbox::Status::Active { is_checked }
            | iced_checkbox::Status::Disabled { is_checked } => {
                let mut active = match class {
                    Checkbox::Primary => iced_checkbox::Style {
                        background: Background::Color(if is_checked {
                            cosmic.accent.base.into()
                        } else {
                            self.current_container().small_widget.into()
                        }),
                        icon_color: cosmic.accent.on.into(),
                        border: Border {
                            radius: corners.radius_xs.into(),
                            width: if is_checked { 0.0 } else { 1.0 },
                            color: if is_checked {
                                cosmic.accent.base
                            } else {
                                cosmic.palette.neutral_8
                            }
                            .into(),
                        },

                        text_color: None,
                    },
                    Checkbox::Secondary => iced_checkbox::Style {
                        background: Background::Color(if is_checked {
                            cosmic.background(self.transparent).component.base.into()
                        } else {
                            self.current_container().small_widget.into()
                        }),
                        icon_color: cosmic.background(self.transparent).on.into(),
                        border: Border {
                            radius: corners.radius_xs.into(),
                            width: if is_checked { 0.0 } else { 1.0 },
                            color: cosmic.palette.neutral_8.into(),
                        },
                        text_color: None,
                    },
                    Checkbox::Success => iced_checkbox::Style {
                        background: Background::Color(if is_checked {
                            cosmic.success.base.into()
                        } else {
                            self.current_container().small_widget.into()
                        }),
                        icon_color: cosmic.success.on.into(),
                        border: Border {
                            radius: corners.radius_xs.into(),
                            width: if is_checked { 0.0 } else { 1.0 },
                            color: if is_checked {
                                cosmic.success.base
                            } else {
                                cosmic.palette.neutral_8
                            }
                            .into(),
                        },
                        text_color: None,
                    },
                    Checkbox::Danger => iced_checkbox::Style {
                        background: Background::Color(if is_checked {
                            cosmic.destructive.base.into()
                        } else {
                            self.current_container().small_widget.into()
                        }),
                        icon_color: cosmic.destructive.on.into(),
                        border: Border {
                            radius: corners.radius_xs.into(),
                            width: if is_checked { 0.0 } else { 1.0 },
                            color: if is_checked {
                                cosmic.destructive.base
                            } else {
                                cosmic.palette.neutral_8
                            }
                            .into(),
                        },
                        text_color: None,
                    },
                };
                if disabled {
                    match &mut active.background {
                        Background::Color(color) => {
                            color.a /= 2.;
                        }
                        Background::Gradient(gradient) => {
                            *gradient = gradient.scale_alpha(0.5);
                        }
                    }
                    if let Some(c) = active.text_color.as_mut() {
                        c.a /= 2.
                    };
                    active.border.color.a /= 2.;
                }
                active
            }
            iced_checkbox::Status::Hovered { is_checked } => {
                let cur_container = self.current_container().small_widget;
                // TODO: this should probably be done with a custom widget instead, or the theme needs more small widget variables.
                let hovered_bg = over(cosmic.palette.neutral_0.with_alpha(0.1), cur_container);
                match class {
                    Checkbox::Primary => iced_checkbox::Style {
                        background: Background::Color(if is_checked {
                            cosmic.accent.hover_state_color().into()
                        } else {
                            hovered_bg.into()
                        }),
                        icon_color: cosmic.accent.on.into(),
                        border: Border {
                            radius: corners.radius_xs.into(),
                            width: if is_checked { 0.0 } else { 1.0 },
                            color: if is_checked {
                                cosmic.accent.base
                            } else {
                                cosmic.palette.neutral_8
                            }
                            .into(),
                        },
                        text_color: None,
                    },
                    Checkbox::Secondary => iced_checkbox::Style {
                        background: Background::Color(if is_checked {
                            self.current_container().component.hover.into()
                        } else {
                            hovered_bg.into()
                        }),
                        icon_color: self.current_container().on.into(),
                        border: Border {
                            radius: corners.radius_xs.into(),
                            width: if is_checked { 0.0 } else { 1.0 },
                            color: if is_checked {
                                self.current_container().base
                            } else {
                                cosmic.palette.neutral_8
                            }
                            .into(),
                        },
                        text_color: None,
                    },
                    Checkbox::Success => iced_checkbox::Style {
                        background: Background::Color(if is_checked {
                            cosmic.success.hover.into()
                        } else {
                            hovered_bg.into()
                        }),
                        icon_color: cosmic.success.on.into(),
                        border: Border {
                            radius: corners.radius_xs.into(),
                            width: if is_checked { 0.0 } else { 1.0 },
                            color: if is_checked {
                                cosmic.success.base
                            } else {
                                cosmic.palette.neutral_8
                            }
                            .into(),
                        },
                        text_color: None,
                    },
                    Checkbox::Danger => iced_checkbox::Style {
                        background: Background::Color(if is_checked {
                            cosmic.destructive.hover.into()
                        } else {
                            hovered_bg.into()
                        }),
                        icon_color: cosmic.destructive.on.into(),
                        border: Border {
                            radius: corners.radius_xs.into(),
                            width: if is_checked { 0.0 } else { 1.0 },
                            color: if is_checked {
                                cosmic.destructive.base
                            } else {
                                cosmic.palette.neutral_8
                            }
                            .into(),
                        },
                        text_color: None,
                    },
                }
            }
        }
    }
}

/*
 * TODO: Container
 */
#[derive(Default)]
pub enum Container<'a> {
    WindowBackground,
    Background,
    Card,
    ContextDrawer {
        transparent: bool,
    },
    Custom(Box<dyn Fn(&Theme) -> iced_container::Style + 'a>),
    Dialog(bool),
    Dropdown,
    HeaderBar {
        focused: bool,
        sharp_corners: bool,
        transparent: bool,
    },
    List,
    Primary,
    Secondary,
    Tooltip,
    #[default]
    Transparent,
}

impl<'a> Container<'a> {
    pub fn custom<F: Fn(&Theme) -> iced_container::Style + 'a>(f: F) -> Self {
        Self::Custom(Box::new(f))
    }

    #[must_use]
    pub fn background(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style {
        iced_container::Style {
            icon_color: Some(Color::from(theme.background(transparent).on)),
            text_color: Some(Color::from(theme.background(transparent).on)),
            background: Some(iced::Background::Color(
                theme.background(transparent).base.into(),
            )),
            border: Border {
                radius: theme.corner_radii.radius_s.into(),
                ..Default::default()
            },
            shadow: Shadow::default(),
            snap: true,
        }
    }

    #[must_use]
    pub fn primary(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style {
        iced_container::Style {
            icon_color: Some(Color::from(theme.primary(transparent).on)),
            text_color: Some(Color::from(theme.primary(transparent).on)),
            background: Some(iced::Background::Color(
                theme.primary(transparent).base.into(),
            )),
            border: Border {
                radius: theme.corner_radii.radius_s.into(),
                ..Default::default()
            },
            shadow: Shadow::default(),
            snap: true,
        }
    }

    #[must_use]
    pub fn secondary(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style {
        iced_container::Style {
            icon_color: Some(Color::from(theme.secondary(transparent).on)),
            text_color: Some(Color::from(theme.secondary(transparent).on)),
            background: Some(iced::Background::Color(
                theme.secondary(transparent).base.into(),
            )),
            border: Border {
                radius: theme.corner_radii.radius_s.into(),
                ..Default::default()
            },
            shadow: Shadow::default(),
            snap: true,
        }
    }
}

impl<'a> From<iced_container::StyleFn<'a, Theme>> for Container<'a> {
    fn from(value: iced_container::StyleFn<'a, Theme>) -> Self {
        Self::custom(value)
    }
}

impl iced_container::Catalog for Theme {
    type Class<'a> = Container<'a>;

    fn default<'a>() -> Self::Class<'a> {
        Container::default()
    }

    fn style(&self, class: &Self::Class<'_>) -> iced_container::Style {
        let cosmic = self.cosmic();

        // Ensures visually aligned radii for content and window corners
        let window_corner_radius = cosmic.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 });

        match class {
            Container::Transparent => {
                let component = &self.current_container().component;

                iced_container::Style {
                    icon_color: Some(component.on.into()),
                    text_color: Some(component.on.into()),
                    background: None,
                    border: Border {
                        radius: 0.into(),
                        ..Default::default()
                    },
                    shadow: Shadow::default(),
                    snap: true,
                }
            }

            Container::Custom(f) => f(self),

            Container::WindowBackground => iced_container::Style {
                icon_color: Some(Color::from(cosmic.background(self.transparent).on)),
                text_color: Some(Color::from(cosmic.background(self.transparent).on)),
                background: Some(iced::Background::Color(
                    cosmic.background(self.transparent).base.into(),
                )),
                border: Border {
                    radius: [
                        cosmic.corner_radii.radius_0[0],
                        cosmic.corner_radii.radius_0[1],
                        window_corner_radius[2],
                        window_corner_radius[3],
                    ]
                    .into(),
                    ..Default::default()
                },
                shadow: Shadow::default(),
                snap: true,
            },

            Container::List => {
                let component = &self.current_container().component;
                iced_container::Style {
                    icon_color: Some(component.on.into()),
                    text_color: Some(component.on.into()),
                    background: Some(Background::Color(component.base.into())),
                    border: iced::Border {
                        radius: cosmic.corner_radii.radius_s.into(),
                        ..Default::default()
                    },
                    shadow: Shadow::default(),
                    snap: true,
                }
            }

            Container::HeaderBar {
                focused,
                sharp_corners,
                transparent,
            } => {
                let (icon_color, text_color) = if *focused {
                    (
                        Color::from(cosmic.accent_text_color()),
                        Color::from(cosmic.background(self.transparent).on),
                    )
                } else {
                    use crate::ext::ColorExt;
                    let unfocused_color =
                        Color::from(cosmic.background(self.transparent).component.on)
                            .blend_alpha(cosmic.background(self.transparent).base.into(), 0.5);
                    (unfocused_color, unfocused_color)
                };

                iced_container::Style {
                    icon_color: Some(icon_color),
                    text_color: Some(text_color),
                    background: if *transparent {
                        None
                    } else {
                        Some(iced::Background::Color(
                            cosmic.background(self.transparent).base.into(),
                        ))
                    },
                    border: Border {
                        radius: [
                            if *sharp_corners {
                                cosmic.corner_radii.radius_0[0]
                            } else {
                                window_corner_radius[0]
                            },
                            if *sharp_corners {
                                cosmic.corner_radii.radius_0[1]
                            } else {
                                window_corner_radius[1]
                            },
                            cosmic.corner_radii.radius_0[2],
                            cosmic.corner_radii.radius_0[3],
                        ]
                        .into(),
                        ..Default::default()
                    },
                    snap: true,
                    shadow: Shadow::default(),
                }
            }

            Container::ContextDrawer { transparent } => {
                let mut a = Container::primary(cosmic, self.transparent && *transparent);

                if cosmic.is_high_contrast {
                    a.border.width = 1.;
                    a.border.color = cosmic.primary(self.transparent).divider.into();
                }
                a
            }

            Container::Background => Container::background(cosmic, self.transparent),

            Container::Primary => Container::primary(cosmic, self.transparent),

            Container::Secondary => Container::secondary(cosmic, self.transparent),

            Container::Dropdown => iced_container::Style {
                icon_color: None,
                text_color: None,
                background: Some(iced::Background::Color(cosmic.bg_component_color().into())),
                border: Border {
                    color: cosmic.bg_component_divider().into(),
                    width: 1.0,
                    radius: cosmic.corner_radii.radius_s.into(),
                },
                shadow: Shadow::default(),
                snap: true,
            },

            Container::Tooltip => iced_container::Style {
                icon_color: None,
                text_color: None,
                background: Some(iced::Background::Color(cosmic.palette.neutral_2.into())),
                border: Border {
                    radius: cosmic.corner_radii.radius_l.into(),
                    ..Default::default()
                },
                shadow: Shadow::default(),
                snap: true,
            },

            Container::Card => {
                let cosmic = self.cosmic();

                match self.layer {
                    cosmic_theme::Layer::Background => iced_container::Style {
                        icon_color: Some(Color::from(
                            cosmic.background(self.transparent).component.on,
                        )),
                        text_color: Some(Color::from(
                            cosmic.background(self.transparent).component.on,
                        )),
                        background: Some(iced::Background::Color(
                            cosmic.background(self.transparent).component.base.into(),
                        )),
                        border: Border {
                            radius: cosmic.corner_radii.radius_s.into(),
                            ..Default::default()
                        },
                        shadow: Shadow::default(),
                        snap: true,
                    },
                    cosmic_theme::Layer::Primary => iced_container::Style {
                        icon_color: Some(Color::from(
                            cosmic.primary(self.transparent).component.on,
                        )),
                        text_color: Some(Color::from(
                            cosmic.primary(self.transparent).component.on,
                        )),
                        background: Some(iced::Background::Color(
                            cosmic.primary(self.transparent).component.base.into(),
                        )),
                        border: Border {
                            radius: cosmic.corner_radii.radius_s.into(),
                            ..Default::default()
                        },
                        shadow: Shadow::default(),
                        snap: true,
                    },
                    cosmic_theme::Layer::Secondary => iced_container::Style {
                        icon_color: Some(Color::from(
                            cosmic.secondary(self.transparent).component.on,
                        )),
                        text_color: Some(Color::from(
                            cosmic.secondary(self.transparent).component.on,
                        )),
                        background: Some(iced::Background::Color(
                            cosmic.secondary(self.transparent).component.base.into(),
                        )),
                        border: Border {
                            radius: cosmic.corner_radii.radius_s.into(),
                            ..Default::default()
                        },
                        shadow: Shadow::default(),
                        snap: true,
                    },
                }
            }

            Container::Dialog(is_overlay) => iced_container::Style {
                icon_color: Some(Color::from(cosmic.primary(self.transparent).on)),
                text_color: Some(Color::from(cosmic.primary(self.transparent).on)),
                background: Some(iced::Background::Color(
                    cosmic.primary(self.transparent && !is_overlay).base.into(),
                )),
                border: Border {
                    color: cosmic
                        .primary(self.transparent && !is_overlay)
                        .divider
                        .into(),
                    width: 1.0,
                    radius: cosmic.corner_radii.radius_m.into(),
                },
                shadow: Shadow {
                    color: cosmic.shade.into(),
                    offset: Vector::new(0.0, 4.0),
                    blur_radius: 16.0,
                },
                snap: true,
            },
        }
    }
}

#[derive(Default)]
pub enum Slider {
    #[default]
    Standard,
    Custom {
        active: Rc<dyn Fn(&Theme) -> slider::Style>,
        hovered: Rc<dyn Fn(&Theme) -> slider::Style>,
        dragging: Rc<dyn Fn(&Theme) -> slider::Style>,
    },
}

/*
 * Slider
 */
impl slider::Catalog for Theme {
    type Class<'a> = Slider;

    fn default<'a>() -> Self::Class<'a> {
        Slider::default()
    }

    fn style(&self, class: &Self::Class<'_>, status: slider::Status) -> slider::Style {
        let cosmic: &cosmic_theme::Theme = self.cosmic();
        let hc = self.theme_type.is_high_contrast();
        let is_dark = self.theme_type.is_dark();

        let mut appearance = match class {
            Slider::Standard =>
            //TODO: no way to set rail thickness
            {
                let (active_track, inactive_track) = if hc {
                    (
                        cosmic.accent_text_color(),
                        if is_dark {
                            cosmic.palette.neutral_5
                        } else {
                            cosmic.palette.neutral_3
                        },
                    )
                } else {
                    (cosmic.accent.base, cosmic.palette.neutral_6)
                };
                slider::Style {
                    rail: Rail {
                        backgrounds: (
                            Background::Color(active_track.into()),
                            Background::Color(inactive_track.into()),
                        ),
                        border: Border {
                            radius: cosmic.corner_radii.radius_xs.into(),
                            color: if hc && !is_dark {
                                self.current_container().component.border.into()
                            } else {
                                Color::TRANSPARENT
                            },
                            width: if hc && !is_dark { 1. } else { 0. },
                        },
                        width: 4.0,
                    },

                    handle: slider::Handle {
                        shape: slider::HandleShape::Rectangle {
                            height: 20,
                            width: 20,
                            border_radius: cosmic.corner_radii.radius_m.into(),
                        },
                        border_color: Color::TRANSPARENT,
                        border_width: 0.0,
                        background: Background::Color(cosmic.accent.base.into()),
                    },

                    breakpoint: slider::Breakpoint {
                        color: cosmic.on_bg_color().into(),
                    },
                }
            }
            Slider::Custom { active, .. } => active(self),
        };
        match status {
            slider::Status::Active => appearance,
            slider::Status::Hovered => match class {
                Slider::Standard => {
                    appearance.handle.shape = slider::HandleShape::Rectangle {
                        height: 26,
                        width: 26,
                        border_radius: cosmic.corner_radii.radius_m.into(),
                    };
                    appearance.handle.border_width = 3.0;
                    appearance.handle.border_color =
                        self.cosmic().palette.neutral_10.with_alpha(0.1).into();
                    appearance
                }
                Slider::Custom { hovered, .. } => hovered(self),
            },
            slider::Status::Dragged => match class {
                Slider::Standard => {
                    let mut style = {
                        appearance.handle.shape = slider::HandleShape::Rectangle {
                            height: 26,
                            width: 26,
                            border_radius: cosmic.corner_radii.radius_m.into(),
                        };
                        appearance.handle.border_width = 3.0;
                        appearance.handle.border_color =
                            self.cosmic().palette.neutral_10.with_alpha(0.1).into();
                        appearance
                    };
                    style.handle.border_color =
                        self.cosmic().palette.neutral_10.with_alpha(0.2).into();
                    style
                }
                Slider::Custom { dragging, .. } => dragging(self),
            },
        }
    }
}

impl menu::Catalog for Theme {
    type Class<'a> = ();

    fn default<'a>() -> <Self as menu::Catalog>::Class<'a> {}

    fn style(&self, _class: &<Self as menu::Catalog>::Class<'_>) -> menu::Style {
        let cosmic = self.cosmic();

        menu::Style {
            text_color: cosmic.on_bg_color().into(),
            background: Background::Color(cosmic.background(self.transparent).base.into()),
            border: Border {
                radius: cosmic.corner_radii.radius_m.into(),
                ..Default::default()
            },
            selected_text_color: cosmic.accent_text_color().into(),
            selected_background: Background::Color(
                cosmic.background(self.transparent).component.hover.into(),
            ),
            shadow: Default::default(),
        }
    }
}

impl pick_list::Catalog for Theme {
    type Class<'a> = ();

    fn default<'a>() -> <Self as pick_list::Catalog>::Class<'a> {}

    fn style(
        &self,
        _class: &<Self as pick_list::Catalog>::Class<'_>,
        status: pick_list::Status,
    ) -> pick_list::Style {
        let cosmic = &self.cosmic();
        let hc = cosmic.is_high_contrast;
        let appearance = pick_list::Style {
            text_color: cosmic.on_bg_color().into(),
            background: Color::TRANSPARENT.into(),
            placeholder_color: cosmic.on_bg_color().into(),
            border: Border {
                radius: cosmic.corner_radii.radius_m.into(),
                width: if hc { 1. } else { 0. },
                color: if hc {
                    self.current_container().component.border.into()
                } else {
                    Color::TRANSPARENT
                },
            },
            // icon_size: 0.7, // TODO: how to replace
            handle_color: cosmic.on_bg_color().into(),
        };

        match status {
            pick_list::Status::Active => appearance,
            pick_list::Status::Hovered => pick_list::Style {
                background: Background::Color(cosmic.background(self.transparent).base.into()),
                ..appearance
            },
            pick_list::Status::Opened { is_hovered: _ } => appearance,
        }
    }
}

/*
 * TODO: Radio
 */
impl radio::Catalog for Theme {
    type Class<'a> = ();

    fn default<'a>() -> Self::Class<'a> {}

    fn style(&self, _class: &Self::Class<'_>, status: radio::Status) -> radio::Style {
        let cur_container = self.current_container();
        let theme = self.cosmic();

        match status {
            radio::Status::Active { is_selected } => radio::Style {
                background: if is_selected {
                    Color::from(theme.accent.base).into()
                } else {
                    // TODO: this seems to be defined weirdly in FIGMA
                    Color::from(cur_container.small_widget).into()
                },
                dot_color: theme.accent.on.into(),
                border_width: 1.0,
                border_color: if is_selected {
                    Color::from(theme.accent.base)
                } else {
                    Color::from(theme.palette.neutral_8)
                },
                text_color: None,
            },
            radio::Status::Hovered { is_selected } => {
                let bg = if is_selected {
                    theme.accent.base
                } else {
                    self.current_container().small_widget
                };
                // TODO: this should probably be done with a custom widget instead, or the theme needs more small widget variables.
                let hovered_bg = Color::from(over(theme.palette.neutral_0.with_alpha(0.1), bg));
                radio::Style {
                    background: hovered_bg.into(),
                    dot_color: theme.accent.on.into(),
                    border_width: 1.0,
                    border_color: if is_selected {
                        Color::from(theme.accent.base)
                    } else {
                        Color::from(theme.palette.neutral_8)
                    },
                    text_color: None,
                }
            }
        }
    }
}

/*
 * Toggler
 */
impl toggler::Catalog for Theme {
    type Class<'a> = ();

    fn default<'a>() -> Self::Class<'a> {}

    fn style(&self, _class: &Self::Class<'_>, status: toggler::Status) -> toggler::Style {
        let cosmic = self.cosmic();
        const HANDLE_MARGIN: f32 = 2.0;
        let neutral_10 = cosmic.palette.neutral_10.with_alpha(0.1);

        let mut active = toggler::Style {
            background: if matches!(status, toggler::Status::Active { is_toggled: true }) {
                cosmic.accent.base.into()
            } else if cosmic.is_dark {
                cosmic.palette.neutral_6.into()
            } else {
                cosmic.palette.neutral_5.into()
            },
            foreground: cosmic.palette.neutral_2.into(),
            border_radius: cosmic.radius_xl().into(),
            handle_radius: cosmic
                .radius_xl()
                .map(|x| (x - HANDLE_MARGIN).max(0.0))
                .into(),
            handle_margin: HANDLE_MARGIN,
            background_border_width: 0.0,
            background_border_color: Color::TRANSPARENT,
            foreground_border_width: 0.0,
            foreground_border_color: Color::TRANSPARENT,
            text_color: None,
            padding_ratio: 0.0,
        };
        match status {
            toggler::Status::Active { is_toggled: _ } => active,
            toggler::Status::Hovered { is_toggled: _ } => {
                let is_active = matches!(status, toggler::Status::Hovered { is_toggled: true });
                toggler::Style {
                    background: if is_active {
                        over(neutral_10, cosmic.accent_color())
                    } else {
                        over(
                            neutral_10,
                            if cosmic.is_dark {
                                cosmic.palette.neutral_6
                            } else {
                                cosmic.palette.neutral_5
                            },
                        )
                    }
                    .into(),
                    ..active
                }
            }
            toggler::Status::Disabled { is_toggled: _ } => {
                active.background = active.background.scale_alpha(0.5);
                active.foreground = active.foreground.scale_alpha(0.5);
                active
            }
        }
    }
}

/*
 * TODO: Pane Grid
 */
impl pane_grid::Catalog for Theme {
    type Class<'a> = ();

    fn default<'a>() -> <Self as pane_grid::Catalog>::Class<'a> {}

    fn style(&self, _class: &<Self as pane_grid::Catalog>::Class<'_>) -> pane_grid::Style {
        let theme = self.cosmic();

        pane_grid::Style {
            hovered_region: Highlight {
                background: Background::Color(theme.bg_color().into()),
                border: Border {
                    radius: theme.corner_radii.radius_0.into(),
                    width: 2.0,
                    color: theme.bg_divider().into(),
                },
            },
            picked_split: pane_grid::Line {
                color: theme.accent.base.into(),
                width: 2.0,
            },
            hovered_split: pane_grid::Line {
                color: theme.accent.hover.into(),
                width: 2.0,
            },
        }
    }
}

/*
 * TODO: Progress Bar
 */
#[derive(Default)]
pub enum ProgressBar {
    #[default]
    Primary,
    Success,
    Danger,
    Custom(Box<dyn Fn(&Theme) -> progress_bar::Style>),
}

impl ProgressBar {
    pub fn custom<F: Fn(&Theme) -> progress_bar::Style + 'static>(f: F) -> Self {
        Self::Custom(Box::new(f))
    }
}

impl progress_bar::Catalog for Theme {
    type Class<'a> = ProgressBar;

    fn default<'a>() -> Self::Class<'a> {
        ProgressBar::default()
    }

    fn style(&self, class: &Self::Class<'_>) -> progress_bar::Style {
        let theme = self.cosmic();

        let (active_track, inactive_track) = if theme.is_high_contrast {
            (
                theme.accent_text_color(),
                if theme.is_dark {
                    theme.palette.neutral_6
                } else {
                    theme.palette.neutral_4
                },
            )
        } else {
            (
                theme.accent.base,
                theme.background(self.transparent).divider,
            )
        };
        let border = Border {
            radius: theme.corner_radii.radius_xl.into(),
            color: if theme.is_high_contrast && !theme.is_dark {
                self.current_container().component.border.into()
            } else {
                Color::TRANSPARENT
            },
            width: if theme.is_high_contrast && !theme.is_dark {
                1.
            } else {
                0.
            },
        };
        match class {
            ProgressBar::Primary => progress_bar::Style {
                background: Color::from(inactive_track).into(),
                bar: Color::from(active_track).into(),
                border,
            },
            ProgressBar::Success => progress_bar::Style {
                background: Color::from(inactive_track).into(),
                bar: Color::from(theme.success.base).into(),
                border,
            },
            ProgressBar::Danger => progress_bar::Style {
                background: Color::from(inactive_track).into(),
                bar: Color::from(theme.destructive.base).into(),
                border,
            },
            ProgressBar::Custom(f) => f(self),
        }
    }
}

/*
 * TODO: Rule
 */
#[derive(Default)]
pub enum Rule {
    #[default]
    Default,
    LightDivider,
    HeavyDivider,
    Custom(Box<dyn Fn(&Theme) -> rule::Style>),
}

impl Rule {
    pub fn custom<F: Fn(&Theme) -> rule::Style + 'static>(f: F) -> Self {
        Self::Custom(Box::new(f))
    }
}

impl rule::Catalog for Theme {
    type Class<'a> = Rule;

    fn default<'a>() -> Self::Class<'a> {
        Rule::default()
    }

    fn style(&self, class: &Self::Class<'_>) -> rule::Style {
        match class {
            Rule::Default => rule::Style {
                color: self.current_container().divider.into(),
                radius: 0.0.into(),
                fill_mode: rule::FillMode::Full,
                snap: true,
            },
            Rule::LightDivider => rule::Style {
                color: self.current_container().divider.into(),
                radius: 0.0.into(),
                fill_mode: rule::FillMode::Padded(8),
                snap: true,
            },
            Rule::HeavyDivider => rule::Style {
                color: self.current_container().divider.into(),
                radius: 2.0.into(),
                fill_mode: rule::FillMode::Full,
                snap: true,
            },
            Rule::Custom(f) => f(self),
        }
    }
}

#[derive(Default, Clone, Copy)]
pub enum Scrollable {
    #[default]
    Permanent,
    Minimal,
}

/*
 * TODO: Scrollable
 */
impl scrollable::Catalog for Theme {
    type Class<'a> = Scrollable;

    fn default<'a>() -> Self::Class<'a> {
        Scrollable::default()
    }

    fn style(&self, class: &Self::Class<'_>, status: scrollable::Status) -> scrollable::Style {
        match status {
            scrollable::Status::Active {
                is_horizontal_scrollbar_disabled: _,
                is_vertical_scrollbar_disabled: _,
            } => {
                let cosmic = self.cosmic();
                let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7);
                let neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7);
                let mut a = scrollable::Style {
                    container: iced_container::transparent(self),
                    vertical_rail: scrollable::Rail {
                        border: Border {
                            radius: cosmic.corner_radii.radius_s.into(),
                            ..Default::default()
                        },
                        background: None,
                        scroller: scrollable::Scroller {
                            background: if cosmic.is_dark {
                                neutral_6.into()
                            } else {
                                neutral_5.into()
                            },
                            border: Border {
                                radius: cosmic.corner_radii.radius_s.into(),
                                ..Default::default()
                            },
                        },
                    },
                    horizontal_rail: scrollable::Rail {
                        border: Border {
                            radius: cosmic.corner_radii.radius_s.into(),
                            ..Default::default()
                        },
                        background: None,
                        scroller: scrollable::Scroller {
                            background: if cosmic.is_dark {
                                neutral_6.into()
                            } else {
                                neutral_5.into()
                            },
                            border: Border {
                                radius: cosmic.corner_radii.radius_s.into(),
                                ..Default::default()
                            },
                        },
                    },
                    gap: None,
                    // TODO: what is auto scroll?
                    auto_scroll: AutoScroll {
                        background: Color::TRANSPARENT.into(),
                        border: Border::default(),
                        shadow: Shadow::default(),
                        icon: Color::TRANSPARENT,
                    },
                };
                let small_widget_container = self.current_container().small_widget.with_alpha(0.7);

                if matches!(class, Scrollable::Permanent) {
                    a.horizontal_rail.background =
                        Some(Background::Color(small_widget_container.into()));
                    a.vertical_rail.background =
                        Some(Background::Color(small_widget_container.into()));
                }

                a
            }
            // TODO handle vertical / horizontal
            scrollable::Status::Hovered { .. } | scrollable::Status::Dragged { .. } => {
                let cosmic = self.cosmic();
                let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7);
                let neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7);

                // if is_mouse_over_scrollbar {
                //     let hover_overlay = cosmic.palette.neutral_0.with_alpha(0.2);
                //     neutral_5 = over(hover_overlay, neutral_5);
                // }
                let mut a: scrollable::Style = scrollable::Style {
                    container: iced_container::Style::default(),
                    vertical_rail: scrollable::Rail {
                        border: Border {
                            radius: cosmic.corner_radii.radius_s.into(),
                            ..Default::default()
                        },
                        background: None,
                        scroller: scrollable::Scroller {
                            background: if cosmic.is_dark {
                                neutral_6.into()
                            } else {
                                neutral_5.into()
                            },
                            border: Border {
                                radius: cosmic.corner_radii.radius_s.into(),
                                ..Default::default()
                            },
                        },
                    },
                    horizontal_rail: scrollable::Rail {
                        border: Border {
                            radius: cosmic.corner_radii.radius_s.into(),
                            ..Default::default()
                        },
                        background: None,
                        scroller: scrollable::Scroller {
                            background: if cosmic.is_dark {
                                neutral_6.into()
                            } else {
                                neutral_5.into()
                            },
                            border: Border {
                                radius: cosmic.corner_radii.radius_s.into(),
                                ..Default::default()
                            },
                        },
                    },
                    gap: None,
                    // TODO: what is auto scroll?
                    auto_scroll: AutoScroll {
                        background: Color::TRANSPARENT.into(),
                        border: Border::default(),
                        shadow: Shadow::default(),
                        icon: Color::TRANSPARENT,
                    },
                };

                if matches!(class, Scrollable::Permanent) {
                    let small_widget_container =
                        self.current_container().small_widget.with_alpha(0.7);

                    a.horizontal_rail.background =
                        Some(Background::Color(small_widget_container.into()));
                    a.vertical_rail.background =
                        Some(Background::Color(small_widget_container.into()));
                }

                a
            }
        }
    }
}

#[derive(Clone, Default)]
pub enum Svg {
    /// Apply a custom appearance filter
    Custom(Rc<dyn Fn(&Theme) -> svg::Style>),
    /// No filtering is applied
    #[default]
    Default,
}

impl Svg {
    pub fn custom<F: Fn(&Theme) -> svg::Style + 'static>(f: F) -> Self {
        Self::Custom(Rc::new(f))
    }
}

impl svg::Catalog for Theme {
    type Class<'a> = Svg;

    fn default<'a>() -> Self::Class<'a> {
        Svg::default()
    }

    fn style(&self, class: &Self::Class<'_>, _status: svg::Status) -> svg::Style {
        #[allow(clippy::match_same_arms)]
        match class {
            Svg::Default => svg::Style::default(),
            Svg::Custom(appearance) => appearance(self),
        }
    }
}

/*
 * TODO: Text
 */
#[derive(Clone, Copy, Default)]
pub enum Text {
    Accent,
    #[default]
    Default,
    Color(Color),
    // TODO: Can't use dyn Fn since this must be copy
    Custom(fn(&Theme) -> iced_widget::text::Style),
}

impl From<Color> for Text {
    fn from(color: Color) -> Self {
        Self::Color(color)
    }
}

impl iced_widget::text::Catalog for Theme {
    type Class<'a> = Text;

    fn default<'a>() -> Self::Class<'a> {
        Text::default()
    }

    fn style(&self, class: &Self::Class<'_>) -> iced_widget::text::Style {
        match class {
            Text::Accent => iced_widget::text::Style {
                color: Some(self.cosmic().accent_text_color().into()),
                ..Default::default()
            },
            Text::Default => iced_widget::text::Style {
                color: None,
                ..Default::default()
            },
            Text::Color(c) => iced_widget::text::Style {
                color: Some(*c),
                ..Default::default()
            },
            Text::Custom(f) => f(self),
        }
    }
}

#[derive(Copy, Clone, Default)]
pub enum TextInput {
    #[default]
    Default,
    Search,
}

/*
 * TODO: Text Input
 */
impl text_input::Catalog for Theme {
    type Class<'a> = TextInput;

    fn default<'a>() -> Self::Class<'a> {
        TextInput::default()
    }

    fn style(&self, class: &Self::Class<'_>, status: text_input::Status) -> text_input::Style {
        let palette = self.cosmic();
        let bg = self.current_container().small_widget.with_alpha(0.25);

        let neutral_9 = palette.palette.neutral_9;
        let value = neutral_9.into();
        let placeholder = neutral_9.with_alpha(0.7).into();
        let selection = palette.accent.base.into();

        let mut appearance = match class {
            TextInput::Default => text_input::Style {
                background: Color::from(bg).into(),
                border: Border {
                    radius: palette.corner_radii.radius_s.into(),
                    width: 1.0,
                    color: self.current_container().component.divider.into(),
                },
                icon: self.current_container().on.into(),
                placeholder,
                value,
                selection,
            },
            TextInput::Search => text_input::Style {
                background: Color::from(bg).into(),
                border: Border {
                    radius: palette.corner_radii.radius_m.into(),
                    ..Default::default()
                },
                icon: self.current_container().on.into(),
                placeholder,
                value,
                selection,
            },
        };

        match status {
            text_input::Status::Active => appearance,
            text_input::Status::Hovered => {
                let bg = self.current_container().small_widget.with_alpha(0.25);

                match class {
                    TextInput::Default => text_input::Style {
                        background: Color::from(bg).into(),
                        border: Border {
                            radius: palette.corner_radii.radius_s.into(),
                            width: 1.0,
                            color: self.current_container().on.into(),
                        },
                        icon: self.current_container().on.into(),
                        placeholder,
                        value,
                        selection,
                    },
                    TextInput::Search => text_input::Style {
                        background: Color::from(bg).into(),
                        border: Border {
                            radius: palette.corner_radii.radius_m.into(),
                            ..Default::default()
                        },
                        icon: self.current_container().on.into(),
                        placeholder,
                        value,
                        selection,
                    },
                }
            }
            text_input::Status::Focused { is_hovered: _ } => {
                let bg = self.current_container().small_widget.with_alpha(0.25);

                match class {
                    TextInput::Default => text_input::Style {
                        background: Color::from(bg).into(),
                        border: Border {
                            radius: palette.corner_radii.radius_s.into(),
                            width: 1.0,
                            color: palette.accent.base.into(),
                        },
                        icon: self.current_container().on.into(),
                        placeholder,
                        value,
                        selection,
                    },
                    TextInput::Search => text_input::Style {
                        background: Color::from(bg).into(),
                        border: Border {
                            radius: palette.corner_radii.radius_m.into(),
                            ..Default::default()
                        },
                        icon: self.current_container().on.into(),
                        placeholder,
                        value,
                        selection,
                    },
                }
            }
            text_input::Status::Disabled => {
                appearance.background = match appearance.background {
                    Background::Color(color) => Background::Color(Color {
                        a: color.a * 0.5,
                        ..color
                    }),
                    Background::Gradient(gradient) => {
                        Background::Gradient(gradient.scale_alpha(0.5))
                    }
                };
                appearance.border.color.a /= 2.;
                appearance.icon.a /= 2.;
                appearance.placeholder.a /= 2.;
                appearance.value.a /= 2.;
                appearance
            }
        }
    }
}

#[derive(Default)]
pub enum TextEditor<'a> {
    #[default]
    Default,
    Custom(text_editor::StyleFn<'a, Theme>),
}

impl iced_widget::text_editor::Catalog for Theme {
    type Class<'a> = TextEditor<'a>;

    fn default<'a>() -> Self::Class<'a> {
        TextEditor::default()
    }

    fn style(
        &self,
        class: &Self::Class<'_>,
        status: iced_widget::text_editor::Status,
    ) -> iced_widget::text_editor::Style {
        if let TextEditor::Custom(style) = class {
            return style(self, status);
        }

        let cosmic = self.cosmic();

        let selection = cosmic.accent.base.into();
        let value = cosmic.palette.neutral_9.into();
        let placeholder = cosmic.palette.neutral_9.with_alpha(0.7).into();
        let _icon: Color = cosmic.background(self.transparent).on.into();
        // TODO do we need to add icon color back?

        match status {
            iced_widget::text_editor::Status::Active
            | iced_widget::text_editor::Status::Hovered
            | iced_widget::text_editor::Status::Disabled => iced_widget::text_editor::Style {
                background: iced::Color::from(cosmic.bg_color()).into(),
                border: Border {
                    radius: cosmic.corner_radii.radius_0.into(),
                    width: f32::from(cosmic.space_xxxs()),
                    color: iced::Color::from(cosmic.bg_divider()),
                },
                placeholder,
                value,
                selection,
            },
            iced_widget::text_editor::Status::Focused { is_hovered: _ } => {
                iced_widget::text_editor::Style {
                    background: iced::Color::from(cosmic.bg_color()).into(),
                    border: Border {
                        radius: cosmic.corner_radii.radius_0.into(),
                        width: f32::from(cosmic.space_xxxs()),
                        color: iced::Color::from(cosmic.accent.base),
                    },
                    placeholder,
                    value,
                    selection,
                }
            }
        }
    }
}

#[cfg(feature = "markdown")]
impl iced_widget::markdown::Catalog for Theme {
    fn code_block<'a>() -> <Self as iced_container::Catalog>::Class<'a> {
        Container::custom(|_| iced_container::Style {
            background: Some(iced::color!(0x111111).into()),
            text_color: Some(Color::WHITE),
            border: iced::border::rounded(2),
            ..iced_container::Style::default()
        })
    }
}

impl iced_widget::table::Catalog for Theme {
    type Class<'a> = iced_widget::table::StyleFn<'a, Self>;

    fn default<'a>() -> Self::Class<'a> {
        Box::new(|theme| iced_widget::table::Style {
            separator_x: theme.current_container().divider.into(),
            separator_y: theme.current_container().divider.into(),
        })
    }

    fn style(&self, class: &Self::Class<'_>) -> iced_widget::table::Style {
        class(self)
    }
}

#[cfg(feature = "qr_code")]
impl iced_widget::qr_code::Catalog for Theme {
    type Class<'a> = iced_widget::qr_code::StyleFn<'a, Self>;

    fn default<'a>() -> Self::Class<'a> {
        Box::new(|_theme| iced_widget::qr_code::Style {
            cell: Color::BLACK,
            background: Color::WHITE,
        })
    }

    fn style(&self, class: &Self::Class<'_>) -> iced_widget::qr_code::Style {
        class(self)
    }
}

impl combo_box::Catalog for Theme {}

impl Base for Theme {
    fn default(preference: iced::theme::Mode) -> Self {
        match preference {
            iced::theme::Mode::Light => Theme::light(),
            iced::theme::Mode::Dark | iced::theme::Mode::None => Theme::dark(),
        }
    }

    fn mode(&self) -> iced::theme::Mode {
        if self.theme_type.is_dark() {
            iced::theme::Mode::Dark
        } else {
            iced::theme::Mode::Light
        }
    }

    fn base(&self) -> iced::theme::Style {
        iced::theme::Style {
            background_color: self.cosmic().bg_color().into(),
            text_color: self.cosmic().on_bg_color().into(),
            icon_color: self.cosmic().on_bg_color().into(),
        }
    }

    fn palette(&self) -> Option<iced::theme::Palette> {
        Some(iced::theme::Palette {
            primary: self.cosmic().accent.base.into(),
            success: self.cosmic().success.base.into(),
            warning: self.cosmic().warning.base.into(),
            danger: self.cosmic().destructive.base.into(),
            background: iced::Color::from(self.cosmic().bg_color()),
            text: iced::Color::from(self.cosmic().on_bg_color()),
        })
    }

    fn name(&self) -> &str {
        match &self.theme_type {
            crate::theme::ThemeType::Dark => "Cosmic Dark Theme",
            crate::theme::ThemeType::Light => "Cosmic Light Theme",
            crate::theme::ThemeType::HighContrastDark => "Cosmic High Contrast Dark Theme",
            crate::theme::ThemeType::HighContrastLight => "Cosmic High Contrast Light Theme",
            crate::theme::ThemeType::Custom(_theme) => "Custom Cosmic Theme",
            crate::theme::ThemeType::System { prefer_dark: _, theme } => &theme.name,
        }
    }
}
