import * as fsp from "node:fs/promises";
import * as path from "node:path";
import { hashDirectoryContents, hashFileContents, } from "@superblocksteam/shared";
import { unreachable } from "@superblocksteam/util";
import { listLocalDirectory } from "../list-dir.js";
/**
 * Takes a snapshot of a local directory. The current implementation stores the snapshot in memory.
 * This is not scalable for large directories and is subject to change.
 *
 * This function now captures the DRAFT state (current filesystem state).
 * For committed state, use snapshotLocalDirectoryCommittedState.
 *
 * @param localDirectoryPath - The path to the local directory to snapshot.
 * @returns A promise that resolves to a snapshot of the local directory.
 */
export async function snapshotLocalDirectory(localDirectoryPath) {
    const directoryContents = [];
    const localDirListing = listLocalDirectory(localDirectoryPath);
    for await (const localDirEntry of localDirListing) {
        let entry;
        switch (localDirEntry.type) {
            case "-": {
                const hash = await hashFileContents(localDirEntry.contents);
                entry = {
                    type: "-",
                    name: localDirEntry.name,
                    executable: localDirEntry.executable,
                    hash,
                    contents: localDirEntry.contents,
                };
                break;
            }
            case "d": {
                const subdirectoryContents = await snapshotLocalDirectory(localDirEntry.localPath);
                entry = {
                    type: "d",
                    name: localDirEntry.name,
                    hash: subdirectoryContents.hash,
                    contents: subdirectoryContents.contents,
                };
                break;
            }
            case "l": {
                entry = {
                    type: "l",
                    target: localDirEntry.target,
                    name: localDirEntry.name,
                };
                break;
            }
            default:
                unreachable(localDirEntry);
        }
        directoryContents.push(entry);
    }
    const directoryHash = await hashDirectoryContents(directoryContents);
    return {
        hash: directoryHash,
        contents: directoryContents,
    };
}
/**
 * Takes a snapshot of a local directory showing the COMMITTED state.
 * This function:
 * - Uses .sbswap.edit content from the drafts directory instead of current files (for edited files)
 * - Excludes files that have .sbswap.add markers in the drafts directory (draft additions)
 * - Excludes .sbswap.edit and .sbswap.add marker files themselves
 *
 * @param localDirectoryPath - The path to the local directory to snapshot.
 * @param appRootPath - The app root path (defaults to localDirectoryPath for top-level calls).
 * @param editMarkerMap - Map of edit markers (for internal recursive use).
 * @param addMarkerSet - Set of add markers (for internal recursive use).
 * @returns A promise that resolves to a snapshot of the committed state.
 */
