use std::sync::{Arc, OnceLock};
use super::git::Git;

struct ForkPointInner {
    fork_point: OnceLock<Option<Vec<u8>>>,
    repo1: Git,
    repo2: Git,
    commit1: Vec<u8>,
    commit2: Vec<u8>,
}

/// On demand computed fork point between two code bases.
#[derive(Clone)]
pub struct ForkPoint(Arc<ForkPointInner>);

impl ForkPoint {
    pub fn new(repo1: Git, repo2: Git, commit1: Vec<u8>, commit2: Vec<u8>) -> Self {
        Self(Arc::new(ForkPointInner {
            fork_point: OnceLock::new(),
            repo1,
            repo2,
            commit1,
            commit2,
        }))
    }

    /// Get the fork point. This will block (potentially for seconds) if the fork point
    /// hasn't been calculated yet.
    pub fn get(&self) -> Option<&[u8]> {
        self.0.fork_point.get_or_init(||
            if self.0.repo1 == self.0.repo2 {
                self.simple_merge_base()
            } else {
                self.combined_merge_base()
            }
        ).as_deref()
    }

    /// The simple case: repo1 and repo2 point to the same repository.
    fn simple_merge_base(&self) -> Option<Vec<u8>> {
        // If the two code bases do not share a common ancestor, merge_base will return
        // an Err. Treat that as None.
        self.0.repo1.merge_base(&self.0.commit1, &self.0.commit2).ok()
    }

    /// The complicated case: repo1 and repo2 are different. Create a temporary repository
    /// pointing to both.
    fn combined_merge_base(&self) -> Option<Vec<u8>> {
        // Treat any error as not having a common ancestor. That's not completely correct
        // but currently, finding ForkPoint is just an optimization to speed up some git
        // operations.
        let commit1 = self.0.repo1.revparse(&self.0.commit1).ok()?;
        let commit2 = self.0.repo2.revparse(&self.0.commit2).ok()?;
        let dir = tempfile::tempdir().ok()?;
        let repo = Git::create(dir.path().as_os_str(), true).ok()?;
        repo.add_to_alternates([&self.0.repo1, &self.0.repo2]).ok()?;
        repo.merge_base(&commit1, &commit2).ok()
    }
}
