//! Tree query operations.

use std::path::Path;

use crate::commit::types::CommitOid;
use crate::tree::types::{TreeEntry, TreeInfo};
use crate::{GitDomain, GitError, GitResult};

impl GitDomain {
    pub fn tree_get(&self, oid: &CommitOid) -> GitResult<TreeInfo> {
        let oid = oid
            .to_oid()
            .map_err(|_| GitError::InvalidOid(oid.to_string()))?;
        let tree = self
            .repo()
            .find_tree(oid)
            .map_err(|_| GitError::ObjectNotFound(oid.to_string()))?;
        Ok(TreeInfo::from_git2(&tree))
    }

    pub fn tree_exists(&self, oid: &CommitOid) -> bool {
        oid.to_oid()
            .ok()
            .and_then(|oid| self.repo.find_tree(oid).ok())
            .is_some()
    }

    pub fn tree_entry(&self, oid: &CommitOid, index: usize) -> GitResult<TreeEntry> {
        let oid = oid
            .to_oid()
            .map_err(|_| GitError::InvalidOid(oid.to_string()))?;
        let tree = self
            .repo()
            .find_tree(oid)
            .map_err(|_| GitError::ObjectNotFound(oid.to_string()))?;
        let entry = tree
            .get(index)
            .ok_or_else(|| GitError::Internal("tree entry not found".to_string()))?;
        Ok(TreeEntry::from_git2(entry, self.repo()))
    }

    pub fn tree_list(&self, oid: &CommitOid) -> GitResult<Vec<TreeEntry>> {
        let oid = oid
            .to_oid()
            .map_err(|_| GitError::InvalidOid(oid.to_string()))?;
        let tree = self
            .repo()
            .find_tree(oid)
            .map_err(|_| GitError::ObjectNotFound(oid.to_string()))?;
        let repo = self.repo();
        let entries: Vec<TreeEntry> = tree
            .iter()
            .map(|entry| TreeEntry::from_git2(entry, repo))
            .collect();
        Ok(entries)
    }

    pub fn tree_entry_count(&self, oid: &CommitOid) -> GitResult<usize> {
        let info = self.tree_get(oid)?;
        Ok(info.entry_count)
    }

    pub fn tree_entry_by_path(&self, tree_oid: &CommitOid, path: &str) -> GitResult<TreeEntry> {
        let oid = tree_oid
            .to_oid()
            .map_err(|_| GitError::InvalidOid(tree_oid.to_string()))?;
        let tree = self
            .repo()
            .find_tree(oid)
            .map_err(|_| GitError::ObjectNotFound(tree_oid.to_string()))?;
        let entry = tree
            .get_path(Path::new(path))
            .map_err(|e| GitError::Internal(format!("path '{}': {}", path, e)))?;
        Ok(TreeEntry::from_git2(entry, self.repo()))
    }

    pub fn tree_entry_by_path_from_commit(
        &self,
        commit_oid: &CommitOid,
        path: &str,
    ) -> GitResult<TreeEntry> {
        let oid = commit_oid
            .to_oid()
            .map_err(|_| GitError::InvalidOid(commit_oid.to_string()))?;
        let commit = self
            .repo()
            .find_commit(oid)
            .map_err(|_| GitError::ObjectNotFound(commit_oid.to_string()))?;
        let tree = self
            .repo()
            .find_tree(commit.tree_id())
            .map_err(|e| GitError::Internal(e.to_string()))?;
        let entry = tree
            .get_path(Path::new(path))
            .map_err(|e| GitError::Internal(format!("path '{}': {}", path, e)))?;
        Ok(TreeEntry::from_git2(entry, self.repo()))
    }

    pub fn tree_is_empty(&self, oid: &CommitOid) -> GitResult<bool> {
        let info = self.tree_get(oid)?;
        Ok(info.is_empty)
    }

    pub fn tree_diffstats(
        &self,
        old_tree: &CommitOid,
        new_tree: &CommitOid,
    ) -> GitResult<crate::diff::types::DiffStats> {
        use crate::diff::types::DiffStats;
        let old_oid = old_tree
            .to_oid()
            .map_err(|_| GitError::InvalidOid(old_tree.to_string()))?;
        let new_oid = new_tree
            .to_oid()
            .map_err(|_| GitError::InvalidOid(new_tree.to_string()))?;
        let old_tree = self
            .repo()
            .find_tree(old_oid)
            .map_err(|e| GitError::Internal(e.to_string()))?;
        let new_tree = self
            .repo()
            .find_tree(new_oid)
            .map_err(|e| GitError::Internal(e.to_string()))?;
        let diff = self
            .repo()
            .diff_tree_to_tree(Some(&old_tree), Some(&new_tree), None)
            .map_err(|e| GitError::Internal(e.to_string()))?;
        let stats = diff
            .stats()
            .map_err(|e| GitError::Internal(e.to_string()))?;
        Ok(DiffStats::from_git2(&stats))
    }
}
