use std::fmt::{self, Debug};

use dashmap::DashMap;
use tracing::{info_span, instrument};

use super::{query, Content, ContentVersion, Dir, Query, VPath, VPathBuf};

pub struct Blake3ContentVersionCache<T> {
    salt: Vec<u8>,
    inner: T,
    cache: DashMap<VPathBuf, ContentVersion>,
}

impl<T> Blake3ContentVersionCache<T> {
    pub fn new(salt: Vec<u8>, inner: T) -> Self {
        Self {
            salt,
            inner,
            cache: DashMap::new(),
        }
    }
}

impl<T> Blake3ContentVersionCache<T>
where
    T: Dir,
{
    #[instrument(name = "Blake3ContentVersionCache::content_version", skip(self))]
    fn content_version(&self, path: &VPath) -> Option<ContentVersion> {
        self.cache.get(path).map(|x| x.clone()).or_else(|| {
            let _span = info_span!("cache_miss").entered();

            let version = query::<Content>(&self.inner, path).map(|content| {
                let mut hasher = blake3::Hasher::new();
                hasher.update(&self.salt);
                hasher.update(&content.bytes());
                let hash = hasher.finalize().to_hex();
                ContentVersion {
                    string: format!("b3-{}", &hash[0..8]),
                }
            });
            if let Some(version) = &version {
                self.cache.insert(path.to_owned(), version.clone());
            }
            version
        })
    }
}

impl<T> Dir for Blake3ContentVersionCache<T>
where
    T: Dir,
{
    fn query(&self, path: &VPath, query: &mut Query) {
        query.try_provide(|| self.content_version(path));

        self.inner.query(path, query);
    }
}

impl<T> fmt::Debug for Blake3ContentVersionCache<T>
where
    T: Debug,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Blake3ContentVersionCache({:?})", self.inner)
    }
}
