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

use super::QRCodeWidget;
use crate::{
    qrcode::{QRCode, QRCodeData},
    widgets::{
        qrcode::create_pages::{TextPage, WiFiPage},
        window::Window,
    },
};

mod imp {
    use std::{cell::RefCell, sync::LazyLock};

    use glib::subclass::Signal;

    use super::*;

    #[derive(gtk::CompositeTemplate, glib::Properties)]
    #[template(resource = "/com/belmoussaoui/Decoder/ui/qrcode_create.ui")]
    #[properties(wrapper_type = super::QRCodeCreatePage)]
    pub struct QRCodeCreatePage {
        pub actions: gio::SimpleActionGroup,
        #[template_child]
        pub qr_widget: TemplateChild<QRCodeWidget>,
        #[template_child]
        pub wifi_page: TemplateChild<WiFiPage>,
        #[template_child]
        pub text_page: TemplateChild<TextPage>,
        #[template_child]
        pub qr_type_stack: TemplateChild<adw::ViewStack>,
        pub semaphore: async_lock::Semaphore,

        #[property(get, set)]
        content: RefCell<String>,
    }

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

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

            klass.install_action_async("create.save", None, async |obj, _, _| {
                if let Err(err) = obj.save().await {
                    tracing::error!("Failed to decode QR: {err}");
                }
            });
            klass.install_action("create.export", None, |page, _, _| {
                page.export();
            });
        }

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

        fn new() -> Self {
            Self {
                actions: Default::default(),
                qr_widget: Default::default(),
                text_page: Default::default(),
                wifi_page: Default::default(),
                content: Default::default(),
                qr_type_stack: Default::default(),
                semaphore: async_lock::Semaphore::new(1),
            }
        }
    }

    #[glib::derived_properties]
    impl ObjectImpl for QRCodeCreatePage {
        fn signals() -> &'static [Signal] {
            static SIGNALS: LazyLock<Vec<Signal>> = LazyLock::new(|| {
                vec![
                    Signal::builder("created")
                        .param_types([QRCode::static_type()])
                        .build(),
                    Signal::builder("exported")
                        .param_types([String::static_type()])
                        .build(),
                ]
            });
            SIGNALS.as_ref()
        }

        fn constructed(&self) {
            let obj = self.obj();
            obj.init_widgets();
            self.parent_constructed();
        }
    }

    impl WidgetImpl for QRCodeCreatePage {}
    impl BinImpl for QRCodeCreatePage {}

    #[gtk::template_callbacks]
    impl QRCodeCreatePage {
        #[template_callback]
        async fn on_content_notify(page: &super::QRCodeCreatePage) {
            let content = page.content();
            let _guard = page.imp().semaphore.acquire().await;
            match blocking::unblock(move || QRCodeData::try_from(content.as_str())).await {
                Ok(qrcode) => page.imp().qr_widget.set_qrcode(qrcode),
                Err(err) => {
                    tracing::error!("Could not regen QR code: {err}");
                    page.root()
                        .unwrap()
                        .downcast_ref::<Window>()
                        .unwrap()
                        .add_toast(&gettext("Could not generate QR code"));
                }
            }
        }

        #[template_callback]
        fn on_visible_child_notify(
            stack: &adw::ViewStack,
            _pspec: glib::ParamSpec,
            page: &super::QRCodeCreatePage,
        ) {
            let stack_page = stack.visible_child().unwrap();
            let content = stack_page.property::<String>("content");
            page.set_content(content);
        }
    }
}

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

impl QRCodeCreatePage {
    fn init_widgets(&self) {
        let imp = self.imp();

        imp.wifi_page
            .bind_property("content", self, "content")
            .build();

        imp.text_page
            .bind_property("content", self, "content")
            .build();
    }

    async fn save(&self) -> anyhow::Result<()> {
        let content = self.content();
        let code = QRCode::create(&content).await?;
        self.root()
            .unwrap()
            .downcast_ref::<Window>()
            .unwrap()
            .add_toast(&gettext("QR Code saved in history"));
        self.emit_created(&code);

        Ok(())
    }

    fn export(&self) {
        let content = self.content();
        self.emit_exported(&content);
    }

    fn emit_created(&self, code: &QRCode) {
        self.emit_by_name::<()>("created", &[code]);
    }

    fn emit_exported(&self, code: &str) {
        self.emit_by_name::<()>("exported", &[&code]);
    }
}
