"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WORK_SELECTION_SCHEMA_VERSION = exports.DEFAULT_WORK_SELECTION_SESSION = void 0;
exports.selectWorkArtifact = selectWorkArtifact;
exports.inspectWorkSelection = inspectWorkSelection;
exports.clearWorkSelection = clearWorkSelection;
exports.cleanupStaleWorkSelection = cleanupStaleWorkSelection;
exports.resolveWorkSelection = resolveWorkSelection;
exports.resolveWorkSelectionStatePath = resolveWorkSelectionStatePath;
const node_crypto_1 = require("node:crypto");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const errors_1 = require("../errors");
const paths_1 = require("../paths");
const path_resolution_1 = require("../runtime/path-resolution");
exports.DEFAULT_WORK_SELECTION_SESSION = "default";
exports.WORK_SELECTION_SCHEMA_VERSION = 1;
const MAX_INTENT_LENGTH = 256;
const SESSION_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
const PLANNING_ARTIFACT_TYPES = new Set([
    "slice_overview",
    "phase_overview",
    "milestone_overview",
    "task_definition",
    "implementation_summary",
    "reconciliation"
]);
function selectWorkArtifact(options) {
    const session = normalizeSession(options.session);
    const intent = normalizeIntent(options.intent);
    const artifact = readPlanningArtifactIdentity(options.repositoryRoot, options.artifactPath);
    const statePath = resolveWorkSelectionStatePath(options.repositoryRoot, options.env);
    withSelectionLock(statePath, () => {
        const store = readWorkSelectionStore(statePath, options.repositoryRoot);
        assertMutationPrecondition(store, session, options.ifMatch, {
            command: "work select",
            argv: [
                "day-shift", "work", "select", "--artifact", artifact.artifactPath, "--session", session,
                ...(intent === null ? [] : ["--intent", intent])
            ],
            selectedArtifactPath: artifact.artifactPath
        });
        const currentArtifact = readPlanningArtifactIdentity(options.repositoryRoot, artifact.artifactPath);
        const nextRevision = store.revision + 1;
        store.revision = nextRevision;
        store.sessions[session] = {
            artifactPath: currentArtifact.artifactPath,
            artifactType: currentArtifact.artifactType,
            selectedAt: (options.now ?? new Date()).toISOString(),
            source: "explicit-selection",
            intent,
            contentHash: currentArtifact.contentHash,
            revision: nextRevision
        };
        writeWorkSelectionStore(statePath, store);
    });
    return inspectWorkSelection({ ...options, session });
}
function inspectWorkSelection(options) {
    const session = normalizeSession(options.session);
    const statePath = resolveWorkSelectionStatePath(options.repositoryRoot, options.env);
    const store = readWorkSelectionStore(statePath, options.repositoryRoot);
    const selection = store.sessions[session] ?? null;
    const base = {
        session,
        selection,
        workspaceKey: store.workspaceKey,
        storeRevision: store.revision,
        storageScope: "user-local",
        preconditionToken: createPreconditionToken(store, session)
    };
    if (selection === null) {
        return { ...base, state: "none", staleReason: null };
    }
    const absolutePath = (0, paths_1.resolveRepositoryRelativePath)(options.repositoryRoot, (0, paths_1.parseRepositoryRelativePath)(selection.artifactPath));
    if (!node_fs_1.default.existsSync(absolutePath)) {
        return { ...base, state: "stale", staleReason: "artifact-missing" };
    }
    const rawText = node_fs_1.default.readFileSync(absolutePath, "utf8");
    const artifactType = parseArtifactType(rawText);
    if (artifactType !== selection.artifactType) {
        return { ...base, state: "stale", staleReason: "artifact-identity-changed" };
    }
    if (hashContent(rawText) !== selection.contentHash) {
        return { ...base, state: "stale", staleReason: "artifact-content-changed" };
    }
    return { ...base, state: "fresh", staleReason: null };
}
function clearWorkSelection(options) {
    const session = normalizeSession(options.session);
    const statePath = resolveWorkSelectionStatePath(options.repositoryRoot, options.env);
    return withSelectionLock(statePath, () => {
        const store = readWorkSelectionStore(statePath, options.repositoryRoot);
        if (store.sessions[session] === undefined) {
            if (options.ifMatch !== undefined)
                assertMutationPrecondition(store, session, options.ifMatch, {
                    command: "work clear",
                    argv: ["day-shift", "work", "clear", "--session", session]
                });
            return { changed: false, session, storeRevision: store.revision, workspaceKey: store.workspaceKey, storageScope: "user-local" };
        }
        assertMutationPrecondition(store, session, options.ifMatch, {
            command: "work clear",
            argv: ["day-shift", "work", "clear", "--session", session]
        });
        delete store.sessions[session];
        store.revision += 1;
        writeWorkSelectionStore(statePath, store);
        return { changed: true, session, storeRevision: store.revision, workspaceKey: store.workspaceKey, storageScope: "user-local" };
    });
}
function cleanupStaleWorkSelection(options) {
    const session = normalizeSession(options.session);
    const statePath = resolveWorkSelectionStatePath(options.repositoryRoot, options.env);
    return withSelectionLock(statePath, () => {
        const store = readWorkSelectionStore(statePath, options.repositoryRoot);
        const selection = store.sessions[session];
        if (selection === undefined) {
            if (options.ifMatch !== undefined)
                assertMutationPrecondition(store, session, options.ifMatch, {
                    command: "work cleanup",
                    argv: ["day-shift", "work", "cleanup", "--session", session]
                });
            return { changed: false, reason: "selection-absent", session, storeRevision: store.revision, workspaceKey: store.workspaceKey, storageScope: "user-local" };
        }
        const freshness = inspectRecordFreshness(options.repositoryRoot, selection);
        if (freshness.state === "fresh") {
            return { changed: false, reason: "selection-fresh", session, storeRevision: store.revision, workspaceKey: store.workspaceKey, storageScope: "user-local" };
        }
        assertMutationPrecondition(store, session, options.ifMatch, {
            command: "work cleanup",
            argv: ["day-shift", "work", "cleanup", "--session", session]
        });
        delete store.sessions[session];
        store.revision += 1;
        writeWorkSelectionStore(statePath, store);
        return { changed: true, reason: "stale-cleared", session, storeRevision: store.revision, workspaceKey: store.workspaceKey, storageScope: "user-local" };
    });
}
function resolveWorkSelection(options) {
    const session = normalizeSession(options.session);
    const explicit = options.explicitArtifactPath === undefined
        ? null
        : readPlanningArtifactIdentity(options.repositoryRoot, options.explicitArtifactPath);
    if (explicit !== null) {
        return {
            status: "resolved",
            source: "explicit",
            session,
            artifactPath: explicit.artifactPath,
            artifactType: explicit.artifactType,
            freshness: "fresh",
            staleReason: null,
            preconditionToken: null,
            conflict: null
        };
    }
    const inspection = inspectWorkSelection({ ...options, session });
    if (inspection.selection === null) {
        return {
            status: "none",
            source: "none",
            session,
            artifactPath: null,
            artifactType: null,
            freshness: "none",
            staleReason: null,
            preconditionToken: inspection.preconditionToken,
            conflict: null
        };
    }
    return {
        status: inspection.state === "stale" ? "stale" : "resolved",
        source: "stored",
        session,
        artifactPath: inspection.selection.artifactPath,
        artifactType: inspection.selection.artifactType,
        freshness: inspection.state,
        staleReason: inspection.staleReason,
        preconditionToken: inspection.preconditionToken,
        conflict: null
    };
}
function resolveWorkSelectionStatePath(repositoryRoot, env = process.env) {
    const stateRoot = (0, path_resolution_1.resolveDayShiftUserStateDirectory)({ env }).stateDirectory;
    return node_path_1.default.join(stateRoot, "work-selection", `${workspaceKey(repositoryRoot)}.json`);
}
function readPlanningArtifactIdentity(repositoryRoot, artifactPath) {
    const parsedPath = (0, paths_1.parseRepositoryRelativePath)(artifactPath);
    const absolutePath = (0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, parsedPath);
    if (!node_fs_1.default.existsSync(absolutePath) || !node_fs_1.default.statSync(absolutePath).isFile()) {
        throw (0, errors_1.createUsageError)(`Work selection artifact '${parsedPath.normalizedPath}' does not exist.`, ["Select an existing repository-relative planning artifact."]);
    }
    const rawText = node_fs_1.default.readFileSync(absolutePath, "utf8");
    const artifactType = parseArtifactType(rawText);
    if (artifactType === null || !PLANNING_ARTIFACT_TYPES.has(artifactType)) {
        throw (0, errors_1.createUsageError)(`Work selection artifact '${parsedPath.normalizedPath}' is not a supported planning artifact.`);
    }
    return { artifactPath: parsedPath.normalizedPath, artifactType, contentHash: hashContent(rawText) };
}
function parseArtifactType(rawText) {
    const match = rawText.match(/^artifact_type:\s*["']?([^"'\r\n]+)["']?\s*$/m);
    return match?.[1].trim() ?? null;
}
function normalizeSession(session) {
    const normalized = session?.trim() || exports.DEFAULT_WORK_SELECTION_SESSION;
    if (!SESSION_PATTERN.test(normalized)) {
        throw (0, errors_1.createUsageError)("Work selection session must start with a lowercase letter or digit and contain at most 64 lowercase letters, digits, dots, underscores, or hyphens.");
    }
    return normalized;
}
function normalizeIntent(intent) {
    if (intent === undefined || intent.trim() === "")
        return null;
    const normalized = intent.trim();
    if (normalized.length > MAX_INTENT_LENGTH || /[\u0000-\u001f\u007f]/.test(normalized)) {
        throw (0, errors_1.createUsageError)(`Work selection intent must be at most ${MAX_INTENT_LENGTH} printable characters.`);
    }
    return normalized;
}
function readWorkSelectionStore(statePath, repositoryRoot) {
    const key = workspaceKey(repositoryRoot);
    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)("Work selection state is malformed JSON.", ["Clear or restore the user-local work selection state before retrying."]);
    }
    if (!isWorkSelectionStore(value) || value.workspaceKey !== key) {
        throw (0, errors_1.createInvalidConfigError)("Work selection state has an unsupported or mismatched shape.", ["Clear or restore the user-local work selection state before retrying."]);
    }
    return value;
}
function isWorkSelectionStore(value) {
    if (typeof value !== "object" || value === null)
        return false;
    const candidate = value;
    return candidate.schemaVersion === 1
        && typeof candidate.workspaceKey === "string"
        && Number.isInteger(candidate.revision)
        && candidate.revision >= 0
        && typeof candidate.sessions === "object"
        && candidate.sessions !== null;
}
function writeWorkSelectionStore(statePath, store) {
    node_fs_1.default.mkdirSync(node_path_1.default.dirname(statePath), { recursive: true });
    const temporaryPath = `${statePath}.${process.pid}.tmp`;
    node_fs_1.default.writeFileSync(temporaryPath, `${JSON.stringify(store, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
    node_fs_1.default.renameSync(temporaryPath, statePath);
}
function createPreconditionToken(store, session) {
    const selection = store.sessions[session] ?? null;
    const payload = JSON.stringify({
        v: 1,
        w: store.workspaceKey,
        s: session,
        sr: store.revision,
        rr: selection?.revision ?? null,
        a: selection === null ? null : (0, node_crypto_1.createHash)("sha256").update(selection.artifactPath).digest("hex").slice(0, 16),
        c: selection?.contentHash ?? null
    });
    const encoded = Buffer.from(payload, "utf8").toString("base64url");
    const digest = (0, node_crypto_1.createHash)("sha256").update(payload).digest("base64url").slice(0, 22);
    return `ws1.${encoded}.${digest}`;
}
function assertMutationPrecondition(store, session, ifMatch, retry) {
    const currentToken = createPreconditionToken(store, session);
    const selectionExists = store.sessions[session] !== undefined;
    if (!selectionExists && ifMatch === undefined)
        return;
    if (ifMatch === undefined || !isWellFormedPreconditionToken(ifMatch) || ifMatch !== currentToken) {
        throw new errors_1.RuntimeError({
            code: "WORK_SELECTION_PRECONDITION_FAILED",
            exitCode: errors_1.RuntimeExitCode.USAGE,
            message: "Work selection precondition does not match current session state.",
            details: [
                `Session: ${session}`,
                `Current store revision: ${store.revision}`,
                `Refresh argv: ${JSON.stringify(["day-shift", "work", "current", "--session", session, "--format", "json"])}`,
                "No selection state was changed."
            ],
            recoveryActions: [
                (0, errors_1.createDescriptorBackedRecoveryAction)({
                    command: "work current",
                    argv: ["day-shift", "work", "current", "--session", session, "--format", "json"],
                    explanation: "Refresh the current session evidence and precondition token without changing selection state."
                }),
                (0, errors_1.createDescriptorBackedRecoveryAction)({
                    command: retry.command,
                    argv: [
                        ...retry.argv,
                        "--if-match", currentToken,
                        "--format", "json"
                    ],
                    explanation: "Retry the intended mutation against the current session precondition token.",
                    selectedArtifactPath: retry.selectedArtifactPath
                })
            ]
        });
    }
}
function isWellFormedPreconditionToken(value) {
    const match = value.match(/^ws1\.([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]{22})$/);
    if (match === null)
        return false;
    try {
        const payload = Buffer.from(match[1], "base64url").toString("utf8");
        const expected = (0, node_crypto_1.createHash)("sha256").update(payload).digest("base64url").slice(0, 22);
        const parsed = JSON.parse(payload);
        return expected === match[2] && parsed?.v === 1;
    }
    catch {
        return false;
    }
}
function withSelectionLock(statePath, operation) {
    node_fs_1.default.mkdirSync(node_path_1.default.dirname(statePath), { recursive: true });
    const lockPath = `${statePath}.lock`;
    const deadline = Date.now() + 1000;
    let descriptor = null;
    while (descriptor === null) {
        try {
            descriptor = node_fs_1.default.openSync(lockPath, "wx", 0o600);
            node_fs_1.default.writeFileSync(descriptor, JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }));
        }
        catch (error) {
            if (!isAlreadyExistsError(error))
                throw error;
            if (recoverStaleLock(lockPath))
                continue;
            if (Date.now() >= deadline) {
                throw new errors_1.RuntimeError({
                    code: "WORK_SELECTION_LOCKED",
                    exitCode: errors_1.RuntimeExitCode.USAGE,
                    message: "Work selection state is busy with another mutation.",
                    details: ["Retry after refreshing current selection evidence. No selection state was changed."]
                });
            }
            Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
        }
    }
    try {
        return operation();
    }
    finally {
        node_fs_1.default.closeSync(descriptor);
        try {
            node_fs_1.default.unlinkSync(lockPath);
        }
        catch (error) {
            if (!isMissingPathError(error))
                throw error;
        }
    }
}
function recoverStaleLock(lockPath) {
    try {
        const stats = node_fs_1.default.statSync(lockPath);
        if (Date.now() - stats.mtimeMs <= 120_000)
            return false;
        node_fs_1.default.unlinkSync(lockPath);
        return true;
    }
    catch (error) {
        if (isMissingPathError(error))
            return true;
        return false;
    }
}
function inspectRecordFreshness(repositoryRoot, selection) {
    const absolutePath = (0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, (0, paths_1.parseRepositoryRelativePath)(selection.artifactPath));
    if (!node_fs_1.default.existsSync(absolutePath))
        return { state: "stale", staleReason: "artifact-missing" };
    const rawText = node_fs_1.default.readFileSync(absolutePath, "utf8");
    if (parseArtifactType(rawText) !== selection.artifactType)
        return { state: "stale", staleReason: "artifact-identity-changed" };
    if (hashContent(rawText) !== selection.contentHash)
        return { state: "stale", staleReason: "artifact-content-changed" };
    return { state: "fresh", staleReason: null };
}
function isAlreadyExistsError(error) {
    return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
}
function isMissingPathError(error) {
    return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
}
function workspaceKey(repositoryRoot) {
    const canonicalRoot = node_fs_1.default.realpathSync(repositoryRoot);
    const marker = node_fs_1.default.existsSync(node_path_1.default.join(canonicalRoot, ".day-shift", "config.toml"))
        ? ".day-shift/config.toml"
        : ".git";
    return `ws-${(0, node_crypto_1.createHash)("sha256").update(`${canonicalRoot}\0${marker}`).digest("hex").slice(0, 24)}`;
}
function hashContent(rawText) {
    return `sha256:${(0, node_crypto_1.createHash)("sha256").update(rawText).digest("hex")}`;
}
