use ashpd::{desktop::Color, WindowIdentifier};
use gtk::{gdk, glib, prelude::*, subclass::prelude::*};

use super::colour;

mod imp {
    use std::cell::Cell;

    use colour::try_parse_rgba;

    use super::*;

    #[derive(Debug, gtk::CompositeTemplate, glib::Properties)]
    #[properties(wrapper_type = super::ColourEntry)]
    #[template(resource = "/org/gnome/design/Contrast/colour_entry.ui")]
    pub struct ColourEntry {
        #[property(get, set = Self::set_colour, explicit_notify)]
        pub colour: Cell<gdk::RGBA>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for ColourEntry {
        const NAME: &'static str = "ColourEntry";
        type ParentType = gtk::Entry;
        type Type = super::ColourEntry;
        type Interfaces = (gtk::Accessible,);

        fn new() -> Self {
            Self {
                colour: Cell::new(gdk::RGBA::BLACK),
            }
        }

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

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

    #[glib::derived_properties]
    impl ObjectImpl for ColourEntry {
        fn dispose(&self) {
            self.dispose_template();
        }

        fn constructed(&self) {
            let obj = self.obj();
            self.parent_constructed();
            obj.set_direction(gtk::TextDirection::Ltr);
            obj.set_width_chars(7);
            obj.set_max_width_chars(7);

            obj.bind_property("colour", &*obj, "text")
                .transform_to(move |_, colour: &gdk::RGBA| {
                    let hex_colour = colour::rgba_to_hex(colour);
                    Some(hex_colour.to_value())
                })
                .transform_from(move |_, text: &str| {
                    try_parse_rgba(text).ok().map(|c| c.to_value())
                })
                .sync_create()
                .bidirectional()
                .build();
        }
    }

    impl WidgetImpl for ColourEntry {}

    impl EntryImpl for ColourEntry {}

    impl AccessibleImpl for ColourEntry {
        fn platform_state(&self, state: gtk::AccessiblePlatformState) -> bool {
            self.obj().delegate_get_accessible_platform_state(state)
        }
    }

    #[gtk::template_callbacks]
    impl ColourEntry {
        fn set_colour(&self, mut colour: gdk::RGBA) {
            // Resets the alpha to 1.0 to make sure the window doesn't turn transparent
            // the alpha is not used when computing the WCAG value anyway.
            colour.set_alpha(1.0);
            if self.colour.get() == colour {
                return;
            }
            self.colour.set(colour);
            self.obj().notify_colour();
        }

        async fn pick_color(&self) -> ashpd::Result<()> {
            tracing::info!("Picking a color");
            let root = self.obj().root().unwrap();

            let identifier = WindowIdentifier::from_native(&root).await;
            let color = Color::pick()
                .identifier(identifier)
                .send()
                .await?
                .response()?;
            self.set_colour(gdk::RGBA::from(color));
            Ok(())
        }

        #[template_callback]
        async fn icon_pressed(&self, pos: gtk::EntryIconPosition, _entry: &gtk::Entry) {
            if pos == gtk::EntryIconPosition::Secondary {
                gtk::glib::timeout_future(std::time::Duration::from_millis(200)).await;
                if let Err(err) = self.pick_color().await {
                    tracing::error!("Failed to pick a color {err}");
                }
            }
        }
    }
}

glib::wrapper! {
    pub struct ColourEntry(ObjectSubclass<imp::ColourEntry>)
        @extends gtk::Widget, gtk::Entry,
        @implements gtk::Accessible, gtk::Editable;
}
