/* commit_log_view/context_menu.rs
 *
 * Copyright 2026 Christian Kruse
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

use gettextrs::gettext;
use gtk::gdk;
use gtk::prelude::*;
use relm4::gtk;
use relm4::prelude::*;

use super::{CommitItem, CommitLogView, CommitLogViewMsg, CommitLogViewOutput};
use crate::git::SquashMode;
use crate::menus;

#[derive(Debug)]
pub enum ContextMenuAction {
    ShowContextMenu(String),
    CopyHash { short: bool },
    CreateTagFromContext,
    CheckoutFromContext,
    ArchiveFromContext,
    SquashFromContext,
    FixupFromContext,
    RewordFromContext,
    DropFromContext,
    EditFromContext,
}

impl CommitLogView {
    pub(super) fn build_history_actions() -> (
        gtk::gio::SimpleAction,
        gtk::gio::SimpleAction,
        gtk::gio::SimpleAction,
        gtk::gio::SimpleAction,
        gtk::gio::SimpleAction,
    ) {
        let squash_action = gtk::gio::SimpleAction::new("squash", None);
        squash_action.set_enabled(false);
        let fixup_action = gtk::gio::SimpleAction::new("fixup", None);
        fixup_action.set_enabled(false);
        let reword_action = gtk::gio::SimpleAction::new("reword", None);
        reword_action.set_enabled(false);
        let drop_action = gtk::gio::SimpleAction::new("drop", None);
        drop_action.set_enabled(false);
        let edit_action = gtk::gio::SimpleAction::new("edit", None);
        edit_action.set_enabled(false);

        (squash_action, fixup_action, reword_action, drop_action, edit_action)
    }

    pub(super) fn install_context_menu(&self, list_view_widget: &gtk::ListView, sender: &ComponentSender<Self>) {
        let popover = gtk::PopoverMenu::from_model(Some(&menus::commit_context_menu()));
        popover.set_parent(list_view_widget);
        popover.set_has_arrow(false);

        let group = gtk::gio::SimpleActionGroup::new();

        let copy_action = gtk::gio::SimpleAction::new("copy-hash", None);
        let s = sender.input_sender().clone();
        copy_action.connect_activate(move |_, _| {
            s.emit(CommitLogViewMsg::ContextMenu(ContextMenuAction::CopyHash { short: false }))
        });
        group.add_action(&copy_action);

        let copy_short_action = gtk::gio::SimpleAction::new("copy-short-hash", None);
        let s = sender.input_sender().clone();
        copy_short_action.connect_activate(move |_, _| {
            s.emit(CommitLogViewMsg::ContextMenu(ContextMenuAction::CopyHash { short: true }))
        });
        group.add_action(&copy_short_action);

        let action = gtk::gio::SimpleAction::new("create-tag", None);
        let s = sender.input_sender().clone();
        action.connect_activate(move |_, _| {
            s.emit(CommitLogViewMsg::ContextMenu(ContextMenuAction::CreateTagFromContext))
        });
        group.add_action(&action);

        let checkout_action = gtk::gio::SimpleAction::new("checkout", None);
        let s = sender.input_sender().clone();
        checkout_action.connect_activate(move |_, _| {
            s.emit(CommitLogViewMsg::ContextMenu(ContextMenuAction::CheckoutFromContext))
        });
        group.add_action(&checkout_action);

        let archive_action = gtk::gio::SimpleAction::new("archive", None);
        let s = sender.input_sender().clone();
        archive_action
            .connect_activate(move |_, _| s.emit(CommitLogViewMsg::ContextMenu(ContextMenuAction::ArchiveFromContext)));
        group.add_action(&archive_action);

        {
            let s = sender.input_sender().clone();
            self.squash_action.connect_activate(move |_, _| {
                s.emit(CommitLogViewMsg::ContextMenu(ContextMenuAction::SquashFromContext))
            });
            group.add_action(&self.squash_action);
        }

        {
            let s = sender.input_sender().clone();
            self.fixup_action.connect_activate(move |_, _| {
                s.emit(CommitLogViewMsg::ContextMenu(ContextMenuAction::FixupFromContext))
            });
            group.add_action(&self.fixup_action);
        }

        {
            let s = sender.input_sender().clone();
            self.reword_action.connect_activate(move |_, _| {
                s.emit(CommitLogViewMsg::ContextMenu(ContextMenuAction::RewordFromContext))
            });
            group.add_action(&self.reword_action);
        }

        {
            let s = sender.input_sender().clone();
            self.drop_action.connect_activate(move |_, _| {
                s.emit(CommitLogViewMsg::ContextMenu(ContextMenuAction::DropFromContext))
            });
            group.add_action(&self.drop_action);
        }

        {
            let s = sender.input_sender().clone();
            self.edit_action.connect_activate(move |_, _| {
                s.emit(CommitLogViewMsg::ContextMenu(ContextMenuAction::EditFromContext))
            });
            group.add_action(&self.edit_action);
        }

        list_view_widget.insert_action_group("commit-item", Some(&group));

        let gesture = gtk::GestureClick::new();
        gesture.set_button(gdk::BUTTON_SECONDARY);

        let s = sender.input_sender().clone();
        let popover_for_gesture = popover.clone();
        let list_view_for_gesture = list_view_widget.clone();
        let selection_model_for_gesture = self.list_view.selection_model.clone();

        gesture.connect_released(move |gesture, _, x, y| {
            gesture.set_state(gtk::EventSequenceState::Claimed);

            if let Some(picked) = list_view_for_gesture.pick(x, y, gtk::PickFlags::DEFAULT)
                && let Some(oid) = find_commit_oid_from_widget(&picked, &list_view_for_gesture)
            {
                if let Some(model) = selection_model_for_gesture.model()
                    && let Some(pos) = position_for_oid_in_model(&model, &oid)
                    && !selection_model_for_gesture.is_selected(pos)
                {
                    selection_model_for_gesture.select_item(pos, true);
                }

                s.emit(CommitLogViewMsg::ContextMenu(ContextMenuAction::ShowContextMenu(oid)));

                let rect = gdk::Rectangle::new(x as i32, y as i32, 1, 1);
                popover_for_gesture.set_pointing_to(Some(&rect));
                popover_for_gesture.popup();
            }
        });

        list_view_widget.add_controller(gesture);
    }

    pub(super) fn handle_context_menu_action(&mut self, action: ContextMenuAction, sender: &ComponentSender<Self>) {
        match action {
            ContextMenuAction::ShowContextMenu(oid) => {
                self.context_commit_oid = Some(oid);
                self.update_history_actions_enabled();
            }

            ContextMenuAction::CopyHash { short } => {
                if let Some(oid) = self.context_commit_oid.take() {
                    let hash = if short { &oid[..oid.len().min(7)] } else { &oid };
                    if let Some(display) = gtk::gdk::Display::default() {
                        display.clipboard().set_text(hash);
                    }

                    sender
                        .output(CommitLogViewOutput::ShowToast(gettext("Copied “{}”").replace("{}", hash)))
                        .ok();
                }
            }

            ContextMenuAction::CreateTagFromContext => {
                if let Some(oid) = self.context_commit_oid.take() {
                    sender.output(CommitLogViewOutput::CreateTagRequested(oid)).ok();
                }
            }

            ContextMenuAction::CheckoutFromContext => {
                if let Some(oid) = self.context_commit_oid.take() {
                    sender.output(CommitLogViewOutput::CheckoutRequested(oid)).ok();
                }
            }

            ContextMenuAction::ArchiveFromContext => {
                if let Some(oid) = self.context_commit_oid.take() {
                    sender.output(CommitLogViewOutput::ArchiveRequested(oid)).ok();
                }
            }

            ContextMenuAction::SquashFromContext => {
                let oids = self.selected_oids();

                if oids.len() >= 2 {
                    sender
                        .output(CommitLogViewOutput::SquashRequested {
                            oids,
                            mode: SquashMode::Squash,
                        })
                        .ok();
                }
            }

            ContextMenuAction::FixupFromContext => {
                let oids = self.selected_oids();

                if oids.len() >= 2 {
                    sender
                        .output(CommitLogViewOutput::SquashRequested {
                            oids,
                            mode: SquashMode::Fixup,
                        })
                        .ok();
                }
            }

            ContextMenuAction::RewordFromContext => {
                if let Some(oid) = self.context_commit_oid.take() {
                    sender.output(CommitLogViewOutput::RewordRequested(oid)).ok();
                }
            }

            ContextMenuAction::DropFromContext => {
                if let Some(oid) = self.context_commit_oid.take() {
                    sender.output(CommitLogViewOutput::DropRequested(oid)).ok();
                }
            }

            ContextMenuAction::EditFromContext => {
                if let Some(oid) = self.context_commit_oid.take() {
                    sender.output(CommitLogViewOutput::EditRequested(oid)).ok();
                }
            }
        }
    }
}

fn position_for_oid_in_model(model: &gtk::gio::ListModel, oid: &str) -> Option<u32> {
    use relm4::gtk::glib;

    let n = model.n_items();

    for i in 0..n {
        let Some(obj) = model.item(i) else {
            continue;
        };

        let Ok(boxed) = obj.downcast::<glib::BoxedAnyObject>() else {
            continue;
        };

        let item: std::cell::Ref<CommitItem> = boxed.borrow();

        if item.commit.oid == oid {
            return Some(i);
        }
    }

    None
}

// Walk up the widget hierarchy from the given widget until we find one with the "commit-item" CSS class, and return its widget name
// (which is set to the commit OID). This is used to determine which commit was right-clicked for showing the context menu.
fn find_commit_oid_from_widget(start: &gtk::Widget, list_view: &gtk::ListView) -> Option<String> {
    let mut current = Some(start.clone());
    while let Some(ref w) = current {
        if w == list_view.upcast_ref::<gtk::Widget>() {
            break;
        }

        if w.has_css_class("commit-item") {
            let name = w.widget_name();
            if !name.is_empty() && name != w.type_().name() {
                return Some(name.to_string());
            }
            break;
        }

        current = w.parent();
    }

    None
}
