// SPDX-License-Identifier: GPL-3.0-or-later
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::{gdk, gio, glib};

use crate::application::Application;
use crate::config::PROFILE;
use crate::image;

const FAVICON_SIZES: [u32; 4] = [32, 128, 180, 192];
const SVG_MIME_TYPE: &str = "image/svg+xml";

mod imp {
    use super::*;

    use adw::subclass::prelude::AdwApplicationWindowImpl;
    use gtk::CompositeTemplate;

    #[derive(Debug, Default, CompositeTemplate)]
    #[template(resource = "/org/gnome/design/Emblem/ui/window.ui")]
    pub struct Window {
        #[template_child]
        pub generate: TemplateChild<crate::Generate>,
        #[template_child]
        pub export: TemplateChild<crate::Export>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for Window {
        const NAME: &'static str = "Window";
        type Type = super::Window;
        type ParentType = adw::ApplicationWindow;

        fn class_init(klass: &mut Self::Class) {
            klass.bind_template();

            klass.install_action_async("win.generate-favicons", None, async |obj, _, _| {
                if let Err(err) = obj.generate_favicons().await {
                    log::error!("Could not generate favicons: {err}");
                }
            });

            klass.install_action_async("save-png", None, async |obj, _, _| {
                if let Err(err) = obj.imp().export.save_png_action().await {
                    log::error!("Could not save png: {err}");
                }
            });

            klass.install_action_async("save-svg", None, async |obj, _, _| {
                if let Err(err) = obj.imp().export.save_svg_action().await {
                    log::error!("Could not save svg: {err}");
                }
            });
        }

        // You must call `Widget`'s `init_template()` within `instance_init()`.
        fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
            obj.init_template();
        }
    }

    impl ObjectImpl for Window {
        fn constructed(&self) {
            let obj = self.obj();
            self.parent_constructed();

            // Devel Profile
            if PROFILE == "Devel" {
                obj.add_css_class("devel");
            }

            let formats = gdk::ContentFormats::builder()
                .add_type(gio::File::static_type())
                .add_mime_type(SVG_MIME_TYPE)
                .build();
            let target = gtk::DropTargetAsync::new(Some(formats), gdk::DragAction::COPY);

            target.connect_drop(glib::clone!(
                #[weak]
                obj,
                #[upgrade_or]
                true,
                move |_target, drop, _x, _y| {
                    glib::spawn_future_local(glib::clone!(
                        #[weak]
                        obj,
                        #[strong]
                        drop,
                        async move {
                            match read_drop(&drop).await {
                                Ok(bytes) => {
                                    drop.finish(gdk::DragAction::COPY);
                                    obj.imp().generate.set_symbolic(bytes);
                                }
                                Err(err) => {
                                    drop.finish(gdk::DragAction::empty());
                                    log::error!("Could not perform drop: {err}");
                                }
                            }
                        }
                    ));
                    true
                }
            ));

            obj.add_controller(target);
        }
    }

    impl WidgetImpl for Window {}
    impl WindowImpl for Window {}
    impl ApplicationWindowImpl for Window {}
    impl AdwApplicationWindowImpl for Window {}
}

glib::wrapper! {
    pub struct Window(ObjectSubclass<imp::Window>)
        @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow,
        @implements gio::ActionMap, gio::ActionGroup, gtk::ConstraintTarget, gtk::Accessible, gtk::Buildable, gtk::ShortcutManager, gtk::Native, gtk::Root;
}

impl Window {
    pub fn new(app: &Application) -> Self {
        glib::Object::builder().property("application", app).build()
    }

    async fn generate_favicons(&self) -> anyhow::Result<()> {
        let imp = self.imp();

        let dialog = gtk::FileDialog::new();
        let dest_folder = match dialog.select_folder_future(Some(self)).await {
            Err(err) if err.matches(gtk::DialogError::Dismissed) => return Ok(()),
            res => res?,
        };

        for size in FAVICON_SIZES {
            let dest = dest_folder.child(format!("favicon-{size}x{size}.png"));
            let config = image::Config {
                size,
                border_radius: size / 8,
                symbolic: imp.generate.symbolic(),
                // TODO why is it here?
                symbolic_color: imp.export.symbolic_color(),
                gradient_start: imp.generate.color(),
                gradient_end: imp.generate.gradient(),
            };
            image::save_png(&config, &dest).await?;
        }

        {
            const SVG_SIZE: u32 = 256;
            let dest = dest_folder.child("favicon.svg");
            let config = image::Config {
                size: SVG_SIZE,
                border_radius: SVG_SIZE / 8,
                symbolic: imp.generate.symbolic(),
                symbolic_color: imp.export.symbolic_color(),
                gradient_start: imp.generate.color(),
                gradient_end: imp.generate.gradient(),
            };
            image::save_svg(&config, &dest).await?;
        }

        Ok(())
    }
}

async fn read_drop(drop: &gdk::Drop) -> anyhow::Result<glib::Bytes> {
    if let Ok((input, mime)) = drop
        .read_future(&[SVG_MIME_TYPE], glib::Priority::DEFAULT)
        .await
        && mime.as_str() == SVG_MIME_TYPE
    {
        let bytes = crate::utils::read_stream(input).await?;
        return Ok(glib::Bytes::from_owned(bytes));
    }

    if let Ok(value) = drop
        .read_value_future(gio::File::static_type(), glib::Priority::DEFAULT)
        .await
    {
        let file = value.get::<gio::File>()?;
        let file_info = file
            .query_info_future(
                gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
                gio::FileQueryInfoFlags::NONE,
                glib::Priority::default(),
            )
            .await?;
        if let Some(mime) = file_info.content_type()
            && mime == SVG_MIME_TYPE
        {
            let (bytes, _) = file.load_contents_future().await?;
            return Ok(glib::Bytes::from_owned(bytes));
        }
    }

    anyhow::bail!("Could not read unknown drop type");
}
