// 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},
    utils,
};

// Widget displayed when a QR code was scanned

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

    use glib::subclass::Signal;

    use super::*;

    #[derive(Default, gtk::CompositeTemplate)]
    #[template(resource = "/com/belmoussaoui/Decoder/ui/qrcode_scanned_page.ui")]
    pub struct QRScannedPage {
        #[template_child]
        pub qr_widget: TemplateChild<QRCodeWidget>,
        #[template_child]
        pub contents: TemplateChild<gtk::TextView>,

        pub actions: gio::SimpleActionGroup,
        pub scanned_code: RefCell<Option<String>>,
    }

    #[gtk::template_callbacks]
    impl QRScannedPage {
        #[template_callback]
        fn on_copy_button_clicked(&self) {
            let buffer = self.contents.buffer();
            let contents = buffer.text(&buffer.start_iter(), &buffer.end_iter(), true);
            self.obj().clipboard().set_text(&contents);
        }
    }

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

        fn class_init(klass: &mut Self::Class) {
            klass.bind_template();
            klass.bind_template_callbacks();
            klass.install_action("page.export", None, |page, _, _| {
                page.export();
            });
        }

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

    impl ObjectImpl for QRScannedPage {
        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()
        }
    }

    impl WidgetImpl for QRScannedPage {}
    impl NavigationPageImpl for QRScannedPage {}
}

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

impl QRScannedPage {
    async fn save(&self, content: &str) -> anyhow::Result<()> {
        let code = QRCode::create(content).await?;
        self.emit_created(&code);

        Ok(())
    }

    fn export(&self) {
        if let Some(code_content) = self.imp().scanned_code.take() {
            self.emit_exported(&code_content);
        }
    }

    pub async fn set_scanned_code(&self, code: &str) -> anyhow::Result<()> {
        let window = self
            .root()
            .and_downcast::<crate::widgets::Window>()
            .unwrap();
        match QRCodeData::try_from(code) {
            Ok(data) => {
                let contents = code.to_string();
                let escaped = glib::markup_escape_text(&contents);
                let linkified_contents = utils::linkify(&escaped);

                self.imp().qr_widget.set_qrcode(data);
                self.imp().contents.buffer().set_text(&linkified_contents);
                self.save(&contents).await?;

                self.imp().scanned_code.replace(Some(contents));

                window.add_toast(&gettext("QR Code saved in history"));
            }
            Err(err) => {
                tracing::error!("Could generate QR code: {err}");

                window.add_toast(&gettext("Could not generate QR code"));
            }
        }

        Ok(())
    }

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

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