use std::sync::Arc;

use gpui::{App, FontFeatures, FontWeight};
use project::project_settings::ProjectSettings;
use settings::{EditableSettingControl, Settings, SettingsContent};
use theme::{FontFamilyCache, FontFamilyName, ThemeSettings};
use ui::{
    CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup,
    prelude::*,
};

use crate::EditorSettings;

#[derive(IntoElement)]
pub struct EditorSettingsControls {}

impl Default for EditorSettingsControls {
    fn default() -> Self {
        Self::new()
    }
}

impl EditorSettingsControls {
    pub fn new() -> Self {
        Self {}
    }
}

impl RenderOnce for EditorSettingsControls {
    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
        SettingsContainer::new()
            .child(
                SettingsGroup::new("Font")
                    .child(
                        h_flex()
                            .gap_2()
                            .justify_between()
                            .child(BufferFontFamilyControl)
                            .child(BufferFontWeightControl),
                    )
                    .child(BufferFontSizeControl)
                    .child(BufferFontLigaturesControl),
            )
            .child(SettingsGroup::new("Editor").child(InlineGitBlameControl))
            .child(
                SettingsGroup::new("Gutter").child(
                    h_flex()
                        .gap_2()
                        .justify_between()
                        .child(LineNumbersControl)
                        .child(RelativeLineNumbersControl),
                ),
            )
    }
}

#[derive(IntoElement)]
struct BufferFontFamilyControl;

impl EditableSettingControl for BufferFontFamilyControl {
    type Value = SharedString;

    fn name(&self) -> SharedString {
        "Buffer Font Family".into()
    }

    fn read(cx: &App) -> Self::Value {
        let settings = ThemeSettings::get_global(cx);
        settings.buffer_font.family.clone()
    }

    fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
        settings.theme.buffer_font_family = Some(FontFamilyName(value.into()));
    }
}

impl RenderOnce for BufferFontFamilyControl {
    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
        let value = Self::read(cx);

        h_flex()
            .gap_2()
            .child(Icon::new(IconName::Font))
            .child(DropdownMenu::new(
                "buffer-font-family",
                value,
                ContextMenu::build(window, cx, |mut menu, _, cx| {
                    let font_family_cache = FontFamilyCache::global(cx);

                    for font_name in font_family_cache.list_font_families(cx) {
                        menu = menu.custom_entry(
                            {
                                let font_name = font_name.clone();
                                move |_window, _cx| Label::new(font_name.clone()).into_any_element()
                            },
                            {
                                let font_name = font_name.clone();
                                move |_window, cx| {
                                    Self::write(font_name.clone(), cx);
                                }
                            },
                        )
                    }

                    menu
                }),
            ))
    }
}

#[derive(IntoElement)]
struct BufferFontSizeControl;

impl EditableSettingControl for BufferFontSizeControl {
    type Value = Pixels;

    fn name(&self) -> SharedString {
        "Buffer Font Size".into()
    }

    fn read(cx: &App) -> Self::Value {
        ThemeSettings::get_global(cx).buffer_font_size(cx)
    }

    fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
        settings.theme.buffer_font_size = Some(value.into());
    }
}

impl RenderOnce for BufferFontSizeControl {
    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
        let value = Self::read(cx);

        h_flex()
            .gap_2()
            .child(Icon::new(IconName::FontSize))
            .child(NumericStepper::new(
                "buffer-font-size",
                value.to_string(),
                move |_, _, cx| {
                    Self::write(value - px(1.), cx);
                },
                move |_, _, cx| {
                    Self::write(value + px(1.), cx);
                },
            ))
    }
}

#[derive(IntoElement)]
struct BufferFontWeightControl;

impl EditableSettingControl for BufferFontWeightControl {
    type Value = FontWeight;

    fn name(&self) -> SharedString {
        "Buffer Font Weight".into()
    }

    fn read(cx: &App) -> Self::Value {
        let settings = ThemeSettings::get_global(cx);
        settings.buffer_font.weight
    }

    fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
        settings.theme.buffer_font_weight = Some(value.0);
    }
}

impl RenderOnce for BufferFontWeightControl {
    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
        let value = Self::read(cx);

        h_flex()
            .gap_2()
            .child(Icon::new(IconName::FontWeight))
            .child(DropdownMenu::new(
                "buffer-font-weight",
                value.0.to_string(),
                ContextMenu::build(window, cx, |mut menu, _window, _cx| {
                    for weight in FontWeight::ALL {
                        menu = menu.custom_entry(
                            move |_window, _cx| Label::new(weight.0.to_string()).into_any_element(),
                            {
                                move |_, cx| {
                                    Self::write(weight, cx);
                                }
                            },
                        )
                    }

                    menu
                }),
            ))
    }
}

