import { createHash } from "node:crypto";
import { readdir, readFile, mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { MINICODE_HOME } from "../agent/config.js";
import { createProjectIndex } from "./project-index.js";
import { loadPlugins } from "./plugin-loader.js";
const CACHE_FILENAME = "index.json";
const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", "coverage"]);
function hashWorkspacePath(workspaceRoot) {
    const normalized = path.resolve(workspaceRoot);
    return createHash("sha256").update(normalized, "utf8").digest("hex").slice(0, 32);
}
/**
 * Return the cache directory for a workspace. Index files live under ~/.minicode/cache/<hash>/
 * so caches are global and keyed by workspace path.
 */
export function getWorkspaceCacheDir(workspaceRoot) {
    return path.join(MINICODE_HOME, "cache", hashWorkspacePath(workspaceRoot));
}
function hashContent(content) {
    return createHash("sha256").update(content, "utf8").digest("hex");
}
async function collectSourceFiles(dir, root, files, validExtensions) {
    const entries = await readdir(dir, { withFileTypes: true });
    for (const entry of entries) {
        const fullPath = path.join(dir, entry.name);
        if (entry.isDirectory()) {
            if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
                await collectSourceFiles(fullPath, root, files, validExtensions);
            }
            continue;
        }
        if (entry.isFile()) {
            const ext = path.extname(entry.name).toLowerCase();
            if (validExtensions.has(ext)) {
                files.push(path.relative(root, fullPath));
            }
        }
    }
}
/**
 * Compute content hashes for all source files in the workspace.
 */
export async function computeFileHashes(workspaceRoot) {
    const root = path.resolve(workspaceRoot);
    const plugins = await loadPlugins(root);
    const validExtensions = new Set(plugins.flatMap((p) => p.extensions));
    const sourceFiles = [];
    await collectSourceFiles(root, root, sourceFiles, validExtensions);
    const hashes = new Map();
    for (const relPath of sourceFiles) {
        const absPath = path.join(root, relPath);
        try {
            const content = await readFile(absPath, "utf8");
            hashes.set(relPath, hashContent(content));
        }
        catch {
            // skip unreadable files
        }
    }
    return hashes;
}
function hashesMatch(cached, current) {
    if (Object.keys(cached).length !== current.size)
        return false;
    for (const [relPath, hash] of current) {
        if (cached[relPath] !== hash)
            return false;
    }
    return true;
}
/**
 * Save the project index to disk. Call after buildProjectIndex.
 */
export async function saveIndex(index, cacheDir, fileHashes) {
    const payload = {
        version: 1,
        fileHashes: Object.fromEntries(fileHashes),
        symbols: [...index.symbols.entries()],
        files: [...index.files.entries()],
        dependencyEdges: [...index.dependencyEdges],
        projectFiles: [...index.projectFiles.entries()],
        workspaceRoot: index.workspaceRoot,
    };
    await mkdir(cacheDir, { recursive: true });
    const cachePath = path.join(cacheDir, CACHE_FILENAME);
    await writeFile(cachePath, JSON.stringify(payload, null, 0), "utf8");
}
/**
 * Load the project index from cache if all file hashes match.
 * Returns null if cache is missing, invalid, or any file has changed.
 */
export async function loadIndex(cacheDir, fileHashes) {
    const cachePath = path.join(cacheDir, CACHE_FILENAME);
    let raw;
    try {
        raw = await readFile(cachePath, "utf8");
    }
    catch {
        return null;
    }
    let payload;
    try {
        payload = JSON.parse(raw);
    }
    catch {
        return null;
    }
    if (payload.version !== 1 || !payload.fileHashes)
        return null;
    if (!hashesMatch(payload.fileHashes, fileHashes))
        return null;
    const symbols = new Map(payload.symbols);
    const files = new Map(payload.files);
    const dependencyEdges = payload.dependencyEdges ?? [];
    const projectFiles = new Map(payload.projectFiles);
    const workspaceRoot = payload.workspaceRoot ?? "";
    const plugins = await loadPlugins(workspaceRoot);
    return createProjectIndexFromCache(symbols, files, dependencyEdges, plugins, projectFiles, workspaceRoot);
}
function createProjectIndexFromCache(symbols, files, dependencyEdges, plugins, projectFiles, workspaceRoot) {
    return createProjectIndex(symbols, files, dependencyEdges, plugins, projectFiles, workspaceRoot);
}
