// Copyright 2024 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only

use std::sync::{Arc, LazyLock};

use cosmic::config::{CosmicTk, FontConfig};
use cosmic::{Apply, Element, Task, widget};
use cosmic_config::ConfigSet;
use mime::FONT;

use crate::app;
use crate::widget::selection_context_item;

use super::{ContextView, Message, drawer};

pub static FONT_SEARCH: LazyLock<widget::Id> = LazyLock::new(|| widget::Id::new("FONT_SEARCH"));

const INTERFACE_FONT: &str = "interface_font";
const MONOSPACE_FONT: &str = "monospace_font";

pub fn load_font_families() -> (Vec<Arc<str>>, Vec<Arc<str>>) {
    let mut font_system = cosmic::iced::advanced::graphics::text::font_system()
        .write()
        .unwrap();

    let (mut interface, mut mono): (Vec<Arc<str>>, Vec<Arc<str>>) =
        font_system.raw().db().faces().fold(
            (Vec::new(), Vec::new()),
            |(mut interface, mut mono), face| {
                if face.stretch != fontdb::Stretch::Normal || face.style != fontdb::Style::Normal {
                    return (interface, mono);
                }

                let font_name = match face.families.first() {
                    Some(name) => &name.0,
                    None => return (interface, mono),
                };

                if face.monospaced {
                    if mono.last().is_none_or(|name| &**name != font_name.as_str()) {
                        mono.push(Arc::from(font_name.as_str()));
                    }
                } else if interface
                    .last()
                    .is_none_or(|name| &**name != font_name.as_str())
                {
                    interface.push(Arc::from(font_name.as_str()));
                }

                (interface, mono)
            },
        );

    interface.sort_unstable();
    interface.dedup();
    mono.sort_unstable();
    mono.dedup();

    (interface, mono)
}

#[derive(Debug)]
pub struct Model {
    font_search: String,
    font_filter: Vec<Arc<str>>,

    interface_font_families: Vec<Arc<str>>,
    pub interface_font: FontConfig,

    monospace_font_families: Vec<Arc<str>>,
    pub monospace_font: FontConfig,
}

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

impl Model {
    pub fn new() -> Model {
        Model {
            font_filter: Vec::new(),
            font_search: String::new(),
            interface_font_families: Vec::new(),
            interface_font: cosmic::config::interface_font(),
            monospace_font_families: Vec::new(),
            monospace_font: cosmic::config::monospace_font(),
        }
    }

    pub fn reset(&mut self) {
        self.font_search.clear();
        self.font_filter.clear();
    }

    pub fn font_loaded(
        &mut self,
        mono: Vec<Arc<str>>,
        interface: Vec<Arc<str>>,
    ) -> Task<crate::app::Message> {
        self.interface_font_families = interface;
        self.monospace_font_families = mono;

        Task::none()
    }

    pub fn select(
        &mut self,
        font: String,
        context_view: &ContextView,
    ) -> Option<Task<app::Message>> {
        match *context_view {
            ContextView::MonospaceFont => {
                self.monospace_font = FontConfig {
                    family: font.to_string(),
                    weight: cosmic::iced::font::Weight::Normal,
                    style: cosmic::iced::font::Style::Normal,
                    stretch: cosmic::iced::font::Stretch::Normal,
                };

                update_config(MONOSPACE_FONT, self.monospace_font.clone());
                None
            }
            ContextView::SystemFont => {
                self.interface_font = FontConfig {
                    family: font.to_string(),
                    weight: cosmic::iced::font::Weight::Normal,
                    style: cosmic::iced::font::Style::Normal,
                    stretch: cosmic::iced::font::Stretch::Normal,
                };
                update_config(INTERFACE_FONT, self.interface_font.clone());
                tokio::spawn(async move {
                    set_gnome_font_name(font.as_ref()).await;
                });
                None
            }
            _ => None,
        }
    }

    pub fn search(&mut self, input: String, context_view: &ContextView) -> Task<app::Message> {
        self.font_search = input.to_lowercase();
        self.font_filter.clear();

        let mut result: Option<Vec<Arc<str>>> = None;

        if let Some(fonts) = self.current_font_family(context_view) {
            result = Some(
                fonts
                    .iter()
                    .filter(|f| f.to_lowercase().contains(&self.font_search))
                    .cloned()
                    .collect(),
            );
        }

        if let Some(fonts) = result.as_mut() {
            self.font_filter.append(fonts);
        }
        Task::none()
    }

    pub fn search_input(&self) -> Element<'_, crate::pages::Message> {
        widget::search_input("", &self.font_search)
            .on_input(|input| Message::DrawerFont(drawer::FontMessage::Search(input)))
            .on_clear(Message::DrawerFont(drawer::FontMessage::Search(
                String::new(),
            )))
            .id(FONT_SEARCH.clone())
            .apply(Element::from)
            .map(crate::pages::Message::Appearance)
    }

    pub fn selection_context(
        &self,
        context_view: &ContextView,
        callback: impl Fn(Arc<str>) -> super::Message,
    ) -> Element<'_, super::Message> {
        let (mut families, current_font) = match *context_view {
            ContextView::MonospaceFont => {
                (&self.monospace_font_families, &self.monospace_font.family)
            }
            ContextView::SystemFont => (&self.interface_font_families, &self.interface_font.family),
            _ => (&self.monospace_font_families, &self.monospace_font.family),
        };

        if !self.font_filter.is_empty() {
            families = &self.font_filter;
        }

        let list = families.iter().fold(
            widget::list_column::with_capacity(families.len()),
            |list, family| {
                let selected = &**family == current_font;
                list.add(selection_context_item(
                    family,
                    selected,
                    callback(family.clone()),
                ))
            },
        );
        list.into()
    }

    fn current_font_family(&self, context_view: &ContextView) -> Option<&Vec<Arc<str>>> {
        match *context_view {
            ContextView::SystemFont => Some(&self.interface_font_families),
            ContextView::MonospaceFont => Some(&self.monospace_font_families),
            _ => None,
        }
    }
}

fn update_config(variant: &str, font: FontConfig) {
    if let Ok(config) = CosmicTk::config() {
        _ = config.set(variant, font);
    }
}

/// Set the preferred icon theme for GNOME/GTK applications.
pub async fn set_gnome_font_name(font_name: &str) {
    let _res = tokio::process::Command::new("gsettings")
        .args(["set", "org.gnome.desktop.interface", "font-name", font_name])
        .status()
        .await;
}
