"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createLinkMaterializeCommand = createLinkMaterializeCommand;
const node_fs_1 = __importDefault(require("node:fs"));
const errors_1 = require("../../../lib/errors");
const markdown_link_materialization_1 = require("../../../lib/planning/markdown-link-materialization");
const output_1 = require("../../../lib/output");
const paths_1 = require("../../../lib/paths");
const cli_constants_1 = require("../../../lib/config/cli-constants");
const validation_links_check_1 = require("../validation-links-check");
const global_format_1 = require("../global-format");
async function createLinkMaterializeCommand() {
    const { Command } = await import('commander');
    const command = new Command('materialize');
    command
        .description('Preview or materialize validated local Markdown body references as clickable links.')
        .option('--scope <scope>', "Materialization scope. Currently only 'planning' is supported.", 'planning')
        .option('--artifact <path>', 'Repository-relative Markdown artifact file or directory path. Required for write execution.')
        .option('--dry-run', 'Preview candidates without writing files. Repository-wide execution is dry-run only.')
        .option('--write', 'Apply materialization changes to selected artifact file(s).')
        .option('--validation-evidence-id <id>', 'Exact evidence ID returned by the matching link materialize dry-run. Required when a write has candidates.')
        .option('--format <format>', 'Output format. Accepted values: text, json.')
        .addHelpText('after', [
        '',
        'Boundaries:',
        '  Evidence source: the dry-run returns revision-bound validation counts, findings, candidates, and one stable evidence ID from the same inspection; validation remains read-only and writeCapable: false.',
        '  Dry-run: previews repository-wide, selected-artifact, or selected-directory candidates without writing files. A zero-candidate preview with every required link relationship resolved is reusable as the lifecycle link gate.',
        '  Write execution: requires --artifact and --validation-evidence-id for candidate writes, fails closed when repository, artifact, scope, revision, or finding state differs, and mutates only selected Markdown artifact files.',
        '  Post-write: returns revision-bound validation evidence for the written state or an exact required recheck when post-write proof cannot be produced.',
        '  Skips malformed, missing, outside-boundary, unsupported external, already-linked, inline-code, fenced-code, duplicate, ambiguous, or otherwise unsafe candidates.',
        '  Rollback: revert the reported changed artifact paths from version control.',
    ].join('\n'))
        .action((_, commandInstance) => {
        const options = commandInstance.opts();
        const requestedFormat = options.format ?? (0, global_format_1.getGlobalFormat)(commandInstance);
        const format = (0, output_1.resolveOutputFormat)(requestedFormat);
        if (format === null) {
            throw (0, errors_1.createUnsupportedOutputFormatError)({
                received: requestedFormat,
                commandBoundary: 'link materialize command output format',
            });
        }
        if (options.scope !== 'planning') {
            throw (0, errors_1.createUsageError)("Link materialize currently supports only the 'planning' scope.", [
                `Received: ${String(options.scope)}`,
            ]);
        }
        const artifactPath = normalizeOptionalArtifactPath(options.artifact);
        const validationEvidenceId = normalizeOptionalValidationEvidenceId(options.validationEvidenceId);
        const write = options.write === true;
        const dryRun = !write || options.dryRun === true;
        if (write && options.dryRun === true) {
            throw (0, errors_1.createUsageError)('Use either --dry-run or --write for link materialize, not both.', [
                'Dry-run is the default when --write is omitted.',
            ]);
        }
        if (write && artifactPath === null) {
            throw (0, errors_1.createUsageError)('Link materialize write execution requires --artifact.', [
                'Repository-wide materialization is preview-only; use --dry-run or select one artifact with --artifact.',
            ]);
        }
        const repositoryRoot = getRootCommand(commandInstance).configRepositoryContext?.root ?? process.cwd();
        const validation = (0, validation_links_check_1.runValidationLinksCheckReport)({
            repositoryRoot,
            artifactPath,
        });
        const report = buildMaterializeReport({
            repositoryRoot,
            artifactPath,
            dryRun,
            write,
            validation,
        });
        const { writePlans, linkGateEvidence: _linkGateEvidence, ...outputReport } = report;
        let emittedFindings = validation.findings;
        if (write && outputReport.changedArtifactPaths.length > 0) {
            assertRevisionBoundWriteEvidence({
                artifactPath,
                providedEvidenceId: validationEvidenceId,
                currentEvidence: report.linkGateEvidence,
            });
            assertWritePlansDoNotChangeAuthorizedTasks(repositoryRoot, writePlans);
            for (const writePlan of writePlans) {
                node_fs_1.default.writeFileSync((0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, writePlan.path), writePlan.markdownText, 'utf8');
            }
            try {
                const postWriteValidation = (0, validation_links_check_1.runValidationLinksCheckReport)({
                    repositoryRoot,
                    artifactPath,
                });
                const postWriteReport = buildMaterializeReport({
                    repositoryRoot,
                    artifactPath,
                    dryRun: true,
                    write: false,
                    validation: postWriteValidation,
                });
                outputReport.validationEvidence.postWriteValidationEvidence = postWriteReport.linkGateEvidence;
                emittedFindings = postWriteValidation.findings;
            }
            catch (error) {
                outputReport.validationEvidence.requiredRecheck = createRequiredLinkMaterializeRecheck({
                    artifactPath,
                    reason: error instanceof Error
                        ? `Post-write validation evidence could not be produced: ${error.message}`
                        : 'Post-write validation evidence could not be produced.',
                });
            }
        }
        if (emittedFindings.some((finding) => finding.severity === 'error')) {
            getRootCommand(commandInstance).runtimeExitCode = errors_1.RuntimeExitCode.ERROR;
        }
        (0, output_1.emitSuccess)({
            command: 'link materialize',
            format,
            text: renderLinkMaterializeReport(outputReport),
            data: outputReport,
            findings: emittedFindings,
        });
    });
    return command;
}
function assertRevisionBoundWriteEvidence(options) {
    if (options.providedEvidenceId === options.currentEvidence.evidenceId) {
        return;
    }
    const requiredRecheck = createRequiredLinkMaterializeRecheck({
        artifactPath: options.artifactPath,
        reason: options.providedEvidenceId === null
            ? 'Candidate writes require the exact evidence ID returned by a matching dry-run.'
            : 'The supplied evidence ID does not match the current repository, artifact, scope, revision, candidate, and finding state.',
    });
    const recoveryCommand = requiredRecheck.argv.join(' ');
    throw new errors_1.RuntimeError({
        message: 'Link materialization candidate writes require current revision-bound preview evidence.',
        code: 'LINK_MATERIALIZE_STALE_VALIDATION_EVIDENCE',
        exitCode: errors_1.RuntimeExitCode.STALE_EVIDENCE,
        details: [
            requiredRecheck.reason,
            'No selected artifact was written.',
            `Refresh the exact evidence with: ${recoveryCommand}`,
        ],
        findings: [
            {
                code: 'LINK_MATERIALIZE_STALE_VALIDATION_EVIDENCE',
                severity: 'error',
                message: requiredRecheck.reason,
                ...(options.artifactPath === null ? {} : { path: options.artifactPath }),
                recovery: recoveryCommand,
                recommendedCommand: recoveryCommand,
                noWriteBoundary: 'Evidence identity is checked against a fresh same-scope inspection before the first materialization write.',
            },
        ],
        recoveryActions: [
            (0, errors_1.createDescriptorBackedRecoveryAction)({
                command: 'link materialize',
                argv: requiredRecheck.argv,
                explanation: 'Refresh revision-bound link-gate evidence for the exact selected scope before retrying a candidate write.',
                ...(options.artifactPath === null ? {} : { selectedArtifactPath: options.artifactPath }),
            }),
        ],
    });
}
function createRequiredLinkMaterializeRecheck(options) {
    return {
        command: 'link materialize',
        argv: [
            'day-shift',
            'link',
            'materialize',
            '--dry-run',
            ...(options.artifactPath === null ? [] : ['--artifact', options.artifactPath]),
            '--format',
            'json',
        ],
        reason: options.reason,
    };
}
function assertWritePlansDoNotChangeAuthorizedTasks(repositoryRoot, writePlans) {
    const protectedTask = writePlans.find((writePlan) => {
        if (!writePlan.path.endsWith('/task-definition.md'))
            return false;
        const currentText = node_fs_1.default.readFileSync((0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, writePlan.path), 'utf8');
        return (/^readiness:\s*"?ready_to_implement"?\s*$/mu.test(currentText) &&
            /^readiness_review_id:\s*"?task-readiness-review-[a-f0-9]+"?\s*$/mu.test(currentText));
    });
    if (protectedTask === undefined)
        return;
    const reviewedAt = new Date().toISOString();
    const argv = [
        'day-shift',
        'task',
        'readiness-review',
        '--task-definition',
        protectedTask.path,
        '--reviewed-by',
        'operator',
        '--reviewed-at',
        reviewedAt,
        '--format',
        'json',
    ];
    const recoveryCommand = argv.join(' ');
    throw new errors_1.RuntimeError({
        message: 'Link materialization cannot change an authorized task revision.',
        code: 'LINK_MATERIALIZE_AUTHORIZED_TASK_REVISION_GUARD',
        exitCode: errors_1.RuntimeExitCode.STALE_EVIDENCE,
        details: [
            `Authorized task '${protectedTask.path}' requires a lifecycle-supported reopen or fresh review path before any content-changing link rewrite.`,
            'No selected artifact, task authorization field, implementation summary, or parent artifact was written.',
            `Inspect fresh readiness evidence with: ${recoveryCommand}`,
        ],
        findings: [
            {
                code: 'LINK_MATERIALIZE_AUTHORIZED_TASK_REVISION_GUARD',
                severity: 'error',
                message: 'The selected write would stale revision-bound task readiness authorization.',
                path: protectedTask.path,
                recovery: recoveryCommand,
                recommendedCommand: recoveryCommand,
                noWriteBoundary: 'Authorized task protection is evaluated for every planned artifact before the first materialization write.',
            },
        ],
        recoveryActions: [
            (0, errors_1.createDescriptorBackedRecoveryAction)({
                command: 'task readiness-review',
                argv,
                explanation: 'Inspect the authorized task through the lifecycle-supported review path before deciding how to reopen it.',
                selectedArtifactPath: protectedTask.path,
            }),
        ],
    });
}
function buildMaterializeReport(options) {
    const resolvedCandidates = collectResolvedCandidates(options.validation);
    const materialization = runMaterialization({
        repositoryRoot: options.repositoryRoot,
        candidates: resolvedCandidates,
    });
    const candidates = materialization.replacements;
    const skippedCandidates = [];
    for (const evidence of options.validation.resolution.valueResolutions) {
        if (evidence.valueShape !== 'body-reference-list') {
            continue;
        }
        if (evidence.resolution.status === 'resolved-local-target') {
            continue;
        }
        skippedCandidates.push({
            sourceArtifactPath: evidence.sourceArtifactPath,
            field: evidence.field,
            originalValue: evidence.originalValue,
            reason: normalizeSkipReason(evidence.resolution.status),
            message: evidence.resolution.message,
        });
    }
    skippedCandidates.push(...materialization.skippedCandidates.map((skipped) => ({
        sourceArtifactPath: skipped.sourceArtifactPath,
        field: skipped.field,
        originalValue: skipped.value,
        reason: skipped.reason,
        message: skipped.message,
    })));
    const previewChangedArtifactPaths = materialization.changedArtifactPaths;
    const previewChangedPaths = previewChangedArtifactPaths;
    const changedArtifactPaths = options.write ? materialization.changedArtifactPaths : [];
    const changedPaths = changedArtifactPaths;
    const artifactInputKind = classifyArtifactInputKind(options.repositoryRoot, options.artifactPath);
    const linkGateEvidence = (0, markdown_link_materialization_1.createRevisionBoundLinkGateEvidence)({
        repositoryRoot: options.repositoryRoot,
        artifactPath: options.artifactPath,
        artifactInputKind,
        discovery: options.validation.discovery,
        resolution: options.validation.resolution,
        findings: options.validation.findings,
        candidateCount: candidates.length,
        previewChangedPaths,
    });
    return {
        repositoryRoot: options.repositoryRoot,
        scope: 'planning',
        artifact: options.artifactPath,
        artifactInputKind,
        readOnly: options.dryRun,
        writeCapable: true,
        dryRun: options.dryRun,
        localOnly: true,
        candidateCount: candidates.length,
        previewChangedPaths,
        previewChangedArtifactPaths,
        changedPaths,
        changedArtifactPaths,
        skippedCandidateCount: skippedCandidates.length,
        skippedCandidates,
        candidates,
        rollbackGuidance: {
            summary: options.dryRun
                ? 'Dry-run only; no rollback is required because no files were written.'
                : changedArtifactPaths.length === 0
                    ? 'No files were changed; no rollback is required.'
                    : 'To rollback, revert the reported changed artifact paths from version control.',
            changedArtifactPaths,
        },
        validationEvidence: {
            ...linkGateEvidence,
            command: 'validation links-check',
            readOnly: true,
            writeCapable: false,
            postWriteValidationEvidence: null,
            requiredRecheck: null,
        },
        writePlans: options.write ? materialization.writePlans : [],
        linkGateEvidence,
    };
}
function collectResolvedCandidates(validation) {
    return (0, markdown_link_materialization_1.collectResolvedMarkdownLinkMaterializationCandidates)(validation.discovery, validation.resolution);
}
function runMaterialization(options) {
    const candidatesByArtifact = new Map();
    for (const candidate of options.candidates) {
        const existing = candidatesByArtifact.get(candidate.sourceArtifactPath) ?? [];
        existing.push(candidate);
        candidatesByArtifact.set(candidate.sourceArtifactPath, existing);
    }
    const replacements = [];
    const skippedCandidates = [];
    const changedArtifactPaths = [];
    const writePlans = [];
    for (const [artifactPath, candidates] of candidatesByArtifact) {
        const absolutePath = (0, paths_1.resolveRepositoryRelativePath)(options.repositoryRoot, artifactPath);
        const markdownText = node_fs_1.default.readFileSync(absolutePath, 'utf8');
        const result = (0, markdown_link_materialization_1.materializeMarkdownLinks)({ markdownText, candidates });
        replacements.push(...result.replacements.map((replacement) => ({
            sourceArtifactPath: replacement.sourceArtifactPath,
            field: replacement.field,
            originalValue: replacement.value,
            normalizedPath: replacement.normalizedPath,
            status: 'candidate',
        })));
        skippedCandidates.push(...result.skippedCandidates);
        if (result.changed) {
            changedArtifactPaths.push(artifactPath);
            writePlans.push({
                path: artifactPath,
                markdownText: result.markdownText,
            });
        }
    }
    return {
        replacements,
        skippedCandidates,
        changedArtifactPaths,
        writePlans,
    };
}
function renderLinkMaterializeReport(report) {
    return [
        'Markdown link materialization',
        '',
        `Scope: ${report.scope}`,
        `Artifact: ${report.artifact ?? '(repository preview)'}`,
        `Artifact input: ${report.artifactInputKind}`,
        `Dry-run: ${report.dryRun ? 'yes' : 'no'}`,
        `Write-capable: ${report.writeCapable ? 'yes' : 'no'}`,
        `Candidate count: ${report.candidateCount}`,
        `Skipped candidate count: ${report.skippedCandidateCount}`,
        `Preview changed paths: ${report.previewChangedPaths.length === 0 ? 'none' : report.previewChangedPaths.join(', ')}`,
        `Changed paths: ${report.changedPaths.length === 0 ? 'none' : report.changedPaths.join(', ')}`,
        `Validation evidence ID: ${report.validationEvidence.evidenceId}`,
        `Selected revision: ${report.validationEvidence.selectedRevisionEvidence.revision ?? 'unobserved'}`,
        `Finding state: ${report.validationEvidence.findingState}`,
        `Reusable lifecycle link gate: ${report.validationEvidence.reusableAsLifecycleLinkGate ? 'yes' : 'no'}`,
        `Post-write evidence: ${report.validationEvidence.postWriteValidationEvidence?.evidenceId ?? 'none'}`,
        `Required recheck: ${report.validationEvidence.requiredRecheck?.argv.join(' ') ?? 'none'}`,
        `Rollback: ${report.rollbackGuidance.summary}`,
        '',
        `Evidence: ${report.validationEvidence.command} (read-only, writeCapable: false)`,
        cli_constants_1.LINK_MATERIALIZE_NO_WRITE_BOUNDARY,
    ].join('\n');
}
function normalizeOptionalValidationEvidenceId(input) {
    if (input === undefined || input === null || input === '') {
        return null;
    }
    if (typeof input !== 'string') {
        throw (0, errors_1.createUsageError)('Link materialize validation evidence ID must be a string.', [`Received: ${String(input)}`]);
    }
    return input;
}
function normalizeOptionalArtifactPath(input) {
    if (input === undefined || input === null || input === '') {
        return null;
    }
    if (typeof input !== 'string') {
        throw (0, errors_1.createUsageError)('Link materialize artifact path must be repository-relative.', [
            `Received: ${String(input)}`,
        ]);
    }
    try {
        return (0, paths_1.parseRepositoryRelativePath)(input).normalizedPath;
    }
    catch (error) {
        if (error instanceof RangeError) {
            throw (0, errors_1.createUsageError)('Link materialize artifact path must be repository-relative.', [error.message]);
        }
        throw error;
    }
}
function classifyArtifactInputKind(repositoryRoot, artifactPath) {
    if (artifactPath === null) {
        return 'repository';
    }
    try {
        return node_fs_1.default.existsSync((0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, artifactPath)) &&
            node_fs_1.default.statSync((0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, artifactPath)).isDirectory()
            ? 'directory'
            : 'file';
    }
    catch {
        return 'file';
    }
}
function normalizeSkipReason(status) {
    if (status === 'planned-local-target') {
        return 'planned-target';
    }
    if (status === 'missing-local-target') {
        return 'missing-local-target';
    }
    if (status === 'malformed-local-link') {
        return 'malformed-local-link';
    }
    if (status === 'outside-traversal-boundary') {
        return 'outside-traversal-boundary';
    }
    if (status === 'unsupported-external-reference') {
        return 'unsupported-external-reference';
    }
    if (status === 'resolved-non-local-source') {
        return 'resolved-non-local-source';
    }
    return 'unresolved';
}
function getRootCommand(commandInstance) {
    let current = commandInstance;
    while (current.parent !== null && current.parent !== undefined) {
        current = current.parent;
    }
    return (current ?? commandInstance);
}
