"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SANDBOX_LIFECYCLE_CONTRACT = void 0;
exports.ensureSandboxRunDirectory = ensureSandboxRunDirectory;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const paths_1 = require("../paths");
const sandbox_contract_1 = require("../paths/sandbox-contract");
const default_assets_1 = require("./default-assets");
exports.SANDBOX_LIFECYCLE_CONTRACT = Object.freeze({
    ownership: "local-only",
    durability: "disposable",
    cleanupPolicy: "manual-deferred"
});
const SANDBOX_PROTECTED_ROOTS = Object.freeze([
    ".day-shift/planning",
    ".day-shift/prompts",
    ".day-shift/templates",
    default_assets_1.RECOMMENDED_MARKDOWNLINT_PATH
]);
function ensureSandboxRunDirectory(options) {
    const runIdResolution = resolveSandboxRunId({
        repositoryRoot: options.repositoryRoot,
        runId: options.runId,
        now: options.now
    });
    const sandboxParentPath = paths_1.SANDBOX_PARENT_DIRECTORY;
    const sandboxRunPath = (0, sandbox_contract_1.assertSandboxRunDirectoryPath)((0, paths_1.composeSandboxRunDirectoryPath)(runIdResolution.runId));
    const dryRun = options.dryRun ?? false;
    const parentAbsolutePath = node_path_1.default.join(options.repositoryRoot, sandboxParentPath);
    const runAbsolutePath = node_path_1.default.join(options.repositoryRoot, sandboxRunPath);
    assertManagedDayShiftRootIsSafe(options.repositoryRoot);
    assertSandboxBoundaryPolicy(options.repositoryRoot, sandboxParentPath);
    assertExistingSandboxPathIsSafe({
        repositoryRoot: options.repositoryRoot,
        relativePath: sandboxParentPath,
        absolutePath: parentAbsolutePath,
        kind: "parent"
    });
    assertExistingSandboxPathIsSafe({
        repositoryRoot: options.repositoryRoot,
        relativePath: sandboxRunPath,
        absolutePath: runAbsolutePath,
        kind: "run"
    });
    const parentExists = node_fs_1.default.existsSync(parentAbsolutePath);
    const runExists = node_fs_1.default.existsSync(runAbsolutePath);
    if (!dryRun && !parentExists) {
        node_fs_1.default.mkdirSync(parentAbsolutePath, { recursive: true });
    }
    if (!dryRun && !runExists) {
        node_fs_1.default.mkdirSync(runAbsolutePath, { recursive: true });
    }
    return {
        runId: runIdResolution.runId,
        runIdSource: runIdResolution.source,
        sandboxParentPath,
        sandboxRunPath,
        parentDirectory: {
            path: sandboxParentPath,
            action: parentExists ? "reuse" : "create"
        },
        runDirectory: {
            path: sandboxRunPath,
            action: runExists ? "reuse" : "create"
        },
        lifecycleContract: exports.SANDBOX_LIFECYCLE_CONTRACT
    };
}
function assertManagedDayShiftRootIsSafe(repositoryRoot) {
    const managedRoot = node_path_1.default.join(repositoryRoot, ".day-shift");
    if (!node_fs_1.default.existsSync(managedRoot))
        return;
    const stat = node_fs_1.default.lstatSync(managedRoot);
    if (stat.isSymbolicLink() || !stat.isDirectory()) {
        throw new RangeError("Managed Day Shift root '.day-shift' must be a non-symbolic directory.");
    }
    if (!(0, paths_1.pathsEqualForFilesystem)(node_fs_1.default.realpathSync(managedRoot), node_path_1.default.resolve(repositoryRoot, ".day-shift"))) {
        throw new RangeError("Managed Day Shift root '.day-shift' must resolve inside the repository root.");
    }
}
function assertSandboxBoundaryPolicy(repositoryRoot, sandboxParentPath) {
    const expectedSandboxParentAbsolutePath = node_path_1.default.resolve(repositoryRoot, sandboxParentPath);
    for (const protectedRoot of SANDBOX_PROTECTED_ROOTS) {
        const protectedRootAbsolutePath = node_path_1.default.resolve(repositoryRoot, protectedRoot);
        if ((0, paths_1.pathsOverlap)(expectedSandboxParentAbsolutePath, protectedRootAbsolutePath)) {
            throw new RangeError(`Sandbox parent path '${sandboxParentPath}' must not overlap protected root '${protectedRoot}'.`);
        }
    }
}
function assertExistingSandboxPathIsSafe(options) {
    if (!node_fs_1.default.existsSync(options.absolutePath)) {
        return;
    }
    const label = options.kind === "parent" ? "Sandbox parent path" : "Sandbox run path";
    const stat = node_fs_1.default.lstatSync(options.absolutePath);
    if (stat.isSymbolicLink()) {
        throw new RangeError(`${label} '${options.relativePath}' must not be a symbolic link.`);
    }
    if (!stat.isDirectory()) {
        throw new RangeError(`${label} '${options.relativePath}' must be a directory.`);
    }
    const expectedAbsolutePath = node_path_1.default.resolve(options.repositoryRoot, options.relativePath);
    const canonicalAbsolutePath = node_fs_1.default.realpathSync(options.absolutePath);
    if ((0, paths_1.pathsEqualForFilesystem)(canonicalAbsolutePath, expectedAbsolutePath)) {
        return;
    }
    const protectedRoot = findOverlappingProtectedRoot(canonicalAbsolutePath, options.repositoryRoot);
    if (protectedRoot !== null) {
        throw new RangeError(`${label} '${options.relativePath}' resolves into protected root '${protectedRoot}'.`);
    }
    throw new RangeError(`${label} '${options.relativePath}' must resolve to '${expectedAbsolutePath}'.`);
}
function findOverlappingProtectedRoot(absolutePath, repositoryRoot) {
    for (const protectedRoot of SANDBOX_PROTECTED_ROOTS) {
        const protectedRootAbsolutePath = node_path_1.default.resolve(repositoryRoot, protectedRoot);
        if ((0, paths_1.pathsOverlap)(absolutePath, protectedRootAbsolutePath)) {
            return protectedRoot;
        }
    }
    return null;
}
function resolveSandboxRunId(options) {
    if (options.runId !== undefined) {
        return {
            runId: options.runId,
            source: "manual"
        };
    }
    return {
        runId: composeAutomaticSandboxRunId(options.repositoryRoot, options.now ?? new Date()),
        source: "auto"
    };
}
function composeAutomaticSandboxRunId(repositoryRoot, now) {
    const dayStamp = formatUtcDayStamp(now);
    const nextSequence = resolveNextRunSequence(repositoryRoot, dayStamp);
    return `${dayStamp}-${String(nextSequence).padStart(3, "0")}`;
}
function formatUtcDayStamp(now) {
    const year = String(now.getUTCFullYear());
    const month = String(now.getUTCMonth() + 1).padStart(2, "0");
    const day = String(now.getUTCDate()).padStart(2, "0");
    return `${year}${month}${day}`;
}
function resolveNextRunSequence(repositoryRoot, dayStamp) {
    const sandboxRoot = node_path_1.default.join(repositoryRoot, paths_1.SANDBOX_PARENT_DIRECTORY);
    if (!node_fs_1.default.existsSync(sandboxRoot)) {
        return 1;
    }
    let nextSequence = 1;
    const runIdPattern = new RegExp(`^${dayStamp}-(\\d{3})$`);
    for (const entry of node_fs_1.default.readdirSync(sandboxRoot, { withFileTypes: true })) {
        if (!entry.isDirectory()) {
            continue;
        }
        if (!entry.name.startsWith(paths_1.SANDBOX_RUN_DIRECTORY_PREFIX)) {
            continue;
        }
        const candidateRunId = entry.name.slice(paths_1.SANDBOX_RUN_DIRECTORY_PREFIX.length);
        const match = runIdPattern.exec(candidateRunId);
        if (match === null) {
            continue;
        }
        const parsedSequence = Number.parseInt(match[1], 10);
        if (!Number.isNaN(parsedSequence) && parsedSequence >= nextSequence) {
            nextSequence = parsedSequence + 1;
        }
    }
    return nextSequence;
}
