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

use crate::{session::model::ReactionList, utils::BoundObject};

/// Helper struct for the context menu of an `ItemRow`.
#[derive(Debug)]
pub(super) struct ItemRowContextMenu {
    /// The popover of the context menu.
    pub(super) popover: gtk::PopoverMenu,
    /// The menu model of the popover.
    menu_model: gio::Menu,
    /// The quick reaction chooser in the context menu.
    quick_reaction_chooser: QuickReactionChooser,
}

impl ItemRowContextMenu {
    /// The identifier in the context menu for the quick reaction chooser.
    const QUICK_REACTION_CHOOSER_ID: &str = "quick-reaction-chooser";

    /// Whether the menu includes an item for the quick reaction chooser.
    fn has_quick_reaction_chooser(&self) -> bool {
        let first_section = self
            .menu_model
            .item_link(0, gio::MENU_LINK_SECTION)
            .and_downcast::<gio::Menu>()
            .expect("item row context menu has at least one section");
        first_section
            .item_attribute_value(0, "custom", Some(&String::static_variant_type()))
            .and_then(|variant| variant.get::<String>())
            .is_some_and(|value| value == Self::QUICK_REACTION_CHOOSER_ID)
    }

    /// Add the quick reaction chooser to this menu, if it is not already
    /// present, and set the reaction list.
    pub(super) fn add_quick_reaction_chooser(&self, reactions: ReactionList) {
        if !self.has_quick_reaction_chooser() {
            let section_menu = gio::Menu::new();
            let item = gio::MenuItem::new(None, None);
            item.set_attribute_value(
                "custom",
                Some(&Self::QUICK_REACTION_CHOOSER_ID.to_variant()),
            );
            section_menu.append_item(&item);
            self.menu_model.insert_section(0, None, &section_menu);

            self.popover.add_child(
                &self.quick_reaction_chooser,
                Self::QUICK_REACTION_CHOOSER_ID,
            );
        }

        self.quick_reaction_chooser.set_reactions(Some(reactions));
    }

    /// Remove the quick reaction chooser from this menu, if it is present.
    pub(super) fn remove_quick_reaction_chooser(&self) {
        if !self.has_quick_reaction_chooser() {
            return;
        }

        self.popover.remove_child(&self.quick_reaction_chooser);
        self.menu_model.remove(0);
    }
}

impl Default for ItemRowContextMenu {
    fn default() -> Self {
        let menu_model = gtk::Builder::from_resource(
            "/org/gnome/Fractal/ui/session/view/content/room_history/event_context_menu.ui",
        )
        .object::<gio::Menu>("event-menu")
        .expect("resource and menu exist");

        let popover = gtk::PopoverMenu::builder()
            .has_arrow(false)
            .halign(gtk::Align::Start)
            .menu_model(&menu_model)
            .build();
        popover.update_property(&[gtk::accessible::Property::Label(&gettext("Context Menu"))]);

        Self {
            popover,
            menu_model,
            quick_reaction_chooser: Default::default(),
        }
    }
}

/// A quick reaction.
#[derive(Debug, Clone, Copy)]
struct QuickReaction {
    /// The emoji that is presented.
    key: &'static str,
    /// The number of the column where this reaction is presented.
    ///
    /// There are 4 columns in total.
    column: i32,
    /// The number of the row where this reaction is presented.
    ///
    /// There are 2 rows in total.
    row: i32,
}

/// The quick reactions to present.
static QUICK_REACTIONS: &[QuickReaction] = &[
    QuickReaction {
        key: "👍️",
        column: 0,
        row: 0,
    },
    QuickReaction {
        key: "👎️",
        column: 1,
        row: 0,
    },
    QuickReaction {
        key: "😄",
        column: 2,
        row: 0,
    },
    QuickReaction {
        key: "🎉",
        column: 3,
        row: 0,
    },
    QuickReaction {
        key: "😕",
        column: 0,
        row: 1,
    },
    QuickReaction {
        key: "❤️",
        column: 1,
        row: 1,
    },
    QuickReaction {
        key: "🚀",
        column: 2,
        row: 1,
    },
];

mod imp {

    use std::{cell::RefCell, collections::HashMap, sync::LazyLock};

    use glib::subclass::{InitializingObject, Signal};

    use super::*;

