/* changed_file_row.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 std::cell::Cell;
use std::path::PathBuf;
use std::rc::Rc;

use adw::prelude::*;
use gettextrs::gettext;
use relm4::factory::FactoryComponent;
use relm4::gtk::gdk;
use relm4::prelude::*;

use crate::git::ChangedFile;
use crate::{menus, ui_helpers};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RowSection {
    Unified,
    Staged,
    Unstaged,
}

pub struct ChangedFileRow {
    file: ChangedFile,
    repo_path: PathBuf,
    section: RowSection,
    index: DynamicIndex,
    updating: Rc<Cell<bool>>,
    popover: Option<gtk::PopoverMenu>,
    check: Option<gtk::CheckButton>,
    index_label: Option<gtk::Label>,
    wt_label: Option<gtk::Label>,
}

#[derive(Debug, Clone)]
pub struct ChangedFileRowInit {
    pub file: ChangedFile,
    pub repo_path: PathBuf,
    pub section: RowSection,
}

impl std::fmt::Debug for ChangedFileRow {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ChangedFileRow")
            .field("file", &self.file)
            .field("index", &self.index)
            .finish()
    }
}

#[derive(Debug, Clone)]
pub enum ChangedFileRowInput {
    ShowContextMenu,
    ToggleStage,
    CopyAbsolutePath,
    CopyRelativePath,
    OpenInFileManager,
    Ignore,
    Discard,
    Delete,
    UpdateFile(ChangedFile),
}

#[derive(Debug)]
pub enum ChangedFileRowOutput {
    ToggleStaged { section: RowSection, idx: usize },
    DiscardRequested { section: RowSection, idx: usize },
    DeleteRequested { section: RowSection, idx: usize },
    IgnoreRequested { section: RowSection, idx: usize },
}

#[relm4::factory(pub)]
impl FactoryComponent for ChangedFileRow {
    type Init = ChangedFileRowInit;
    type Input = ChangedFileRowInput;
    type Output = ChangedFileRowOutput;
    type CommandOutput = ();
    type ParentWidget = gtk::ListBox;

    view! {
        #[name = "row"]
        adw::ActionRow {
            set_use_markup: false,
            set_title: &self.file.path,
            set_title_lines: 1,
            set_activatable: false,

            add_controller = gtk::GestureClick {
                set_button: gdk::BUTTON_SECONDARY,
                connect_released[sender] => move |gesture, _, _, _| {
                    gesture.set_state(gtk::EventSequenceState::Claimed);
                    sender.input(ChangedFileRowInput::ShowContextMenu);
                },
            },
        }
    }

    fn init_model(init: Self::Init, index: &DynamicIndex, _sender: FactorySender<Self>) -> Self {
        Self {
            file: init.file,
            repo_path: init.repo_path,
            section: init.section,
            index: index.clone(),
            updating: Rc::new(Cell::new(false)),
            popover: None,
            check: None,
            index_label: None,
            wt_label: None,
        }
    }

    fn init_widgets(
        &mut self,
        _index: &DynamicIndex,
        root: Self::Root,
        _returned_widget: &<Self::ParentWidget as relm4::factory::FactoryView>::ReturnedWidget,
        sender: FactorySender<Self>,
    ) -> Self::Widgets {
        let widgets = view_output!();

        let check = gtk::CheckButton::new();
        let s = sender.clone();
        let updating = self.updating.clone();

        check.connect_toggled(move |_| {
            if updating.get() {
                return;
            }

            s.input(ChangedFileRowInput::ToggleStage);
        });

        let show_index_icon = matches!(self.section, RowSection::Unified | RowSection::Staged);
        let show_wt_icon = matches!(self.section, RowSection::Unified | RowSection::Unstaged);

        if show_index_icon {
            let index_label = gtk::Label::new(None);
            index_label.add_css_class("staged-icon");
            index_label.add_css_class("status-label");
            root.add_prefix(&index_label);
            self.index_label = Some(index_label);
        }

        if show_wt_icon {
            let wt_label = gtk::Label::new(None);
            wt_label.add_css_class("unstaged-icon");
            wt_label.add_css_class("status-label");
            root.add_prefix(&wt_label);
            self.wt_label = Some(wt_label);
        }

        root.add_prefix(&check);
        self.check = Some(check);

        let popover = gtk::PopoverMenu::from_model(gtk::gio::MenuModel::NONE);
        popover.set_parent(&widgets.row);
        popover.set_has_arrow(false);
        self.popover = Some(popover);

        self.refresh_row();

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

        for (name, input) in [
            ("toggle-stage", ChangedFileRowInput::ToggleStage),
            ("copy-path", ChangedFileRowInput::CopyAbsolutePath),
            ("copy-relative-path", ChangedFileRowInput::CopyRelativePath),
            ("open-in-file-manager", ChangedFileRowInput::OpenInFileManager),
            ("ignore", ChangedFileRowInput::Ignore),
            ("discard", ChangedFileRowInput::Discard),
            ("delete", ChangedFileRowInput::Delete),
        ] {
            let action = gtk::gio::SimpleAction::new(name, None);
            let s = sender.clone();

            action.connect_activate(move |_, _| {
                s.input(input.clone());
            });

            group.add_action(&action);
        }

        widgets.row.insert_action_group("changed-file", Some(&group));

        widgets
    }

    fn update(&mut self, msg: Self::Input, sender: FactorySender<Self>) {
        match msg {
            ChangedFileRowInput::ShowContextMenu => {
                if let Some(popover) = &self.popover {
                    if let Some(action_row) = popover.parent()
                        && let Some(list_box_row) = action_row.parent().and_downcast::<gtk::ListBoxRow>()
                        && !list_box_row.is_selected()
                        && let Some(list) = list_box_row.parent().and_downcast::<gtk::ListBox>()
                    {
                        list.unselect_all();
                        list.select_row(Some(&list_box_row));
                    }

                    let menu_show_unstage = match self.section {
                        RowSection::Staged => true,
                        RowSection::Unstaged => false,
                        RowSection::Unified => self.file.is_fully_staged(),
                    };
                    popover.set_menu_model(Some(&menus::changed_file_context_menu(menu_show_unstage)));

                    popover.popup();
                }
            }

            ChangedFileRowInput::UpdateFile(file) => {
                self.file = file;
                self.refresh_row();
            }

            ChangedFileRowInput::ToggleStage => {
                sender
                    .output(ChangedFileRowOutput::ToggleStaged {
                        section: self.section,
                        idx: self.index.current_index(),
                    })
                    .ok();
            }

            ChangedFileRowInput::CopyAbsolutePath => {
                if let Some(display) = gdk::Display::default() {
                    let abs = self.repo_path.join(&self.file.path);
                    display.clipboard().set_text(&abs.to_string_lossy());
                }
            }

            ChangedFileRowInput::CopyRelativePath => {
                if let Some(display) = gdk::Display::default() {
                    display.clipboard().set_text(&self.file.path);
                }
            }

            ChangedFileRowInput::OpenInFileManager => {
                ui_helpers::open_in_file_manager(&self.repo_path.join(&self.file.path));
            }

            ChangedFileRowInput::Ignore => {
                sender
                    .output(ChangedFileRowOutput::IgnoreRequested {
                        section: self.section,
                        idx: self.index.current_index(),
                    })
                    .ok();
            }

            ChangedFileRowInput::Discard => {
                sender
                    .output(ChangedFileRowOutput::DiscardRequested {
                        section: self.section,
                        idx: self.index.current_index(),
                    })
                    .ok();
            }

            ChangedFileRowInput::Delete => {
                sender
                    .output(ChangedFileRowOutput::DeleteRequested {
                        section: self.section,
                        idx: self.index.current_index(),
                    })
                    .ok();
            }
        }
    }

    fn shutdown(&mut self, _widgets: &mut Self::Widgets, _output: relm4::Sender<Self::Output>) {
        if let Some(popover) = self.popover.take() {
            popover.unparent();
        }
    }
}

impl ChangedFileRow {
    pub fn file(&self) -> &ChangedFile {
        &self.file
    }

    fn refresh_row(&self) {
        if let Some(check) = &self.check {
            let active = match self.section {
                RowSection::Unified => self.file.is_fully_staged(),
                RowSection::Staged => true,
                RowSection::Unstaged => false,
            };
            self.updating.set(true);
            check.set_active(active);
            self.updating.set(false);
        }

        if let Some(lbl) = &self.index_label {
            lbl.set_label(self.file.index_status.map_or("", |s| s.label()));
            lbl.set_tooltip_text(
                self.file
                    .index_status
                    .map(|s| gettext("Staged: %1").replace("%1", s.tooltip()))
                    .as_deref(),
            );
        }

        if let Some(lbl) = &self.wt_label {
            lbl.set_label(self.file.wt_status.map_or("", |s| s.label()));
            lbl.set_tooltip_text(
                self.file
                    .wt_status
                    .map(|s| gettext("Unstaged: %1").replace("%1", s.tooltip()))
                    .as_deref(),
            );
        }
    }
}
