"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildPromotionRecordLocation = buildPromotionRecordLocation;
exports.findDuplicateActivePromotionLocationsSync = findDuplicateActivePromotionLocationsSync;
exports.renderPromotionRecord = renderPromotionRecord;
const node_crypto_1 = require("node:crypto");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const planning_document_utils_1 = require("./planning-document-utils");
function buildPromotionRecordLocation(input) {
    const stablePayload = {
        sourceLevel: input.sourceLevel,
        targetLevel: input.targetLevel,
        sourceWorkId: input.sourceWorkId,
        destinationWorkId: input.destinationWorkId,
        destinationSliceId: input.destinationSliceId,
        taskMappings: [...input.taskMappings]
            .map((mapping) => ({
            taskId: mapping.taskId,
            sourceTaskDefinitionPath: mapping.sourceTaskDefinitionPath,
            destinationTaskDefinitionPath: mapping.destinationTaskDefinitionPath,
        }))
            .sort((left, right) => left.taskId.localeCompare(right.taskId) ||
            left.sourceTaskDefinitionPath.localeCompare(right.sourceTaskDefinitionPath)),
    };
    const id = `promotion-${(0, node_crypto_1.createHash)('sha256').update(JSON.stringify(stablePayload)).digest('hex').slice(0, 24)}`;
    return {
        id,
        path: `.day-shift/state/promotion-records/${id}.json`,
        classification: 'canonical-promotion-record',
    };
}
function findDuplicateActivePromotionLocationsSync(options) {
    const selectedSources = new Set(options.taskMappings.map((mapping) => mapping.sourceTaskDefinitionPath));
    const taskIds = new Set(options.taskMappings.map((mapping) => mapping.taskId));
    const planningRoot = node_path_1.default.join(options.repositoryRoot, '.day-shift', 'planning');
    const selectedSourceOverviewPath = options.selectedSourceOverviewPath ?? '';
    const overviews = options.targetLevel === 'structured'
        ? listPlanningFiles(planningRoot, options.repositoryRoot, 'overview.md')
        : listPlanningFiles(planningRoot, options.repositoryRoot, 'slice-overview.md');
    const candidates = listTaskDefinitions(planningRoot, options.repositoryRoot);
    const duplicates = [];
    if (options.targetLevel === 'governed' && options.destinationSliceId !== undefined) {
        const governedRoot = node_path_1.default.join(planningRoot, 'governed');
        const matchingRoot = node_fs_1.default.statSync(governedRoot, { throwIfNoEntry: false })?.isDirectory()
            ? node_fs_1.default
                .readdirSync(governedRoot, { withFileTypes: true })
                .find((entry) => entry.isDirectory() && !entry.isSymbolicLink() && entry.name === options.destinationSliceId)
            : undefined;
        if (matchingRoot !== undefined) {
            duplicates.push({
                kind: 'slice-identity',
                workId: null,
                sliceId: options.destinationSliceId,
                taskId: null,
                path: `.day-shift/planning/governed/${options.destinationSliceId}`,
            });
        }
    }
    for (const candidate of overviews) {
        if (candidate === selectedSourceOverviewPath ||
            (options.targetLevel === 'structured'
                ? !/^\.day-shift\/planning\/structured\/[^/]+\/overview\.md$/.test(candidate)
                : !/^\.day-shift\/planning\/governed\/[^/]+\/slice-overview\.md$/.test(candidate))) {
            continue;
        }
        let text;
        try {
            text = node_fs_1.default.readFileSync(node_path_1.default.join(options.repositoryRoot, ...candidate.split('/')), 'utf8');
        }
        catch {
            continue;
        }
        const document = (0, planning_document_utils_1.parseMarkdownFrontMatter)(text);
        const duplicateWork = options.targetLevel === 'structured' &&
            document.hasFrontMatter &&
            (0, planning_document_utils_1.readTopLevelField)(document.frontMatterLines, 'id') === options.destinationWorkId;
        const duplicateSlice = options.targetLevel === 'governed' &&
            document.hasFrontMatter &&
            (0, planning_document_utils_1.readTopLevelField)(document.frontMatterLines, 'slice') === options.destinationSliceId;
        if (duplicateWork || duplicateSlice) {
            duplicates.push({
                kind: duplicateWork ? 'work-identity' : 'slice-identity',
                workId: duplicateWork ? (options.destinationWorkId ?? null) : null,
                sliceId: duplicateSlice ? (options.destinationSliceId ?? null) : null,
                taskId: null,
                path: candidate,
            });
        }
    }
    for (const candidate of candidates) {
        if (selectedSources.has(candidate))
            continue;
        const absolute = node_path_1.default.join(options.repositoryRoot, ...candidate.split('/'));
        let text;
        try {
            text = node_fs_1.default.readFileSync(absolute, 'utf8');
        }
        catch {
            continue;
        }
        const document = (0, planning_document_utils_1.parseMarkdownFrontMatter)(text);
        if (!document.hasFrontMatter)
            continue;
        const taskId = (0, planning_document_utils_1.readTopLevelField)(document.frontMatterLines, 'task') ?? (0, planning_document_utils_1.readTopLevelField)(document.frontMatterLines, 'id');
        if (taskId === undefined || !taskIds.has(taskId))
            continue;
        const workId = (0, planning_document_utils_1.readTopLevelField)(document.frontMatterLines, 'work_id') ?? inferStructuredWorkId(candidate);
        const sliceId = (0, planning_document_utils_1.readTopLevelField)(document.frontMatterLines, 'slice') ?? inferGovernedSliceId(candidate);
        const sameDestination = options.targetLevel === 'structured'
            ? workId === options.destinationWorkId
            : sliceId === options.destinationSliceId;
        if (!sameDestination)
            continue;
        duplicates.push({ kind: 'task-location', workId, sliceId, taskId, path: candidate });
    }
    return duplicates.sort((left, right) => left.kind.localeCompare(right.kind) ||
        (left.taskId ?? '').localeCompare(right.taskId ?? '') ||
        left.path.localeCompare(right.path));
}
function renderPromotionRecord(options) {
    const actor = sanitizedAuditText(options.actor, 'Promotion actor', 128);
    const reason = sanitizedAuditText(options.reason, 'Promotion reason', 512);
    const record = {
        schemaVersion: 1,
        recordType: 'promotion-remapping',
        classification: 'canonical',
        promotionRecordId: options.plan.promotionRecord.id,
        promotionRecordPath: options.plan.promotionRecord.path,
        planId: options.plan.planId,
        transactionId: options.transactionId,
        transactionState: 'authorized-not-terminal',
        terminalOutcomeSource: 'repository-transaction-result-and-canonical-postconditions',
        sourceLevel: options.plan.sourceLevel,
        targetLevel: options.plan.targetLevel,
        sourceWorkId: options.plan.sourceWorkId,
        destinationWorkId: options.plan.destinationWorkId,
        destinationImplementationOrder: options.plan.destinationImplementationOrder,
        destinationImplementationOrderSource: options.plan.destinationImplementationOrderSource,
        rootInventoryFingerprint: options.plan.rootInventoryFingerprint,
        destinationSliceId: options.plan.destinationSliceId,
        sourceRootPath: options.plan.sourceRootPath,
        destinationRootPath: options.plan.destinationRootPath,
        taskMappings: options.plan.taskMappings.map((mapping) => ({
            taskId: mapping.taskId,
            sourceTaskDefinitionPath: mapping.sourceTaskDefinitionPath,
            sourceImplementationSummaryPath: mapping.sourceImplementationSummaryPath,
            destinationMilestoneSlug: mapping.destinationMilestoneSlug,
            destinationSequence: mapping.destinationSequence,
            destinationSequencePrefix: mapping.destinationSequencePrefix,
            destinationTaskDefinitionPath: mapping.destinationTaskDefinitionPath,
            destinationImplementationSummaryPath: mapping.destinationImplementationSummaryPath,
        })),
        requiredParentPaths: options.plan.requiredParents.map((parent) => parent.path),
        specificationReferences: options.plan.specifications.map((specification) => ({
            specId: specification.specId,
            specPath: specification.specPath,
            revision: specification.revision,
            readiness: specification.readiness,
        })),
        actor,
        reason,
        appliedAt: options.appliedAt,
    };
    return `${JSON.stringify(record, null, 2)}\n`;
}
function listTaskDefinitions(planningRoot, repositoryRoot) {
    return listPlanningFiles(planningRoot, repositoryRoot, 'task-definition.md');
}
function listPlanningFiles(planningRoot, repositoryRoot, fileName) {
    if (!node_fs_1.default.statSync(planningRoot, { throwIfNoEntry: false })?.isDirectory())
        return [];
    const paths = [];
    const visit = (directory) => {
        for (const entry of node_fs_1.default.readdirSync(directory, { withFileTypes: true })) {
            if (entry.isSymbolicLink())
                continue;
            const absolute = node_path_1.default.join(directory, entry.name);
            if (entry.isDirectory()) {
                visit(absolute);
            }
            else if (entry.isFile() && entry.name === fileName) {
                paths.push(node_path_1.default.relative(repositoryRoot, absolute).split(node_path_1.default.sep).join('/'));
            }
        }
    };
    visit(planningRoot);
    return paths.sort();
}
function inferStructuredWorkId(taskDefinitionPath) {
    return taskDefinitionPath.match(/^\.day-shift\/planning\/structured\/([^/]+)\//)?.[1] ?? null;
}
function inferGovernedSliceId(taskDefinitionPath) {
    return taskDefinitionPath.match(/^\.day-shift\/planning\/governed\/([^/]+)\//)?.[1] ?? null;
}
function sanitizedAuditText(value, label, maximumLength) {
    if (typeof value !== 'string' || value.trim() === '') {
        throw new TypeError(`${label} must be non-empty.`);
    }
    const normalized = value.trim();
    if (normalized.length > maximumLength ||
        containsControlCharacters(normalized) ||
        /(?:^|\s)(?:\/[^\s]*|[A-Za-z]:[\\/][^\s]*)/.test(normalized)) {
        throw new TypeError(`${label} must be bounded single-line audit text without absolute filesystem paths.`);
    }
    return normalized;
}
function containsControlCharacters(value) {
    for (const character of value) {
        const code = character.charCodeAt(0);
        if (code <= 0x1f || code === 0x7f) {
            return true;
        }
    }
    return false;
}
