/* commit_log_view/selection.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 adw::prelude::*;
use gettextrs::ngettext;
use relm4::prelude::*;

use super::{CommitLogView, CommitLogViewMsg, CommitLogViewOutput};
use crate::git::{self, FileDiff};
use crate::ui_helpers;

#[derive(Debug)]
pub enum SelectionAction {
    SelectionChanged,
    CommitDiffLoaded {
        generation: u64,
        result: Result<Vec<FileDiff>, String>,
    },
}

impl CommitLogView {
    pub(super) fn install_selection_listener(&self, sender: &ComponentSender<Self>) {
        let s = sender.input_sender().clone();
        self.list_view
            .selection_model
            .connect_selection_changed(move |_selection, _, _| {
                s.emit(CommitLogViewMsg::Selection(SelectionAction::SelectionChanged));
            });
    }

    pub(super) fn install_unmap_clear(root: &gtk::Widget, sender: &ComponentSender<Self>) {
        let s = sender.output_sender().clone();
        root.connect_unmap(move |_| {
            let _ = s.send(CommitLogViewOutput::SelectedCommitChanged(None));
        });
    }

    pub(super) fn handle_selection_action(
        &mut self,
        action: SelectionAction,
        widgets: &mut <Self as Component>::Widgets,
        sender: &ComponentSender<Self>,
    ) {
        match action {
            SelectionAction::SelectionChanged => {
                let positions = self.selected_positions();

                self.update_history_actions_enabled();

                if positions.len() != 1 {
                    self.selected_oid = None;
                    self.diff_factory.guard().clear();
                    widgets.detail_stack.set_visible_child_name("empty");
                    sender.output(CommitLogViewOutput::SelectedCommitChanged(None)).ok();
                    return;
                }

                let pos = positions[0];
                let Some(item) = self.list_view.get(pos) else {
                    return;
                };

                let commit = item.borrow::<super::CommitItem>().commit.clone();

                widgets.summary_label.set_label(&commit.summary);
                widgets.oid_label.set_label(&commit.oid);

                if commit.parents.is_empty() {
                    widgets.parents_label.set_visible(false);
                } else {
                    let parent_label = if commit.parents.len() == 1 {
                        format!("↑ {}", commit.parents[0])
                    } else {
                        format!("↑ {}", commit.parents.join(", "))
                    };

                    widgets.parents_label.set_label(&parent_label);
                    widgets.parents_label.set_visible(true);
                }

                let author_id = format_identity(&commit.author, &commit.author_email);
                widgets
                    .author_label
                    .set_label(&format!("{} · {}", author_id, ui_helpers::format_time(commit.time)));

                let committer_differs = commit.committer != commit.author
                    || commit.committer_email != commit.author_email
                    || commit.committer_time != commit.time;

                if committer_differs {
                    let committer_id = format_identity(&commit.committer, &commit.committer_email);

                    widgets.committer_label.set_label(&format!(
                        "{} · {} · {}",
                        gettextrs::gettext("Committed by"),
                        committer_id,
                        ui_helpers::format_time(commit.committer_time)
                    ));

                    widgets.committer_label.set_visible(true);
                } else {
                    widgets.committer_label.set_visible(false);
                }

                if commit.body.is_empty() {
                    widgets.body_label.set_visible(false);
                } else {
                    widgets.body_label.set_label(&commit.body);
                    widgets.body_label.set_visible(true);
                }

                widgets.stats_label.set_visible(false);

                self.selected_oid = Some(commit.oid.clone());
                sender
                    .output(CommitLogViewOutput::SelectedCommitChanged(Some(commit.oid.clone())))
                    .ok();

                self.diff_factory.guard().clear();
                widgets.detail_stack.set_visible_child_name("detail");

                self.diff_generation = self.diff_generation.wrapping_add(1);
                let diff_generation = self.diff_generation;
                let p = self.path.clone();
                let oid = commit.oid;

                sender.spawn_oneshot_command(move || {
                    CommitLogViewMsg::Selection(SelectionAction::CommitDiffLoaded {
                        generation: diff_generation,
                        result: git::load_commit_diff(&p, &oid),
                    })
                });
            }

            SelectionAction::CommitDiffLoaded { generation, result } => {
                if generation != self.diff_generation {
                    return;
                }

                match result {
                    Ok(file_diffs) => {
                        let file_count = file_diffs.len();
                        let insertions: usize = file_diffs.iter().map(|f| f.insertions).sum();
                        let deletions: usize = file_diffs.iter().map(|f| f.deletions).sum();

                        if file_count == 0 {
                            widgets.stats_label.set_visible(false);
                        } else {
                            let files_part = ngettext("{n} file changed", "{n} files changed", file_count as u32)
                                .replace("{n}", &file_count.to_string());

                            widgets
                                .stats_label
                                .set_label(&format!("{files_part} · +{insertions} −{deletions}"));

                            widgets.stats_label.set_visible(true);
                        }

                        let gutter_digits = crate::file_diff_row::gutter_digits_for(&file_diffs);

                        let mut guard = self.diff_factory.guard();
                        guard.clear();

                        for file_diff in file_diffs {
                            guard.push_back(crate::file_diff_row::FileDiffRowInit {
                                file_diff,
                                gutter_digits,
                            });
                        }
                    }

                    Err(msg) => {
                        sender.output(CommitLogViewOutput::ShowToast(msg)).ok();
                    }
                }
            }
        }
    }
}

fn format_identity(name: &str, email: &str) -> String {
    if email.is_empty() {
        name.to_string()
    } else {
        format!("{name} <{email}>")
    }
}
