/* git/stash.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::path::Path;

use super::diff::file_diffs_from_diff;
use super::types::*;
use super::{StashPushFlags, git_err, open_repo};

impl From<StashPushFlags> for git2::StashFlags {
    fn from(flags: StashPushFlags) -> Self {
        match flags {
            StashPushFlags::Default => git2::StashFlags::DEFAULT,
            StashPushFlags::IncludeUntracked => git2::StashFlags::INCLUDE_UNTRACKED,
        }
    }
}

pub fn load_stashes_from_path(path: &Path) -> Result<Vec<StashEntry>, String> {
    let mut repo = open_repo(path)?;
    Ok(load_stashes(&mut repo))
}

pub(super) fn load_stashes(repo: &mut git2::Repository) -> Vec<StashEntry> {
    let mut stashes = Vec::new();

    repo.stash_foreach(|index, message, oid| {
        stashes.push(StashEntry {
            index,
            message: message.to_string(),
            oid: oid.to_string(),
        });

        true
    })
    .ok();

    stashes
}

pub fn stash_push(path: &Path, message: Option<&str>, flags: StashPushFlags) -> Result<(), String> {
    let mut repo = open_repo(path)?;
    let sig = repo.signature().map_err(git_err("Failed to get signature"))?;

    repo.stash_save(&sig, message.unwrap_or("Stash"), Some(flags.into()))
        .map_err(git_err("Failed to create stash"))?;

    Ok(())
}

pub fn stash_pop(path: &Path, index: usize) -> Result<(), String> {
    let mut repo = open_repo(path)?;
    let mut opts = git2::StashApplyOptions::new();
    repo.stash_pop(index, Some(&mut opts))
        .map_err(git_err("Failed to pop stash"))?;

    Ok(())
}

pub fn stash_apply(path: &Path, index: usize) -> Result<(), String> {
    let mut repo = open_repo(path)?;
    repo.stash_apply(index, None)
        .map_err(git_err("Failed to apply stash"))?;
    Ok(())
}

pub fn stash_drop(path: &Path, index: usize) -> Result<(), String> {
    let mut repo = open_repo(path)?;
    repo.stash_drop(index).map_err(git_err("Failed to drop stash"))?;

    Ok(())
}

pub fn load_stash_diff(repo_path: &Path, oid_str: &str) -> Result<Vec<FileDiff>, String> {
    let repo = open_repo(repo_path)?;
    let oid = git2::Oid::from_str(oid_str).map_err(git_err("Invalid OID"))?;
    let commit = repo.find_commit(oid).map_err(git_err("Failed to find stash commit"))?;

    let commit_tree = commit.tree().map_err(git_err("Failed to get stash tree"))?;

    let parent_tree = if commit.parent_count() > 0 {
        Some(
            commit
                .parent(0)
                .map_err(git_err("Failed to get stash base"))?
                .tree()
                .map_err(git_err("Failed to get stash base tree"))?,
        )
    } else {
        None
    };

    let tracked_diff = repo
        .diff_tree_to_tree(parent_tree.as_ref(), Some(&commit_tree), None)
        .map_err(git_err("Failed to generate stash diff"))?;

    let mut file_diffs = file_diffs_from_diff(&tracked_diff)?;

    if commit.parent_count() >= 3 {
        let untracked_tree = commit
            .parent(2)
            .map_err(git_err("Failed to get untracked parent"))?
            .tree()
            .map_err(git_err("Failed to get untracked tree"))?;

        let untracked_diff = repo
            .diff_tree_to_tree(None, Some(&untracked_tree), None)
            .map_err(git_err("Failed to generate untracked diff"))?;

        file_diffs.extend(file_diffs_from_diff(&untracked_diff)?);
    }

    Ok(file_diffs)
}
