"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.readFrontMatterScalar = readFrontMatterScalar;
exports.currentRevisionMatches = currentRevisionMatches;
exports.asRecord = asRecord;
exports.requireRunDirectory = requireRunDirectory;
exports.assertEvidenceDirectorySafe = assertEvidenceDirectorySafe;
exports.readBoundedRepositoryFile = readBoundedRepositoryFile;
exports.compareCurrentFile = compareCurrentFile;
exports.writeEvidenceAndManifest = writeEvidenceAndManifest;
exports.writeJsonAtomic = writeJsonAtomic;
exports.jsonBytes = jsonBytes;
exports.serializeEvidenceEnvelope = serializeEvidenceEnvelope;
exports.removeTemporary = removeTemporary;
exports.readBoundedFile = readBoundedFile;
exports.parseObjectJson = parseObjectJson;
exports.collectReceiptIds = collectReceiptIds;
exports.normalizeReceiptIds = normalizeReceiptIds;
exports.optionalIdentity = optionalIdentity;
exports.requireIdentity = requireIdentity;
exports.requireToken = requireToken;
exports.requireBoundedText = requireBoundedText;
exports.requireTimestamp = requireTimestamp;
exports.slug = slug;
exports.canonicalRoot = canonicalRoot;
exports.sha256 = sha256;
// Lifecycle run-context contracts, capture, inspection, and persistence orchestration.
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 task_lifecycle_timestamps_1 = require("../planning/task-lifecycle-timestamps");
const output_safety_1 = require("../output/output-safety");
const paths_1 = require("../paths");
const context_capture_1 = require("./context-capture");
function hasUnsafeControlCharacters(value) {
    for (let index = 0; index < value.length; index += 1) {
        const codePoint = value.charCodeAt(index);
        if ((codePoint >= 0 && codePoint <= 8) ||
            codePoint === 11 ||
            codePoint === 12 ||
            (codePoint >= 14 && codePoint <= 31) ||
            codePoint === 127) {
            return true;
        }
    }
    return false;
}
function readFrontMatterScalar(rawText, key) {
    const frontMatter = rawText.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1] ?? '';
    const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    const value = frontMatter.match(new RegExp(`^${escapedKey}:\\s*["']?([^"'\\r\\n]+)["']?\\s*$`, 'm'))?.[1].trim();
    return value === undefined || value.length === 0 ? null : value;
}
function currentRevisionMatches(repositoryRoot, repositoryPath, expectedRevision) {
    try {
        return (readBoundedRepositoryFile(repositoryRoot, repositoryPath, 'Phase review descendant').hash === expectedRevision);
    }
    catch {
        return false;
    }
}
function asRecord(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value)
        ? value
        : null;
}
function requireRunDirectory(repositoryRoot, value) {
    let relativePath;
    try {
        relativePath = (0, paths_1.parseRepositoryRelativePath)(value).normalizedPath;
    }
    catch {
        throw (0, errors_1.createUsageError)("Lifecycle run directory must be one canonical '.day-shift/runs/run-<run-id>' repository-relative directory (legacy 'sandbox/run-<run-id>' directories remain readable).");
    }
    if (!context_capture_1.RUN_DIRECTORY_PATTERN.test(relativePath)) {
        throw (0, errors_1.createUsageError)("Lifecycle run directory must be one canonical '.day-shift/runs/run-<run-id>' repository-relative directory (legacy 'sandbox/run-<run-id>' directories remain readable).");
    }
    const absolutePath = (0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, relativePath);
    if (!node_fs_1.default.existsSync(absolutePath) || !node_fs_1.default.statSync(absolutePath).isDirectory()) {
        throw (0, errors_1.createUsageError)(`Lifecycle run directory '${relativePath}' must already exist.`);
    }
    if (node_fs_1.default.lstatSync(absolutePath).isSymbolicLink()) {
        throw (0, errors_1.createUsageError)('Lifecycle run directories must not be symbolic links.');
    }
    return { relativePath, absolutePath };
}
function assertEvidenceDirectorySafe(repositoryRoot, runDirectory) {
    const evidenceDirectory = (0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, (0, paths_1.parseRepositoryRelativePath)(node_path_1.default.posix.join(runDirectory, 'evidence')));
    if (!node_fs_1.default.existsSync(evidenceDirectory))
        return;
    if (!node_fs_1.default.statSync(evidenceDirectory).isDirectory() || node_fs_1.default.lstatSync(evidenceDirectory).isSymbolicLink()) {
        throw (0, errors_1.createUsageError)('Lifecycle evidence directory must be a regular repository-local directory.');
    }
}
function readBoundedRepositoryFile(repositoryRoot, value, label, maximumBytes = context_capture_1.MAX_LIFECYCLE_RUN_RESULT_BYTES) {
    const relativePath = (0, paths_1.parseRepositoryRelativePath)(value).normalizedPath;
    const absolutePath = (0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, relativePath);
    if (!node_fs_1.default.existsSync(absolutePath) || !node_fs_1.default.statSync(absolutePath).isFile()) {
        throw (0, errors_1.createUsageError)(`${label} '${relativePath}' must be an existing regular file.`);
    }
    if (node_fs_1.default.lstatSync(absolutePath).isSymbolicLink()) {
        throw (0, errors_1.createUsageError)(`${label} must not be a symbolic link.`);
    }
    const bytes = readBoundedFile(absolutePath, maximumBytes, label);
    return { relativePath, absolutePath, hash: sha256(bytes) };
}
function compareCurrentFile(repositoryRoot, relativePath, expectedHash, label, issues) {
    try {
        const current = readBoundedRepositoryFile(repositoryRoot, relativePath, label);
        if (current.hash !== expectedHash)
            issues.push(`${label}-changed:${relativePath}`);
    }
    catch {
        issues.push(`${label}-missing-or-invalid:${relativePath}`);
    }
}
function writeEvidenceAndManifest(evidenceAbsolute, evidenceBytes, manifestAbsolute, manifest) {
    const evidenceDirectory = node_path_1.default.dirname(evidenceAbsolute);
    if (node_fs_1.default.existsSync(evidenceDirectory) &&
        (!node_fs_1.default.statSync(evidenceDirectory).isDirectory() || node_fs_1.default.lstatSync(evidenceDirectory).isSymbolicLink())) {
        throw (0, errors_1.createUsageError)('Lifecycle evidence directory must be a regular repository-local directory.');
    }
    node_fs_1.default.mkdirSync(evidenceDirectory, { recursive: true });
    const evidenceTemporary = `${evidenceAbsolute}.tmp-${process.pid}`;
    const manifestTemporary = `${manifestAbsolute}.tmp-${process.pid}`;
    const manifestBytes = jsonBytes(manifest, context_capture_1.MAX_LIFECYCLE_RUN_MANIFEST_BYTES);
    let objectWritten = false;
    try {
        if (node_fs_1.default.existsSync(evidenceAbsolute)) {
            const stat = node_fs_1.default.lstatSync(evidenceAbsolute);
            if (stat.isSymbolicLink() ||
                !stat.isFile() ||
                stat.size !== evidenceBytes.byteLength ||
                !node_fs_1.default.readFileSync(evidenceAbsolute).equals(evidenceBytes)) {
                throw (0, errors_1.createUsageError)('Existing lifecycle evidence object does not match its content identity.');
            }
        }
        else {
            node_fs_1.default.writeFileSync(evidenceTemporary, evidenceBytes, { flag: 'wx', mode: 0o600 });
            node_fs_1.default.renameSync(evidenceTemporary, evidenceAbsolute);
            objectWritten = true;
        }
        node_fs_1.default.writeFileSync(manifestTemporary, manifestBytes, { flag: 'wx', mode: 0o600 });
        try {
            node_fs_1.default.renameSync(manifestTemporary, manifestAbsolute);
        }
        catch (error) {
            if (objectWritten)
                node_fs_1.default.unlinkSync(evidenceAbsolute);
            throw error;
        }
    }
    finally {
        removeTemporary(evidenceTemporary);
        removeTemporary(manifestTemporary);
    }
    return objectWritten;
}
function writeJsonAtomic(absolutePath, value, maximumBytes) {
    const bytes = jsonBytes(value, maximumBytes);
    const temporary = `${absolutePath}.tmp-${process.pid}`;
    try {
        node_fs_1.default.writeFileSync(temporary, bytes, { flag: 'wx', mode: 0o600 });
        node_fs_1.default.renameSync(temporary, absolutePath);
    }
    finally {
        removeTemporary(temporary);
    }
}
function jsonBytes(value, maximumBytes) {
    const bytes = Buffer.from(`${JSON.stringify(value, null, 2)}\n`, 'utf8');
    if (bytes.byteLength > maximumBytes) {
        throw (0, errors_1.createUsageError)(`Lifecycle run JSON exceeds the ${maximumBytes}-byte limit.`);
    }
    return bytes;
}
function serializeEvidenceEnvelope(envelope, repositoryRoot) {
    const sanitized = (0, output_safety_1.sanitizeOutputValue)(envelope, { repositoryRoot });
    const bytes = Buffer.from(`${JSON.stringify(sanitized, null, 2)}\n`, 'utf8');
    if (bytes.byteLength === 0 || bytes.byteLength > context_capture_1.MAX_LIFECYCLE_RUN_RESULT_BYTES) {
        throw (0, errors_1.createUsageError)(`Lifecycle structured evidence must contain 1-${context_capture_1.MAX_LIFECYCLE_RUN_RESULT_BYTES} bytes.`);
    }
    return bytes;
}
function removeTemporary(candidate) {
    try {
        if (node_fs_1.default.existsSync(candidate))
            node_fs_1.default.unlinkSync(candidate);
    }
    catch {
        // The primary operation reports the authoritative failure.
    }
}
function readBoundedFile(absolutePath, maximumBytes, label) {
    const stat = node_fs_1.default.statSync(absolutePath);
    if (stat.size <= 0)
        throw (0, errors_1.createUsageError)(`${label} must not be empty.`);
    if (stat.size > maximumBytes) {
        throw (0, errors_1.createUsageError)(`${label} exceeds the ${maximumBytes}-byte limit.`);
    }
    return node_fs_1.default.readFileSync(absolutePath);
}
function parseObjectJson(bytes, label) {
    let parsed;
    try {
        parsed = JSON.parse(bytes.toString('utf8'));
    }
    catch {
        throw (0, errors_1.createUsageError)(`${label} must contain one valid UTF-8 JSON object.`);
    }
    if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
        throw (0, errors_1.createUsageError)(`${label} must contain one JSON object.`);
    }
    return parsed;
}
function collectReceiptIds(value) {
    const found = [];
    const visit = (candidate, depth) => {
        if (depth > 8 || found.length >= context_capture_1.MAX_RECEIPT_IDS)
            return;
        if (Array.isArray(candidate)) {
            for (const item of candidate.slice(0, 256))
                visit(item, depth + 1);
            return;
        }
        if (typeof candidate !== 'object' || candidate === null)
            return;
        for (const [key, item] of Object.entries(candidate)) {
            if (/(?:receipt|review|readiness|validation|transaction)Id$/i.test(key) &&
                typeof item === 'string' &&
                context_capture_1.TOKEN_PATTERN.test(item)) {
                found.push(item);
            }
            else {
                visit(item, depth + 1);
            }
        }
    };
    visit(value, 0);
    return found;
}
function normalizeReceiptIds(values) {
    const result = [...new Set(values.map((value) => requireIdentity(value, 'Receipt identity')))].sort();
    if (result.length > context_capture_1.MAX_RECEIPT_IDS) {
        throw (0, errors_1.createUsageError)(`Lifecycle entries may contain at most ${context_capture_1.MAX_RECEIPT_IDS} receipt identities.`);
    }
    return result;
}
function optionalIdentity(value, label) {
    if (value == null || value.trim() === '')
        return null;
    return requireIdentity(value, label);
}
function requireIdentity(value, label) {
    const normalized = value.trim();
    if (!context_capture_1.TOKEN_PATTERN.test(normalized)) {
        throw (0, errors_1.createUsageError)(`${label} must use 2-128 machine-safe identity characters.`);
    }
    return normalized;
}
function requireToken(value, label) {
    return requireIdentity(value, label);
}
function requireBoundedText(value, label) {
    const normalized = value?.trim() ?? '';
    if (normalized === '' || normalized.length > 512 || hasUnsafeControlCharacters(normalized)) {
        throw (0, errors_1.createUsageError)(`${label} must be non-empty, bounded, and free of control characters.`);
    }
    return normalized;
}
function requireTimestamp(value, label) {
    const normalized = value.trim();
    if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/.test(normalized) ||
        !Number.isFinite(Date.parse(normalized))) {
        throw (0, errors_1.createUsageError)(`${label} must be an ISO-8601 timestamp with an explicit timezone.`);
    }
    return (0, task_lifecycle_timestamps_1.requireObservedLifecycleTimestamp)({
        value: normalized,
        field: label === 'Run opening time' ? 'opened_at' : 'recorded_at',
        label,
    });
}
function slug(value) {
    const normalized = value
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-|-$/g, '');
    return (normalized || 'result').slice(0, 64);
}
function canonicalRoot(value) {
    return node_fs_1.default.realpathSync(node_path_1.default.resolve(value));
}
function sha256(value) {
    return `sha256:${(0, node_crypto_1.createHash)('sha256').update(value).digest('hex')}`;
}
