/* repo_view/pull.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::{RepoView, RepoViewMsg, RepoViewOutput};
use crate::pull_dialog::{PullDialog, PullDialogInit, PullDialogOutput};
use crate::{git, preferences};

impl RepoView {
    pub(super) fn handle_pull_message(
        &mut self,
        msg: RepoViewMsg,
        sender: &ComponentSender<RepoView>,
        root: &adw::ToastOverlay,
    ) {
        match msg {
            RepoViewMsg::Pull => {
                let path = self.path.clone();
                sender.spawn_oneshot_command(move || RepoViewMsg::PullDefaultsLoaded(git::get_pull_defaults(&path)));
            }

            RepoViewMsg::PullDefaultsLoaded(result) => match result {
                Ok(defaults) => {
                    let window = root.ancestor(gtk::Window::static_type()).and_downcast::<gtk::Window>();

                    self.pull_dialog = Some(
                        PullDialog::builder()
                            .launch(PullDialogInit {
                                defaults,
                                parent_window: window,
                            })
                            .forward(sender.input_sender(), |output| match output {
                                PullDialogOutput::Confirmed(settings) => RepoViewMsg::PullConfirmed(settings),
                                PullDialogOutput::Cancelled => RepoViewMsg::PullDialogDismissed,
                            }),
                    );
                }

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

            RepoViewMsg::PullDialogDismissed => {
                self.pull_dialog = None;
            }

            RepoViewMsg::PullConfirmed(settings) => {
                self.pull_dialog = None;

                if self.remote_op_in_progress {
                    return;
                }

                self.remote_op_in_progress = true;
                sender.output(RepoViewOutput::RemoteOpStarted).ok();

                let path = self.path.clone();
                let git_command = preferences::git_command();

                sender.spawn_oneshot_command(move || {
                    let (result, stashed) = git::pull_with_autostash(&path, &settings, &git_command);

                    RepoViewMsg::PullComplete {
                        result,
                        settings,
                        stashed,
                    }
                });
            }

            RepoViewMsg::PullComplete {
                result,
                settings,
                stashed,
            } => {
                self.remote_op_in_progress = false;
                sender.output(RepoViewOutput::RemoteOpFinished).ok();

                let upstream = format!("{}/{}", settings.remote, settings.branch);

                match &result {
                    Ok(outcome) => root.add_toast(adw::Toast::new(&pull_toast_message(outcome, &upstream, stashed))),
                    Err(msg) => root.add_toast(adw::Toast::new(msg)),
                }

                if result.is_ok() || stashed {
                    sender.input(RepoViewMsg::Refresh);
                }
            }

            _ => unreachable!("handle_pull_message called with non-pull message"),
        }
    }
}

fn pull_toast_message(outcome: &git::PullOutcome, upstream: &str, stashed: bool) -> String {
    let msg = match outcome {
        git::PullOutcome::AlreadyUpToDate => gettext("Already up to date"),

        git::PullOutcome::FastForward { commits } => ngettext(
            "Pulled %1 commit from %2 (fast-forward)",
            "Pulled %1 commits from %2 (fast-forward)",
            *commits as u32,
        )
        .replace("%1", &commits.to_string())
        .replace("%2", upstream),

        git::PullOutcome::Merged { commits } => {
            ngettext("Pulled %1 commit from %2 (merge)", "Pulled %1 commits from %2 (merge)", *commits as u32)
                .replace("%1", &commits.to_string())
                .replace("%2", upstream)
        }

        git::PullOutcome::Rebased { commits } => {
            ngettext("Pulled %1 commit from %2 (rebase)", "Pulled %1 commits from %2 (rebase)", *commits as u32)
                .replace("%1", &commits.to_string())
                .replace("%2", upstream)
        }

        git::PullOutcome::MergeConflicts { conflicted_files } => ngettext(
            "Merge conflict in %1 file, resolve conflicts and commit",
            "Merge conflicts in %1 files, resolve conflicts and commit",
            conflicted_files.len() as u32,
        )
        .replace("%1", &conflicted_files.len().to_string()),

        git::PullOutcome::RebaseConflicts => gettext("Rebase paused due to conflicts, resolve and continue"),
    };

    if stashed && outcome.has_conflicts() {
        let stash_note = gettext("Your uncommitted changes are saved in the stash list.");
        format!("{msg} — {stash_note}")
    } else {
        msg
    }
}