export async function snapshotLocalDirectoryCommittedState(localDirectoryPath, appRootPath, editMarkerMap, addMarkerSet) {
    const directoryContents = [];
    // Use the provided appRootPath, or default to localDirectoryPath for top-level calls
    const rootPath = appRootPath || localDirectoryPath;
    const draftsDir = path.join(rootPath, ".superblocks", "drafts");
    // Step 1: Collect all physical entries in the directory
    const allPhysicalEntries = [];
    const localDirListing = listLocalDirectory(localDirectoryPath);
    for await (const entry of localDirListing) {
        allPhysicalEntries.push(entry);
    }
    // Step 2: Create maps for marker files from the drafts directory (only for top-level calls)
    let finalEditMarkerMap;
    let finalAddMarkerSet;
    if (!editMarkerMap || !addMarkerSet) {
        // This is the top-level call, scan the drafts directory
        finalEditMarkerMap = new Map();
        finalAddMarkerSet = new Set();
        try {
            await fsp.access(draftsDir);
            await scanDraftsDirectory(draftsDir, rootPath, finalEditMarkerMap, finalAddMarkerSet);
        }
        catch {
            // Drafts directory doesn't exist, no drafts to consider
        }
    }
    else {
        // Use the provided maps from recursive calls
        finalEditMarkerMap = editMarkerMap;
        finalAddMarkerSet = addMarkerSet;
    }
    // Step 3: Process entries for committed state
    for (const localDirEntry of allPhysicalEntries) {
        // Skip the .superblocks directory entirely
        if (localDirEntry.name === ".superblocks") {
            continue;
        }
        const relativePath = path.relative(rootPath, localDirEntry.localPath);
        // Skip files that are draft additions
        if (finalAddMarkerSet.has(relativePath)) {
            continue;
        }
        let entry;
        switch (localDirEntry.type) {
            case "-": {
                // Regular file
                let fileContentsToUse;
                let executableToUse;
                if (finalEditMarkerMap.has(relativePath)) {
                    // Use the original content from .sbswap.edit file in drafts directory
                    fileContentsToUse = finalEditMarkerMap.get(relativePath);
                    executableToUse = localDirEntry.executable; // Use current executable flag
                }
                else {
                    // No edit marker, use current file content (this is committed content)
                    fileContentsToUse = localDirEntry.contents;
                    executableToUse = localDirEntry.executable;
                }
                const hash = await hashFileContents(fileContentsToUse);
                entry = {
                    type: "-",
                    name: localDirEntry.name,
                    executable: executableToUse,
                    hash,
                    contents: fileContentsToUse,
                };
                break;
            }
            case "d": {
                // Directory
                // Recursive call uses the same committed-state snapshotting, passing the app root and marker maps
                const subdirectorySnapshot = await snapshotLocalDirectoryCommittedState(localDirEntry.localPath, rootPath, // Pass the app root path to recursive calls
                finalEditMarkerMap, // Pass the edit marker map
                finalAddMarkerSet);
                entry = {
                    type: "d",
                    name: localDirEntry.name,
                    hash: subdirectorySnapshot.hash,
                    contents: subdirectorySnapshot.contents,
                };
                break;
            }
            case "l": {
                // Symlink
                entry = {
                    type: "l",
                    target: localDirEntry.target,
                    name: localDirEntry.name,
                };
                break;
            }
            default:
                unreachable(localDirEntry);
        }
        directoryContents.push(entry);
    }
    const directoryHash = await hashDirectoryContents(directoryContents);
    return {
        hash: directoryHash,
        contents: directoryContents,
    };
}
/**
 * Recursively scans the drafts directory to build maps of edit markers and add markers.
 *
 * @param currentDraftsPath - The current path being scanned in the drafts directory.
 * @param appRootPath - The app root directory path.
 * @param editMarkerMap - Map to store edit marker file contents.
 * @param addMarkerSet - Set to store paths of files that are draft additions.
 */
async function scanDraftsDirectory(currentDraftsPath, appRootPath, editMarkerMap, addMarkerSet) {
    try {
        const entries = await fsp.readdir(currentDraftsPath, {
            withFileTypes: true,
        });
        const draftsDir = path.join(appRootPath, ".superblocks", "drafts");
        for (const entry of entries) {
            const fullPath = path.join(currentDraftsPath, entry.name);
            if (entry.isFile()) {
                if (entry.name.endsWith(".sbswap.edit")) {
                    // This is the original content for an edited file
                    const markerRelativePath = path.relative(draftsDir, fullPath);
                    const originalRelativePath = markerRelativePath.slice(0, -".sbswap.edit".length);
                    const originalContent = await fsp.readFile(fullPath);
                    editMarkerMap.set(originalRelativePath, originalContent);
                }
                else if (entry.name.endsWith(".sbswap.add")) {
                    // This marks a file as a draft addition - exclude the corresponding file
                    const markerRelativePath = path.relative(draftsDir, fullPath);
                    const addedFileRelativePath = markerRelativePath.slice(0, -".sbswap.add".length);
                    addMarkerSet.add(addedFileRelativePath);
                }
            }
            if (entry.isDirectory()) {
                await scanDraftsDirectory(fullPath, appRootPath, editMarkerMap, addMarkerSet);
            }
        }
    }
    catch {
        // Ignore errors reading directories
    }
}
/**
 * Takes a snapshot of a local directory, prioritizing .sbswap files for content.
 * .sbswap files themselves are not included in the snapshot output.
 *
 * @param localDirectoryPath - The path to the local directory to snapshot.
 * @returns A promise that resolves to a snapshot of the local directory, reflecting draft states.
 */
export async function snapshotLocalDirectoryWithDrafts(localDirectoryPath) {
    return snapshotLocalDirectory(localDirectoryPath);
}
//# sourceMappingURL=take-snapshot.js.map