/* repo_view/squash.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::{gettext, ngettext};
use relm4::prelude::*;

use super::rewrite_helpers::{RewriteToastLabels, rewrite_outcome_toast};
use super::{RepoView, RepoViewMsg, WorkspaceTab};
use crate::commit_message_dialog::{CommitMessageDialog, CommitMessageDialogInit, CommitMessageDialogOutput};
use crate::confirm_dialog::{ConfirmDialog, ConfirmDialogInit, ConfirmDialogOutput};
use crate::git;

#[derive(Debug)]
pub enum SquashAction {
    Requested {
        oids: Vec<String>,
        mode: git::SquashMode,
    },
    DirtyChecked {
        oids: Vec<String>,
        mode: git::SquashMode,
        summary: git::DirtySummary,
    },
    StashChoiceMade {
        oids: Vec<String>,
        mode: git::SquashMode,
        flags: git::StashPushFlags,
    },
    StashChoiceCancelled,
    StashPushed {
        oids: Vec<String>,
        mode: git::SquashMode,
        result: Result<(), String>,
    },
    SquashConfirmed {
        oids: Vec<String>,
        message: String,
        stashed: bool,
    },
    SquashDialogDismissed,
    FixupConfirmed {
        oids: Vec<String>,
        stashed: bool,
    },
    FixupConfirmDismissed,
    OperationComplete {
        result: Result<git::RebaseOutcome, String>,
        stashed: bool,
    },
}

impl RepoView {
    pub(super) fn handle_squash_action(
        &mut self,
        action: SquashAction,
        sender: &ComponentSender<Self>,
        root: &adw::ToastOverlay,
    ) {
        match action {
            SquashAction::Requested { oids, mode } => {
                let repo_path = self.path.clone();

                sender.spawn_oneshot_command(move || match git::dirty_summary(&repo_path) {
                    Ok(summary) => RepoViewMsg::Squash(SquashAction::DirtyChecked { oids, mode, summary }),
                    Err(msg) => RepoViewMsg::ShowToast(msg),
                });
            }

            SquashAction::DirtyChecked { oids, mode, summary } => {
                if summary.is_dirty() {
                    let oids_for_stash = oids.clone();
                    let mode_for_stash = mode;
                    self.open_rewrite_stash_choice_dialog(
                        gettext(
                            "You have uncommitted changes. They must be stashed before the selected commits can be \
                             squashed.",
                        ),
                        summary,
                        sender,
                        root,
                        move |flags| {
                            RepoViewMsg::Squash(SquashAction::StashChoiceMade {
                                oids: oids_for_stash.clone(),
                                mode: mode_for_stash,
                                flags,
                            })
                        },
                        || RepoViewMsg::Squash(SquashAction::StashChoiceCancelled),
                    );
                } else {
                    self.open_squash_or_fixup_dialog(oids, mode, false, sender, root);
                }
            }

            SquashAction::StashChoiceCancelled => {
                self.stash_switch_dialog = None;
            }

            SquashAction::StashChoiceMade { oids, mode, flags } => {
                self.stash_switch_dialog = None;
                let repo_path = self.path.clone();

                sender.spawn_oneshot_command(move || {
                    let result = git::stash_push(&repo_path, None, flags);
                    RepoViewMsg::Squash(SquashAction::StashPushed { oids, mode, result })
                });
            }

            SquashAction::StashPushed { oids, mode, result } => {
                sender.input(RepoViewMsg::Refresh);

                match result {
                    Ok(()) => {
                        self.open_squash_or_fixup_dialog(oids, mode, true, sender, root);
                    }

                    Err(msg) => {
                        root.add_toast(adw::Toast::new(&msg));
                    }
                }
            }

            SquashAction::SquashDialogDismissed => {
                self.commit_message_dialog = None;
            }

            SquashAction::SquashConfirmed { oids, message, stashed } => {
                self.commit_message_dialog = None;
                let path = self.path.clone();
                let git_command = crate::preferences::git_command();

                sender.spawn_oneshot_command(move || {
                    let result =
                        git::squash_commits(&path, &git_command, &oids, git::SquashMode::Squash, Some(message));
                    RepoViewMsg::Squash(SquashAction::OperationComplete { result, stashed })
                });
            }

            SquashAction::FixupConfirmDismissed => {
                self.confirm_dialog = None;
            }

            SquashAction::FixupConfirmed { oids, stashed } => {
                self.confirm_dialog = None;
                let path = self.path.clone();
                let git_command = crate::preferences::git_command();

                sender.spawn_oneshot_command(move || {
                    let result = git::squash_commits(&path, &git_command, &oids, git::SquashMode::Fixup, None);
                    RepoViewMsg::Squash(SquashAction::OperationComplete { result, stashed })
                });
            }

            SquashAction::OperationComplete { result, stashed } => {
                let toast = rewrite_outcome_toast(
                    &result,
                    stashed,
                    RewriteToastLabels {
                        completed: &gettext("Commits squashed"),
                        completed_with_stash: &gettext("Commits squashed, stashed changes preserved"),
                        paused_no_conflicts: &gettext("Rebase paused unexpectedly, click Continue Rebase to proceed"),
                        paused_no_conflicts_with_stash: &gettext(
                            "Rebase paused unexpectedly, click Continue Rebase to proceed",
                        ),
                    },
                );

                root.add_toast(adw::Toast::new(&toast));

                if matches!(result, Ok(git::RebaseOutcome::Paused { .. })) {
                    sender.input(RepoViewMsg::WorkspaceSelected(WorkspaceTab::WorkingCopy));
                }

                sender.input(RepoViewMsg::Refresh);
            }
        }
    }

    fn open_squash_or_fixup_dialog(
        &mut self,
        oids: Vec<String>,
        mode: git::SquashMode,
        stashed: bool,
        sender: &ComponentSender<Self>,
        root: &adw::ToastOverlay,
    ) {
        match mode {
            git::SquashMode::Squash => self.open_squash_dialog(oids, stashed, sender, root),
            git::SquashMode::Fixup => self.open_fixup_confirm(oids, stashed, sender, root),
        }
    }

    fn open_squash_dialog(
        &mut self,
        oids: Vec<String>,
        stashed: bool,
        sender: &ComponentSender<Self>,
        root: &adw::ToastOverlay,
    ) {
        let messages = match git::load_squash_messages(&self.path, &oids) {
            Ok(m) => m,

            Err(msg) => {
                root.add_toast(adw::Toast::new(&msg));
                return;
            }
        };

        let oldest_subject = messages.first().map(|m| m.subject.clone()).unwrap_or_default();

        let mut body_parts: Vec<String> = Vec::new();

        if let Some(first) = messages.first()
            && !first.body.trim().is_empty()
        {
            body_parts.push(first.body.trim().to_string());
        }

        for m in messages.iter().skip(1) {
            let mut chunk = m.subject.trim().to_string();

            if !m.body.trim().is_empty() {
                chunk.push_str("\n\n");
                chunk.push_str(m.body.trim());
            }

            if !chunk.is_empty() {
                body_parts.push(chunk);
            }
        }
        let initial_body = body_parts.join("\n\n");

        let window = root.ancestor(gtk::Window::static_type()).and_downcast::<gtk::Window>();

        let oids_for_msg = oids.clone();

        self.commit_message_dialog = Some(
            CommitMessageDialog::builder()
                .launch(CommitMessageDialogInit {
                    heading: ngettext("Squash {} commit", "Squash {} commits", oids.len() as u32)
                        .replace("{}", &oids.len().to_string()),
                    body: gettext("Edit the combined commit message"),
                    confirm_label: gettext("Squash"),
                    initial_subject: oldest_subject,
                    initial_body,
                    repo_settings: self.settings.clone(),
                    parent_window: window,
                })
                .forward(sender.input_sender(), move |output| match output {
                    CommitMessageDialogOutput::Confirmed { subject, body } => {
                        let message = if body.is_empty() {
                            subject
                        } else {
                            format!("{subject}\n\n{body}")
                        };

                        RepoViewMsg::Squash(SquashAction::SquashConfirmed {
                            oids: oids_for_msg.clone(),
                            message,
                            stashed,
                        })
                    }

                    CommitMessageDialogOutput::Cancelled => RepoViewMsg::Squash(SquashAction::SquashDialogDismissed),
                }),
        );
    }

    fn open_fixup_confirm(
        &mut self,
        oids: Vec<String>,
        stashed: bool,
        sender: &ComponentSender<Self>,
        root: &adw::ToastOverlay,
    ) {
        let window = root.ancestor(gtk::Window::static_type()).and_downcast::<gtk::Window>();
        let n = oids.len();
        let oids_for_msg = oids.clone();

        self.confirm_dialog = Some(
            ConfirmDialog::builder()
                .launch(ConfirmDialogInit {
                    heading: ngettext("Fixup {} commit?", "Fixup {} commits?", n as u32).replace("{}", &n.to_string()),
                    body: gettext(
                        "All selected commits will be folded into the oldest of them. Their messages will be \
                         discarded.",
                    ),
                    action_label: gettext("Fixup"),
                    appearance: adw::ResponseAppearance::Destructive,
                    parent_window: window,
                })
                .forward(sender.input_sender(), move |output| match output {
                    ConfirmDialogOutput::Cancelled => RepoViewMsg::Squash(SquashAction::FixupConfirmDismissed),

                    ConfirmDialogOutput::Confirmed => RepoViewMsg::Squash(SquashAction::FixupConfirmed {
                        oids: oids_for_msg.clone(),
                        stashed,
                    }),
                }),
        );
    }
}
