"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resumeFromCapsule = exports.MAX_RESUME_CAPSULE_BYTES = exports.DEFAULT_RESUME_EXPIRY_MS = exports.DEFAULT_RESUME_SESSION = exports.RESUME_CAPSULE_SCHEMA_VERSION = void 0;
exports.createResumeCapsule = createResumeCapsule;
exports.inspectResumeCapsule = inspectResumeCapsule;
exports.clearResumeCapsule = clearResumeCapsule;
exports.cleanupResumeCapsule = cleanupResumeCapsule;
exports.resolveResumeCapsuleStatePath = resolveResumeCapsuleStatePath;
const node_crypto_1 = require("node:crypto");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const output_safety_1 = require("../output/output-safety");
const errors_1 = require("../errors");
const paths_1 = require("../paths");
const path_resolution_1 = require("../runtime/path-resolution");
const trust_lanes_1 = require("../prompts/trust-lanes");
exports.RESUME_CAPSULE_SCHEMA_VERSION = 1;
exports.DEFAULT_RESUME_SESSION = "default";
exports.DEFAULT_RESUME_EXPIRY_MS = 24 * 60 * 60 * 1000;
exports.MAX_RESUME_CAPSULE_BYTES = 64 * 1024;
const MAX_ITEMS = 128;
const SESSION_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
function createResumeCapsule(options) {
    const session = normalizeSession(options.session);
    const now = options.now ?? new Date();
    const expiryMs = options.expiresInMs ?? exports.DEFAULT_RESUME_EXPIRY_MS;
    if (!Number.isSafeInteger(expiryMs) || expiryMs <= 0)
        throw (0, errors_1.createUsageError)("Resume expiry must be a positive integer number of milliseconds.");
    const artifact = readArtifact(options.repositoryRoot, options.artifactPath, true);
    const evidence = capturePaths(options.repositoryRoot, options.evidencePaths ?? []);
    const postconditions = capturePaths(options.repositoryRoot, options.postconditionPaths ?? []);
    const completedActionIds = normalizeStrings(options.completedActionIds ?? [], "completed action ids");
    const unresolvedBlockers = normalizeStrings(options.unresolvedBlockers ?? [], "unresolved blockers");
    validateAction(options.nextAction);
    const statePath = resolveResumeCapsuleStatePath(options.repositoryRoot, options.env);
    withLock(statePath, () => {
        const store = readStore(statePath, options.repositoryRoot);
        assertPrecondition(store, session, options.ifMatch);
        const revision = store.revision + 1;
        const record = (0, output_safety_1.sanitizeOutputValue)({
            schemaVersion: 1,
            session,
            artifactPath: artifact.path,
            artifactType: artifact.artifactType,
            artifactHash: artifact.hash,
            createdAt: now.toISOString(),
            expiresAt: new Date(now.getTime() + expiryMs).toISOString(),
            completedActionIds,
            evidence,
            postconditions,
            unresolvedBlockers,
            nextAction: options.nextAction,
            revision
        }, { repositoryRoot: options.repositoryRoot, environment: options.env });
        if (Buffer.byteLength(JSON.stringify(record), "utf8") > exports.MAX_RESUME_CAPSULE_BYTES) {
            throw (0, errors_1.createUsageError)(`Resume capsule exceeds the ${exports.MAX_RESUME_CAPSULE_BYTES}-byte limit.`);
        }
        store.revision = revision;
        store.sessions[session] = record;
        writeStore(statePath, store);
    });
    return inspectResumeCapsule({ ...options, session, now });
}
function inspectResumeCapsule(options) {
    const session = normalizeSession(options.session);
    const statePath = resolveResumeCapsuleStatePath(options.repositoryRoot, options.env);
    const store = readStore(statePath, options.repositoryRoot);
    const capsule = store.sessions[session] ?? null;
    const base = {
        session,
        capsule,
        workspaceKey: store.workspaceKey,
        storeRevision: store.revision,
        storageScope: "user-local",
        preconditionToken: token(store, session),
        trustLanes: {
            capsuleClaims: (0, trust_lanes_1.getTrustLane)("generated-evidence"),
            selectedArtifact: (0, trust_lanes_1.getTrustLane)("selected-artifact"),
            capturedEvidence: (0, trust_lanes_1.getTrustLane)("repository-prose"),
            nextAction: (0, trust_lanes_1.getTrustLane)("command-metadata")
        }
    };
    if (capsule === null)
        return { ...base, state: "none", staleReason: null, conflict: null, completedActionIds: [] };
    const explicit = options.explicitArtifactPath === undefined
        ? null
        : (0, paths_1.parseRepositoryRelativePath)(options.explicitArtifactPath).normalizedPath;
    const selected = options.selectedArtifactPath == null
        ? null
        : (0, paths_1.parseRepositoryRelativePath)(options.selectedArtifactPath).normalizedPath;
    const conflicting = explicit ?? selected;
    if (conflicting !== null && conflicting !== capsule.artifactPath) {
        return {
            ...base,
            state: "conflict",
            staleReason: null,
            completedActionIds: [],
            conflict: { code: "RESUME_SELECTION_CONFLICT", explicitArtifactPath: conflicting, capsuleArtifactPath: capsule.artifactPath, noWrite: true }
        };
    }
    const staleReason = freshness(options.repositoryRoot, capsule, options.now ?? new Date());
    if (staleReason !== null)
        return { ...base, state: "stale", staleReason, conflict: null, completedActionIds: [] };
    return { ...base, state: "fresh", staleReason: null, conflict: null, completedActionIds: [...capsule.completedActionIds] };
}
exports.resumeFromCapsule = inspectResumeCapsule;
function clearResumeCapsule(options) {
    return mutateRemoval(options, false);
}
function cleanupResumeCapsule(options) {
    return mutateRemoval(options, true);
}
function resolveResumeCapsuleStatePath(repositoryRoot, env = process.env) {
    return node_path_1.default.join((0, path_resolution_1.resolveDayShiftUserStateDirectory)({ env }).stateDirectory, "resume-capsules", `${workspaceKey(repositoryRoot)}.json`);
}
function mutateRemoval(options, staleOnly) {
    const session = normalizeSession(options.session);
    const statePath = resolveResumeCapsuleStatePath(options.repositoryRoot, options.env);
    return withLock(statePath, () => {
        const store = readStore(statePath, options.repositoryRoot);
        const capsule = store.sessions[session];
        if (capsule === undefined)
            return { changed: false, reason: "capsule-absent", session, storeRevision: store.revision, workspaceKey: store.workspaceKey, storageScope: "user-local" };
        if (staleOnly && freshness(options.repositoryRoot, capsule, options.now ?? new Date()) === null) {
            return { changed: false, reason: "capsule-fresh", session, storeRevision: store.revision, workspaceKey: store.workspaceKey, storageScope: "user-local" };
        }
        assertPrecondition(store, session, options.ifMatch);
        delete store.sessions[session];
        store.revision += 1;
        writeStore(statePath, store);
        return { changed: true, reason: staleOnly ? "stale-cleared" : "capsule-cleared", session, storeRevision: store.revision, workspaceKey: store.workspaceKey, storageScope: "user-local" };
    });
}
function freshness(root, capsule, now) {
    if (now.getTime() >= Date.parse(capsule.expiresAt))
        return "expired";
    const artifact = readArtifact(root, capsule.artifactPath, false);
    if (!artifact.exists)
        return "artifact-missing";
    if (artifact.artifactType !== capsule.artifactType)
        return "artifact-identity-changed";
    if (artifact.hash !== capsule.artifactHash)
        return "artifact-content-changed";
    for (const item of capsule.evidence) {
        const current = readArtifact(root, item.path, false);
        if (!current.exists)
            return "evidence-missing";
        if (current.hash !== item.hash)
            return "evidence-changed";
    }
    for (const item of capsule.postconditions) {
        const current = readArtifact(root, item.path, false);
        if (!current.exists)
            return "postcondition-missing";
        if (current.hash !== item.hash)
            return "postcondition-changed";
    }
    return null;
}
function capturePaths(root, values) {
    if (values.length > MAX_ITEMS)
        throw (0, errors_1.createUsageError)(`Resume evidence lists may contain at most ${MAX_ITEMS} paths.`);
    return [...new Set(values)].sort().map((value) => {
        const current = readArtifact(root, value, true);
        return { path: current.path, hash: current.hash };
    });
}
function readArtifact(root, value, required) {
    const parsed = (0, paths_1.parseRepositoryRelativePath)(value);
    const absolute = (0, paths_1.resolveRepositoryRelativePath)(root, parsed);
    if (!node_fs_1.default.existsSync(absolute) || !node_fs_1.default.statSync(absolute).isFile()) {
        if (required)
            throw (0, errors_1.createUsageError)(`Resume evidence '${parsed.normalizedPath}' does not exist.`);
        return { path: parsed.normalizedPath, exists: false, hash: "", artifactType: null };
    }
    const raw = node_fs_1.default.readFileSync(absolute, "utf8");
    return {
        path: parsed.normalizedPath,
        exists: true,
        hash: `sha256:${(0, node_crypto_1.createHash)("sha256").update(raw).digest("hex")}`,
        artifactType: raw.match(/^artifact_type:\s*["']?([^"'\r\n]+)["']?\s*$/m)?.[1].trim() ?? null
    };
}
function normalizeStrings(values, label) {
    if (values.length > MAX_ITEMS)
        throw (0, errors_1.createUsageError)(`Resume ${label} may contain at most ${MAX_ITEMS} entries.`);
    return [...new Set(values.map((value) => value.trim()))].filter(Boolean).sort().map((value) => {
        if (value.length > 512 || /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(value))
            throw (0, errors_1.createUsageError)(`Resume ${label} contain an invalid or oversized entry.`);
        return value;
    });
}
function validateAction(action) {
    if (typeof action !== "object" || action === null || typeof action.id !== "string" || typeof action.callable !== "boolean") {
        throw (0, errors_1.createUsageError)("Resume next action must use the executable workflow-action contract.");
    }
    if (action.callable && (!Array.isArray(action.argv) || action.argv.length === 0))
        throw (0, errors_1.createUsageError)("Callable resume actions require exact argv.");
    if (!action.callable && action.argv !== null)
        throw (0, errors_1.createUsageError)("Non-callable resume guidance must not carry argv.");
}
function normalizeSession(value) {
    const session = value?.trim() || exports.DEFAULT_RESUME_SESSION;
    if (!SESSION_PATTERN.test(session))
        throw (0, errors_1.createUsageError)("Resume session must start with a lowercase letter or digit and contain at most 64 lowercase letters, digits, dots, underscores, or hyphens.");
    return session;
}
function readStore(statePath, root) {
    const key = workspaceKey(root);
    if (!node_fs_1.default.existsSync(statePath))
        return { schemaVersion: 1, workspaceKey: key, revision: 0, sessions: {} };
    let value;
    try {
        value = JSON.parse(node_fs_1.default.readFileSync(statePath, "utf8"));
    }
    catch {
        throw (0, errors_1.createInvalidConfigError)("Resume capsule state is malformed JSON.");
    }
    if (value?.schemaVersion !== 1 || value?.workspaceKey !== key || !Number.isInteger(value?.revision) || typeof value?.sessions !== "object" || value.sessions === null) {
        throw (0, errors_1.createInvalidConfigError)("Resume capsule state has an unsupported or mismatched shape.");
    }
    return value;
}
function writeStore(statePath, store) {
    node_fs_1.default.mkdirSync(node_path_1.default.dirname(statePath), { recursive: true });
    const temporary = `${statePath}.${process.pid}.tmp`;
    node_fs_1.default.writeFileSync(temporary, `${JSON.stringify(store, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
    node_fs_1.default.renameSync(temporary, statePath);
}
function token(store, session) {
    const capsule = store.sessions[session] ?? null;
    const payload = JSON.stringify({ v: 1, w: store.workspaceKey, s: session, sr: store.revision, rr: capsule?.revision ?? null, h: capsule?.artifactHash ?? null });
    const encoded = Buffer.from(payload).toString("base64url");
    return `rc1.${encoded}.${(0, node_crypto_1.createHash)("sha256").update(payload).digest("base64url").slice(0, 22)}`;
}
function assertPrecondition(store, session, ifMatch) {
    if (store.sessions[session] === undefined && ifMatch === undefined)
        return;
    if (ifMatch !== token(store, session)) {
        throw new errors_1.RuntimeError({
            code: "RESUME_PRECONDITION_FAILED",
            exitCode: errors_1.RuntimeExitCode.CONCURRENCY_CONFLICT,
            message: "Resume capsule precondition does not match current session state.",
            details: [`Session: ${session}`, `Current store revision: ${store.revision}`, "Inspect the capsule for a fresh token. No resume state was changed."]
        });
    }
}
function withLock(statePath, operation) {
    node_fs_1.default.mkdirSync(node_path_1.default.dirname(statePath), { recursive: true });
    const lockPath = `${statePath}.lock`;
    let descriptor;
    try {
        descriptor = node_fs_1.default.openSync(lockPath, "wx", 0o600);
    }
    catch {
        throw new errors_1.RuntimeError({ code: "RESUME_STATE_LOCKED", exitCode: errors_1.RuntimeExitCode.CONCURRENCY_CONFLICT, message: "Resume capsule state is busy with another mutation.", details: ["Retry after inspecting current capsule evidence. No resume state was changed."] });
    }
    try {
        return operation();
    }
    finally {
        node_fs_1.default.closeSync(descriptor);
        try {
            node_fs_1.default.unlinkSync(lockPath);
        }
        catch { }
    }
}
function workspaceKey(root) {
    const canonical = node_fs_1.default.realpathSync(root);
    const marker = node_fs_1.default.existsSync(node_path_1.default.join(canonical, ".day-shift", "config.toml")) ? ".day-shift/config.toml" : ".git";
    return `ws-${(0, node_crypto_1.createHash)("sha256").update(`${canonical}\0${marker}`).digest("hex").slice(0, 24)}`;
}
