// SPDX-License-Identifier: GPL-3.0-or-later

use adw::prelude::*;
use gettextrs::gettext;
use gtk::{
    gio,
    glib::{self, clone},
    subclass::prelude::*,
};

use crate::{qrcode::QRCode, widgets::QRCodeWidget};

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

    use glib::subclass::Signal;

    use super::*;

    #[derive(Default, gtk::CompositeTemplate, glib::Properties)]
    #[template(resource = "/com/belmoussaoui/Decoder/ui/qrcode_row.ui")]
    #[properties(wrapper_type = super::QRCodeRow)]
    pub struct QRCodeRow {
        pub actions: gio::SimpleActionGroup,
        #[template_child]
        pub qr_widget: TemplateChild<QRCodeWidget>,
        #[template_child]
        pub revealer: TemplateChild<gtk::Revealer>,
        #[template_child]
        pub container: TemplateChild<gtk::Box>,
        #[template_child]
        pub created_at_label: TemplateChild<gtk::Label>,

        #[property(type = QRCode, get, set, construct_only)]
        code: RefCell<Option<QRCode>>,
    }

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

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

            klass.install_action_async("row.delete", None, async |obj, _, _| {
                if let Err(err) = obj.delete().await {
                    tracing::error!("Could not delete row: {err}");
                }
            });
            klass.install_action("row.export", None, |obj, _, _| {
                obj.export();
            });
            klass.install_action("row.copy-content", None, |obj, _, _| {
                obj.copy_content();
            });
        }

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

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

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

impl QRCodeRow {
    pub fn new(code: &QRCode) -> Self {
        let row: QRCodeRow = glib::Object::builder().property("code", code).build();
        row.init_widgets();
        row
    }

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

    async fn delete(&self) -> anyhow::Result<()> {
        self.code().delete().await?;
        self.emit_deleted();

        Ok(())
    }

    fn init_widgets(&self) {
        let imp = self.imp();
        // TODO The datetime is in naive UTC. since we only display the day, the
        // correction should not matter, but still using the local tz is
        // preferable.
        imp.created_at_label.set_label(&format!(
            "Saved at {}",
            self.code().created_at().format("%Y-%m-%d")
        ));

        imp.qr_widget.set_qrcode(self.code().data());

        let kind_widget = self.code().kind().widget();
        imp.container.prepend(&kind_widget);
    }

    pub fn reveal(&self) {
        let revealer = self.imp().revealer.get();

        revealer.set_reveal_child(!revealer.reveals_child());
    }

    fn copy_content(&self) {
        let window = self
            .root()
            .and_downcast::<crate::widgets::Window>()
            .unwrap();
        window.add_toast(&gettext("Copied to clipboard"));
        let content = self.code().content();

        self.clipboard().set_text(&content);
    }

    pub fn emit_deleted(&self) {
        self.emit_by_name::<()>("deleted", &[]);
    }

    pub fn connect_deleted<F: Fn(&Self) + 'static>(&self, f: F) {
        self.connect_local(
            "deleted",
            false,
            clone!(
                #[weak(rename_to = obj)]
                self,
                #[upgrade_or]
                None,
                move |_| {
                    f(&obj);

                    None
                }
            ),
        );
    }

    pub fn connect_exported<F: Fn(&Self, &str) + 'static>(&self, f: F) {
        self.connect_local(
            "exported",
            false,
            clone!(
                #[weak(rename_to = obj)]
                self,
                #[upgrade_or]
                None,
                move |args| {
                    let code = args.get(1).unwrap().get::<&str>().unwrap();
                    f(&obj, code);

                    None
                }
            ),
        );
    }

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