#[derive(IntoElement)]
struct BufferFontLigaturesControl;

impl EditableSettingControl for BufferFontLigaturesControl {
    type Value = bool;

    fn name(&self) -> SharedString {
        "Buffer Font Ligatures".into()
    }

    fn read(cx: &App) -> Self::Value {
        let settings = ThemeSettings::get_global(cx);
        settings
            .buffer_font
            .features
            .is_calt_enabled()
            .unwrap_or(true)
    }

    fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
        let value = if value { 1 } else { 0 };

        let mut features = settings
            .theme
            .buffer_font_features
            .as_ref()
            .map(|features| features.tag_value_list().to_vec())
            .unwrap_or_default();

        if let Some(calt_index) = features.iter().position(|(tag, _)| tag == "calt") {
            features[calt_index].1 = value;
        } else {
            features.push(("calt".into(), value));
        }

        settings.theme.buffer_font_features = Some(FontFeatures(Arc::new(features)));
    }
}

impl RenderOnce for BufferFontLigaturesControl {
    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
        let value = Self::read(cx);

        CheckboxWithLabel::new(
            "buffer-font-ligatures",
            Label::new(self.name()),
            value.into(),
            |selection, _, cx| {
                Self::write(
                    match selection {
                        ToggleState::Selected => true,
                        ToggleState::Unselected | ToggleState::Indeterminate => false,
                    },
                    cx,
                );
            },
        )
    }
}

#[derive(IntoElement)]
struct InlineGitBlameControl;

impl EditableSettingControl for InlineGitBlameControl {
    type Value = bool;

    fn name(&self) -> SharedString {
        "Inline Git Blame".into()
    }

    fn read(cx: &App) -> Self::Value {
        let settings = ProjectSettings::get_global(cx);
        settings.git.inline_blame.enabled
    }

    fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
        settings
            .git
            .get_or_insert_default()
            .inline_blame
            .get_or_insert_default()
            .enabled = Some(value)
    }
}

impl RenderOnce for InlineGitBlameControl {
    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
        let value = Self::read(cx);

        CheckboxWithLabel::new(
            "inline-git-blame",
            Label::new(self.name()),
            value.into(),
            |selection, _, cx| {
                Self::write(
                    match selection {
                        ToggleState::Selected => true,
                        ToggleState::Unselected | ToggleState::Indeterminate => false,
                    },
                    cx,
                );
            },
        )
    }
}

#[derive(IntoElement)]
struct LineNumbersControl;

impl EditableSettingControl for LineNumbersControl {
    type Value = bool;

    fn name(&self) -> SharedString {
        "Line Numbers".into()
    }

    fn read(cx: &App) -> Self::Value {
        let settings = EditorSettings::get_global(cx);
        settings.gutter.line_numbers
    }

    fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
        settings.editor.gutter.get_or_insert_default().line_numbers = Some(value);
    }
}

impl RenderOnce for LineNumbersControl {
    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
        let value = Self::read(cx);

        CheckboxWithLabel::new(
            "line-numbers",
            Label::new(self.name()),
            value.into(),
            |selection, _, cx| {
                Self::write(
                    match selection {
                        ToggleState::Selected => true,
                        ToggleState::Unselected | ToggleState::Indeterminate => false,
                    },
                    cx,
                );
            },
        )
    }
}

#[derive(IntoElement)]
struct RelativeLineNumbersControl;

impl EditableSettingControl for RelativeLineNumbersControl {
    type Value = bool;

    fn name(&self) -> SharedString {
        "Relative Line Numbers".into()
    }

    fn read(cx: &App) -> Self::Value {
        let settings = EditorSettings::get_global(cx);
        settings.relative_line_numbers
    }

    fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
        settings.editor.relative_line_numbers = Some(value);
    }
}

impl RenderOnce for RelativeLineNumbersControl {
    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
        let value = Self::read(cx);

        DropdownMenu::new(
            "relative-line-numbers",
            if value { "Relative" } else { "Ascending" },
            ContextMenu::build(window, cx, |menu, _window, _cx| {
                menu.custom_entry(
                    |_window, _cx| Label::new("Ascending").into_any_element(),
                    move |_, cx| Self::write(false, cx),
                )
                .custom_entry(
                    |_window, _cx| Label::new("Relative").into_any_element(),
                    move |_, cx| Self::write(true, cx),
                )
            }),
        )
    }
}