    #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
    #[template(
        resource = "/org/gnome/Fractal/ui/session/view/content/room_history/quick_reaction_chooser.ui"
    )]
    #[properties(wrapper_type = super::QuickReactionChooser)]
    pub struct QuickReactionChooser {
        #[template_child]
        reaction_grid: TemplateChild<gtk::Grid>,
        /// The list of reactions of the event for which this chooser is
        /// presented.
        #[property(get, set = Self::set_reactions, explicit_notify, nullable)]
        reactions: BoundObject<ReactionList>,
        reaction_bindings: RefCell<HashMap<String, glib::Binding>>,
    }

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

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

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

    #[glib::derived_properties]
    impl ObjectImpl for QuickReactionChooser {
        fn signals() -> &'static [Signal] {
            static SIGNALS: LazyLock<Vec<Signal>> =
                LazyLock::new(|| vec![Signal::builder("more-reactions-activated").build()]);
            SIGNALS.as_ref()
        }

        fn constructed(&self) {
            self.parent_constructed();

            // Construct the quick reactions.
            let grid = &self.reaction_grid;
            for reaction in QUICK_REACTIONS {
                let button = gtk::ToggleButton::builder()
                    .label(reaction.key)
                    .action_name("event.toggle-reaction")
                    .action_target(&reaction.key.to_variant())
                    .css_classes(["flat", "circular"])
                    .build();
                button.connect_clicked(|button| {
                    button.activate_action("context-menu.close", None).unwrap();
                });
                grid.attach(&button, reaction.column, reaction.row, 1, 1);
            }
        }
    }

    impl WidgetImpl for QuickReactionChooser {}
    impl BinImpl for QuickReactionChooser {}

    #[gtk::template_callbacks]
    impl QuickReactionChooser {
        /// Set the list of reactions of the event for which this chooser is
        /// presented.
        fn set_reactions(&self, reactions: Option<ReactionList>) {
            let prev_reactions = self.reactions.obj();

            if prev_reactions == reactions {
                return;
            }

            self.reactions.disconnect_signals();
            for (_, binding) in self.reaction_bindings.borrow_mut().drain() {
                binding.unbind();
            }

            // Reset the state of the buttons.
            for row in 0..=1 {
                for column in 0..=3 {
                    if let Some(button) = self
                        .reaction_grid
                        .child_at(column, row)
                        .and_downcast::<gtk::ToggleButton>()
                    {
                        button.set_active(false);
                    }
                }
            }

            if let Some(reactions) = reactions {
                let signal_handler = reactions.connect_items_changed(clone!(
                    #[weak(rename_to = imp)]
                    self,
                    move |_, _, _, _| {
                        imp.update_reactions();
                    }
                ));
                self.reactions.set(reactions, vec![signal_handler]);
            }

            self.update_reactions();
        }

        /// Update the state of the quick reactions.
        fn update_reactions(&self) {
            let mut reaction_bindings = self.reaction_bindings.borrow_mut();
            let reactions = self.reactions.obj();

            for reaction_item in QUICK_REACTIONS {
                if let Some(reaction) = reactions
                    .as_ref()
                    .and_then(|reactions| reactions.reaction_group_by_key(reaction_item.key))
                {
                    if reaction_bindings.get(reaction_item.key).is_none() {
                        let button = self
                            .reaction_grid
                            .child_at(reaction_item.column, reaction_item.row)
                            .unwrap();
                        let binding = reaction
                            .bind_property("has-own-user", &button, "active")
                            .sync_create()
                            .build();
                        reaction_bindings.insert(reaction_item.key.to_string(), binding);
                    }
                } else if let Some(binding) = reaction_bindings.remove(reaction_item.key) {
                    if let Some(button) = self
                        .reaction_grid
                        .child_at(reaction_item.column, reaction_item.row)
                        .and_downcast::<gtk::ToggleButton>()
                    {
                        button.set_active(false);
                    }

                    binding.unbind();
                }
            }
        }

        /// Handle when the "More reactions" button is activated.
        #[template_callback]
        fn more_reactions_activated(&self) {
            self.obj()
                .emit_by_name::<()>("more-reactions-activated", &[]);
        }
    }
}

glib::wrapper! {
    /// A widget displaying quick reactions and taking its state from a [`ReactionList`].
    pub struct QuickReactionChooser(ObjectSubclass<imp::QuickReactionChooser>)
        @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
}

impl QuickReactionChooser {
    pub fn new() -> Self {
        glib::Object::new()
    }

    /// Connect to the signal emitted when the "More reactions" button is
    /// activated.
    pub fn connect_more_reactions_activated<F: Fn(&Self) + 'static>(
        &self,
        f: F,
    ) -> glib::SignalHandlerId {
        self.connect_closure(
            "more-reactions-activated",
            true,
            closure_local!(move |obj: Self| {
                f(&obj);
            }),
        )
    }
}

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