//! Materializer for Node.js dependencies (`node_modules`).

use super::Materializer;
use crate::core::types::{LockfileEntry, PackageManager, Workspace};
use crate::error::{Error, Result};
use std::path::{Path, PathBuf};

#[cfg(unix)]
use std::os::unix::fs::symlink;
#[cfg(windows)]
use std::os::windows::fs::symlink_dir as symlink;

/// Materializer for Node.js projects.
pub struct NodeModulesMaterializer;

impl Materializer for NodeModulesMaterializer {
    fn materialize(
        &self,
        workspace: &Workspace,
        _entries: &[LockfileEntry],
        target_dir: &Path,
    ) -> Result<()> {
        if !matches!(
            workspace.manager,
            PackageManager::Npm
                | PackageManager::Bun
                | PackageManager::Pnpm
                | PackageManager::YarnClassic
                | PackageManager::YarnModern
        ) {
            return Ok(());
        }

        // Strategy:
        // 1. Symlink the entire node_modules from workspace root if it exists.
        //    This allows leveraging the existing installed dependencies.
        // 2. (Future) Link individual packages from global cache (pnpm store, etc.)
        //    for granular, hermetic population.

        let workspace_nm = workspace.root.join("node_modules");
        let target_nm = target_dir.join("node_modules");

        if workspace_nm.exists() {
            if !target_nm.exists() {
                symlink(&workspace_nm, &target_nm).map_err(|e| Error::Io {
                    source: e,
                    path: Some(target_nm.clone()),
                    operation: "symlink node_modules".to_string(),
                })?;
            }
        } else {
            // Warn or log that dependencies might be missing?
            // For now, we assume if node_modules is missing, the user might run install
            // or we are in a state where it's not needed yet.
        }

        Ok(())
    }
}

impl NodeModulesMaterializer {
    /// Detects the global cache directory for the package manager.
    ///
    /// This can be used in the future to populate dependencies directly from cache.
    pub fn detect_cache_dir(manager: PackageManager) -> Option<PathBuf> {
        let home = std::env::var_os("HOME")
            .or_else(|| std::env::var_os("USERPROFILE"))
            .map(PathBuf::from)?;

        match manager {
            PackageManager::Npm => Some(home.join(".npm")),
            PackageManager::Pnpm => {
                // pnpm store path can vary (v3, etc), checking common default
                Some(home.join(".local/share/pnpm/store/v3"))
            }
            PackageManager::Bun => Some(home.join(".bun/install/cache")),
            PackageManager::YarnClassic => Some(home.join(".yarn/cache")),
            PackageManager::YarnModern => Some(home.join(".yarn/berry/cache")),
            PackageManager::Deno => Some(home.join(".cache/deno")),
            PackageManager::Cargo => None,
        }
    }
}
