// SPDX-FileCopyrightText: 2023 Gleb Smirnov <glebsmirnov0708@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-or-later

use adw::prelude::*;
use adw::subclass::prelude::*;

use gtk::{gio, glib};
use textpieces_core::Parameter;

mod imp {
    use std::cell::{Cell, OnceCell};

    use gettextrs::pgettext;
    use once_cell::sync::Lazy;
    use textpieces_core::ParameterType;

    use crate::utils::{to_entries, GActions};

    use super::*;

    #[derive(Debug, Default, gtk::CompositeTemplate)]
    #[template(resource = "/io/gitlab/liferooter/TextPieces/ui/parameter-row.ui")]
    pub struct ParameterRow {
        pub type_: Cell<ParameterType>,
        action_group: OnceCell<gio::SimpleActionGroup>,
    }

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

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

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

    impl ObjectImpl for ParameterRow {
        fn constructed(&self) {
            self.parent_constructed();

            // Insert actions
            let group = gio::SimpleActionGroup::new();
            let actions: GActions<Self::Type> = &[
                ("set-type-string", |row| {
                    row.imp().set_type(ParameterType::String);
                }),
                ("set-type-bool", |row| {
                    row.imp().set_type(ParameterType::Bool);
                }),
                ("set-type-int", |row| {
                    row.imp().set_type(ParameterType::Int);
                }),
                ("set-type-float", |row| {
                    row.imp().set_type(ParameterType::Float);
                }),
                ("delete", |row| row.emit_by_name("delete", &[])),
            ];

            group.add_action_entries(to_entries(actions, &self.obj()));

            self.obj().insert_action_group("param", Some(&group));

            self.action_group
                .set(group)
                .expect("Action group is already set somehow");

            // Set default type
            self.set_type(ParameterType::String);
        }

        fn signals() -> &'static [glib::subclass::Signal] {
            static SIGNALS: Lazy<[glib::subclass::Signal; 1]> =
                Lazy::new(|| [glib::subclass::Signal::builder("delete").build()]);

            &*SIGNALS
        }
    }

    impl WidgetImpl for ParameterRow {}
    impl ListBoxRowImpl for ParameterRow {}
    impl PreferencesRowImpl for ParameterRow {}
    impl EntryRowImpl for ParameterRow {}

    impl ParameterRow {
        /// Updates row title.
        ///
        /// Row title represents parameter type.
        fn update_title(&self) {
            let type_name = match self.type_.get() {
                ParameterType::String => pgettext("parameter row", "Text"),
                ParameterType::Bool => pgettext("parameter row", "Boolean"),
                ParameterType::Int => pgettext("parameter row", "Integer"),
                ParameterType::Float => pgettext("parameter row", "Real Number"),
            };

            self.obj()
                .set_title(&pgettext!("parameter row", "{} Parameter", type_name));
        }

        /// Update "set-type-..." actions.
        ///
        /// Make current type action inactive
        /// and make other type actions active.
        fn update_type_actions(&self) {
            let type_actions = [
                (ParameterType::String, "set-type-string"),
                (ParameterType::Bool, "set-type-bool"),
                (ParameterType::Int, "set-type-int"),
                (ParameterType::Float, "set-type-float"),
            ];

            let current_type = self.type_.get();

            for (type_, action_name) in type_actions {
                let action = self
                    .action_group
                    .get()
                    .expect("Action group is not set yet somehow")
                    .lookup_action(action_name)
                    .and_downcast::<gio::SimpleAction>()
                    .expect("Type action is not found in group somehow");

                action.set_enabled(type_ != current_type);
            }
        }

        /// Set parameter type.
        pub fn set_type(&self, type_: ParameterType) {
            self.type_.set(type_);

            self.update_title();
            self.update_type_actions();
        }
    }

    #[gtk::template_callbacks]
    impl ParameterRow {}
}

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

impl Default for ParameterRow {
    fn default() -> Self {
        Self::new()
    }
}

impl ParameterRow {
    /// Creates new parameter row.
    pub fn new() -> Self {
        glib::Object::new()
    }

    /// Creates new parameter row from given parameter.
    pub fn from_parameter(parameter: &Parameter) -> Self {
        let obj: Self = glib::Object::new();

        obj.set_text(&parameter.label);
        obj.imp().set_type(parameter.type_);

        obj
    }

    /// Returns parameter object of the row.
    pub fn parameter(&self) -> Parameter {
        Parameter {
            type_: self.imp().type_.get(),
            label: self.text().into(),
        }
    }
}
