pkgrank 0.2.5

Centrality analysis for dependency graphs and file-level import graphs
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};

use super::{dep_graph::fnv1a64, Row};

#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct AnalysisCacheEntry {
    pub schema_version: u32,
    pub rows: Vec<Row>,
    pub convergence: serde_json::Value,
}

#[derive(Debug, Serialize)]
pub(crate) struct AnalysisCacheMeta {
    pub generated_at_unix: i64,
    pub manifest_path: String,
    pub key_hex: String,
    pub workspace_only: bool,
    pub dev: bool,
    pub build: bool,
}

pub(crate) fn analysis_cache_key(
    manifest_path: &Path,
    workspace_only: bool,
    dev: bool,
    build: bool,
    all_features: bool,
    no_default_features: bool,
    features: Option<&str>,
) -> u64 {
    let material = format!(
        "manifest={}\nworkspace_only={}\ndev={}\nbuild={}\nall_features={}\nno_default_features={}\nfeatures={:?}",
        manifest_path.display(),
        workspace_only,
        dev,
        build,
        all_features,
        no_default_features,
        features,
    );
    fnv1a64(material.as_bytes())
}

fn cache_path(cache_root: &Path, key: u64, ext: &str) -> std::path::PathBuf {
    cache_root.join(format!("analyze_{key:016x}.{ext}"))
}

pub(crate) fn analysis_cache_read(cache_root: &Path, key: u64) -> Option<AnalysisCacheEntry> {
    let path = cache_path(cache_root, key, "json");
    let raw = fs::read_to_string(&path).ok()?;
    serde_json::from_str(&raw).ok()
}

pub(crate) fn analysis_cache_write(
    cache_root: &Path,
    key: u64,
    entry: &AnalysisCacheEntry,
    manifest_path: &Path,
    workspace_only: bool,
    dev: bool,
    build: bool,
) {
    let _ = fs::create_dir_all(cache_root);
    let data_path = cache_path(cache_root, key, "json");
    let meta_path = cache_path(cache_root, key, "meta.json");

    if let Ok(json) = serde_json::to_string_pretty(entry) {
        let _ = fs::write(&data_path, json);
    }

    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_secs() as i64)
        .unwrap_or(0);
    let meta = AnalysisCacheMeta {
        generated_at_unix: now,
        manifest_path: manifest_path.display().to_string(),
        key_hex: format!("{key:016x}"),
        workspace_only,
        dev,
        build,
    };
    if let Ok(json) = serde_json::to_string_pretty(&meta) {
        let _ = fs::write(&meta_path, json);
    }
}