// SPDX-License-Identifier: GPL-3.0-or-later
use gettextrs::gettext;

use gtk::subclass::prelude::*;
use gtk::{CompositeTemplate, prelude::*};
use gtk::{gdk, gio, glib};

use crate::export::{Shape, SymbolicColor};
use crate::utils;

mod imp {
    use super::*;

    use std::cell::{Cell, RefCell};

    use glib::Properties;

    // Blue 4 from Palette.
    const DEFAULT_COLOR: gdk::RGBA =
        gdk::RGBA::new(28.0 / 255.0, 113.0 / 255.0, 216.0 / 255.0, 1.0);

    #[derive(Debug, CompositeTemplate, Properties)]
    #[template(resource = "/org/gnome/design/Emblem/ui/generate.ui")]
    #[properties(wrapper_type = super::Generate)]
    pub struct Generate {
        pub last_gradient: Cell<gdk::RGBA>,

        #[property(get, set = Self::set_color, explicit_notify)]
        pub color: Cell<gdk::RGBA>,
        #[property(get, set = Self::set_gradient, explicit_notify)]
        pub gradient: Cell<gdk::RGBA>,
        #[property(name = "symbolic", get, set = Self::set_symbolic, explicit_notify)]
        pub symbolic_bytes: RefCell<Option<glib::Bytes>>,

        #[property(get, set, default)]
        pub symbolic_color: Cell<SymbolicColor>,
        #[property(get, set, default)]
        pub shape: Cell<Shape>,

        #[template_child]
        pub child: TemplateChild<gtk::Widget>,
        #[template_child]
        pub stack: TemplateChild<adw::ViewStack>,
        #[template_child]
        pub symbolic: TemplateChild<gtk::Image>,
        #[template_child]
        pub color_widget: TemplateChild<crate::ColorWidget>,
        #[template_child]
        pub color_button: TemplateChild<crate::ColorButton>,
        #[template_child]
        pub gradient_button: TemplateChild<crate::ColorButton>,
        #[template_child]
        pub color_gradient_button: TemplateChild<crate::ColorButton>,
    }

    impl Default for Generate {
        fn default() -> Self {
            Self {
                color: Cell::new(DEFAULT_COLOR),
                gradient: Cell::new(DEFAULT_COLOR),
                last_gradient: Cell::new(DEFAULT_COLOR),
                symbolic_bytes: RefCell::default(),
                symbolic_color: Cell::default(),
                shape: Cell::default(),
                child: TemplateChild::default(),
                stack: TemplateChild::default(),
                symbolic: TemplateChild::default(),
                color_widget: TemplateChild::default(),
                gradient_button: TemplateChild::default(),
                color_button: TemplateChild::default(),
                color_gradient_button: TemplateChild::default(),
            }
        }
    }

    #[glib::object_subclass]
    impl ObjectSubclass for Generate {
        const NAME: &'static str = "Generate";
        type Type = super::Generate;
        type ParentType = gtk::Widget;

        fn class_init(klass: &mut Self::Class) {
            klass.set_layout_manager_type::<gtk::BinLayout>();
            klass.bind_template();
            klass.bind_template_callbacks();

            klass.install_action_async("select-symbolic", None, |obj, _, _| async move {
                if let Err(err) = obj.select_symbolic().await {
                    log::error!("Could not select symbolic: {err}");
                }
            });
        }

        fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
            obj.init_template();
        }
    }

    #[gtk::template_callbacks]
    impl Generate {
        pub fn set_color(&self, color: gdk::RGBA) {
            if color != self.color.replace(color) {
                self.obj().notify_color();
            }
        }

        pub fn set_gradient(&self, color: gdk::RGBA) {
            if color != self.gradient.replace(color) {
                self.obj().notify_gradient();
            }
        }

        pub fn set_symbolic(&self, bytes: Option<&glib::Bytes>) {
            let obj = self.obj();

            if bytes == self.symbolic_bytes.borrow().as_ref() {
                return;
            }

            self.symbolic_bytes.replace(bytes.cloned());
            self.symbolic.set_visible(bytes.is_some());

            let bytes = bytes.cloned();
            glib::spawn_future_local(glib::clone!(
                #[weak]
                obj,
                async move {
                    let imp = obj.imp();

                    if let Some(bytes) = bytes {
                        match utils::load_symbolic(bytes).await {
                            Ok(paintable) => {
                                imp.symbolic.set_paintable(Some(&paintable));
                                obj.notify_symbolic();
                            }
                            Err(err) => {
                                log::error!("Could not save symbolic: {err}");
                            }
                        }
                    }
                }
            ));
        }

        #[template_callback]
        fn on_notify_color(&self, _args: glib::ParamSpec, button: crate::ColorButton) {
            let widget = self.obj();
            let color = button.color();
            widget.set_color(color);
        }

        #[template_callback]
        fn on_notify_gradient(&self, _args: glib::ParamSpec, button: crate::ColorButton) {
            let widget = self.obj();
            let color = button.color();
            widget.set_gradient(color);
        }

        #[template_callback]
        fn on_notify_color_gradient(&self, _args: glib::ParamSpec, button: crate::ColorButton) {
            let widget = self.obj();
            let color = button.color();
            widget.set_color(color);
            widget.set_gradient(color);
        }

        #[template_callback]
        fn on_notify_visible_child_name(&self, _args: glib::ParamSpec, stack: adw::ViewStack) {
            let widget = self.obj();

            match stack.visible_child_name().unwrap().as_ref() {
                "color" => {
                    self.last_gradient.set(widget.gradient());
                    widget.set_gradient(widget.color());
                }
                "gradient" => {
                    widget.set_gradient(self.last_gradient.get());
                }
                _ => unreachable!(),
            }
        }
    }

    #[glib::derived_properties]
    impl ObjectImpl for Generate {
        fn constructed(&self) {
            let obj = self.obj();
            self.parent_constructed();

            self.last_gradient.set(self.color_widget.gradient());

            obj.set_color(DEFAULT_COLOR);
            obj.set_gradient(DEFAULT_COLOR);

            self.color_gradient_button.set_color(DEFAULT_COLOR);
            self.color_button.set_color(DEFAULT_COLOR);
            self.gradient_button.set_color(DEFAULT_COLOR);

            let symbolic_file =
                gio::File::for_uri("resource:///org/gnome/design/Emblem/camera-flash-symbolic.svg");

            glib::spawn_future_local(glib::clone!(
                #[weak]
                obj,
                async move {
                    if let Ok((bytes, _)) = symbolic_file.load_bytes_future().await {
                        obj.set_symbolic(bytes);
                    }
                }
            ));
        }

        fn dispose(&self) {
            self.dispose_template();
        }
    }
    impl WidgetImpl for Generate {}
}

glib::wrapper! {
    pub struct Generate(ObjectSubclass<imp::Generate>)
        @extends gtk::Widget,
        @implements gtk::ConstraintTarget, gtk::Buildable, gtk::Accessible;
}

impl Default for Generate {
    fn default() -> Self {
        glib::Object::new()
    }
}

impl Generate {
    async fn select_symbolic(&self) -> anyhow::Result<()> {
        let window = self.root().and_downcast::<crate::Window>().unwrap();

        let svg_filter = gtk::FileFilter::new();
        svg_filter.set_name(Some(&gettext("SVG images")));
        svg_filter.add_mime_type("image/svg+xml");

        let filters = gio::ListStore::new::<gtk::FileFilter>();
        filters.append(&svg_filter);

        let dialog = gtk::FileDialog::new();
        dialog.set_title(&gettext("Select Symbolic"));
        dialog.set_filters(Some(&filters));

        let file = match dialog.open_future(Some(&window)).await {
            Err(err) if err.matches(gtk::DialogError::Dismissed) => return Ok(()),
            res => res?,
        };
        let (bytes, _) = file.load_bytes_future().await?;
        self.set_symbolic(bytes);

        Ok(())
    }
}
