"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createLinkDispositionApplyCommand = createLinkDispositionApplyCommand;
const node_fs_1 = __importDefault(require("node:fs"));
const config_contract_1 = require("../../../lib/config/config-contract");
const errors_1 = require("../../../lib/errors");
const output_1 = require("../../../lib/output");
const paths_1 = require("../../../lib/paths");
const runtime_contract_1 = require("../../../lib/runtime/runtime-contract");
const trace_link_dispositions_1 = require("../../../lib/trace-link-dispositions");
const global_format_1 = require("../global-format");
async function createLinkDispositionApplyCommand() {
    const { Command } = await import('commander');
    const command = new Command('apply');
    command
        .description('Apply migrated trace-link disposition updates through an explicit write-capable command.')
        .option('--id <id>', 'Apply one migrated disposition id.')
        .option('--target <path>', 'Apply one migrated disposition target path.')
        .option('--all-migrated', 'Apply every migrated disposition in storage.')
        .option('--artifact <path>', 'Focus apply discovery on one repository-relative planning artifact.')
        .option('--include-body-references', 'Opt in to exact migrated-target rewrites in parsed Markdown body-reference fields.')
        .option('--dry-run', 'Preview apply evidence without writing files.')
        .option('--format <format>', 'Output format. Accepted values: text, json.')
        .addHelpText('after', [
        '',
        'Boundaries:',
        '  Write-capable: applies migrated disposition updates only when write execution is supported by the selected workflow.',
        '  Dry-run: reads disposition storage and planning trace fields, but writes no files, timestamps, or apply evidence.',
        '  Write execution mutates only mechanically safe repository-local trace fields and records apply evidence after successful writes.',
        '  Body-reference rewrites require --include-body-references and exact parsed occurrences in named Markdown sections.',
        '  Retired and deleted dispositions, external targets, fenced-code references, and inferred replacement targets are outside apply scope.',
        '  Rollback: revert the reported changed artifact paths and .day-shift/state/trace-link-dispositions.json from version control.',
    ].join('\n'))
        .action((_, commandInstance) => {
        const options = commandInstance.opts();
        const requestedFormat = options.format ?? commandInstance.optsWithGlobals().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 disposition apply command output format',
            });
        }
        const repositoryRoot = resolveRepositoryContext(commandInstance).root;
        const resolvedConfig = (0, config_contract_1.resolveDayShiftConfig)({
            repositoryContext: {
                root: repositoryRoot,
                detectionSource: 'explicit',
                warnedAboutFallback: false,
            },
        });
        const storageAbsolutePath = (0, trace_link_dispositions_1.resolveTraceLinkDispositionStoragePath)(repositoryRoot);
        if (!node_fs_1.default.existsSync(storageAbsolutePath)) {
            throw (0, errors_1.createUsageError)('Trace-link disposition storage does not exist.', [
                `Storage path: ${trace_link_dispositions_1.TRACE_LINK_DISPOSITION_STORAGE_PATH}`,
            ]);
        }
        const document = (0, trace_link_dispositions_1.parseTraceLinkDispositionStorageDocument)(node_fs_1.default.readFileSync(storageAbsolutePath, 'utf8'));
        const artifactPath = normalizeOptionalRepositoryPath(options.artifact, 'artifact');
        const selectedDispositions = selectApplyDispositions({
            dispositions: document.dispositions,
            id: normalizeOptionalString(options.id, 'id'),
            target: normalizeOptionalRepositoryPath(options.target, 'target'),
            allMigrated: options.allMigrated === true,
        });
        assertMigratedDispositions(selectedDispositions);
        const preview = (0, trace_link_dispositions_1.previewTraceLinkDispositionUpdates)({
            repositoryRoot,
            dispositionStoragePath: trace_link_dispositions_1.TRACE_LINK_DISPOSITION_STORAGE_PATH,
            dispositions: selectedDispositions,
            artifactPath,
            planningRootPath: (0, paths_1.toRepositoryRelativePath)(repositoryRoot, resolvedConfig.derived.planningRootPath),
            specIndexPath: (0, paths_1.toRepositoryRelativePath)(repositoryRoot, resolvedConfig.derived.specIndexPath),
        });
        if (options.dryRun === true) {
            const dryRun = createDryRunResult(preview);
            const rollbackGuidance = createRollbackGuidance({
                dryRun: true,
                changedArtifactPaths: [],
                storagePath: trace_link_dispositions_1.TRACE_LINK_DISPOSITION_STORAGE_PATH,
                applyEvidencePersisted: false,
            });
            (0, output_1.emitSuccess)({
                command: 'link disposition apply',
                format,
                text: renderApplyDryRunText(dryRun, rollbackGuidance),
                data: {
                    repositoryRoot,
                    readOnly: true,
                    writeCapable: true,
                    dryRun: true,
                    localOnly: true,
                    storagePath: trace_link_dispositions_1.TRACE_LINK_DISPOSITION_STORAGE_PATH,
                    artifact: artifactPath,
                    includeBodyReferences: options.includeBodyReferences === true,
                    applyEvidencePersisted: false,
                    rollbackGuidance,
                    ...dryRun,
                },
            });
            return;
        }
        const appliedAt = new Date().toISOString();
        let applyResult;
        try {
            applyResult = (0, trace_link_dispositions_1.applyTraceLinkDispositionPreviewMutations)({
                repositoryRoot,
                storageDocument: document,
                preview,
                appliedAt,
                includeBodyReferences: options.includeBodyReferences === true,
            });
        }
        catch (error) {
            throw (0, errors_1.createUsageError)('Trace-link disposition apply was blocked before writing files.', [
                error instanceof Error ? error.message : String(error),
                'No files were written.',
            ]);
        }
        node_fs_1.default.writeFileSync(storageAbsolutePath, (0, trace_link_dispositions_1.serializeTraceLinkDispositionStorageDocument)({
            schemaVersion: document.schemaVersion,
            dispositions: applyResult.dispositions,
        }), 'utf8');
        const changedPaths = [...applyResult.changedArtifactPaths, trace_link_dispositions_1.TRACE_LINK_DISPOSITION_STORAGE_PATH];
        const rollbackGuidance = createRollbackGuidance({
            dryRun: false,
            changedArtifactPaths: applyResult.changedArtifactPaths,
            storagePath: trace_link_dispositions_1.TRACE_LINK_DISPOSITION_STORAGE_PATH,
            applyEvidencePersisted: true,
        });
        (0, output_1.emitSuccess)({
            command: 'link disposition apply',
            format,
            text: renderApplyText({
                changedPaths,
                skippedPaths: applyResult.skippedPaths,
                conflictPaths: applyResult.conflictPaths,
                unresolvedPaths: applyResult.unresolvedPaths,
                preview,
                rollbackGuidance,
            }),
            data: {
                repositoryRoot,
                readOnly: false,
                writeCapable: true,
                dryRun: false,
                localOnly: true,
                storagePath: trace_link_dispositions_1.TRACE_LINK_DISPOSITION_STORAGE_PATH,
                artifact: artifactPath,
                includeBodyReferences: options.includeBodyReferences === true,
                selectedDispositionCount: preview.selectedDispositionCount,
                proposalCount: preview.proposals.length,
                changedPaths,
                changedArtifactPaths: applyResult.changedArtifactPaths,
                skippedPaths: applyResult.skippedPaths,
                conflictPaths: applyResult.conflictPaths,
                unresolvedPaths: applyResult.unresolvedPaths,
                applyEvidencePersisted: true,
                rollbackGuidance,
                appliedAt,
                preview,
            },
        });
    });
    return command;
}
function createDryRunResult(preview) {
    return {
        selectedDispositionCount: preview.selectedDispositionCount,
        proposalCount: preview.proposals.length,
        changedPaths: [],
        skippedPaths: uniqueEvidencePaths(preview.skipped),
        conflictPaths: uniqueEvidencePaths(preview.conflicts),
        unresolvedPaths: uniqueEvidencePaths(preview.unresolved),
        preview,
    };
}
function selectApplyDispositions(options) {
    const selectorCount = [
        options.id !== undefined,
        options.target !== null && options.target !== undefined,
        options.allMigrated === true,
    ].filter(Boolean).length;
    if (selectorCount > 1) {
        throw (0, errors_1.createUsageError)('Use only one of --id, --target, or --all-migrated for link disposition apply.');
    }
    if (options.id !== undefined) {
        const disposition = options.dispositions.find((record) => record.id === options.id);
        if (disposition === undefined) {
            throw (0, errors_1.createUsageError)(`Trace-link disposition '${options.id}' was not found.`);
        }
        return [disposition];
    }
    if (options.target !== null && options.target !== undefined) {
        const disposition = options.dispositions.find((record) => record.target.normalizedPath === options.target);
        if (disposition === undefined) {
            throw (0, errors_1.createUsageError)(`Trace-link disposition target '${options.target}' was not found.`);
        }
        return [disposition];
    }
    if (options.allMigrated === true) {
        const dispositions = options.dispositions.filter((record) => record.status === 'migrated');
        if (dispositions.length === 0) {
            throw (0, errors_1.createUsageError)('No migrated trace-link dispositions are available to apply.');
        }
        return dispositions;
    }
    throw (0, errors_1.createUsageError)('Provide --id, --target, or --all-migrated for link disposition apply.');
}
function assertMigratedDispositions(dispositions) {
    const unsupported = dispositions.filter((disposition) => disposition.status !== 'migrated');
    if (unsupported.length > 0) {
        throw (0, errors_1.createUsageError)('link disposition apply only supports migrated dispositions as rewrite sources.', [
            `Unsupported disposition ids: ${unsupported.map((disposition) => disposition.id).join(', ')}`,
        ]);
    }
}
function createRollbackGuidance(options) {
    if (options.dryRun) {
        return {
            summary: 'Dry-run only; no rollback is required because no files were written.',
            changedArtifactPaths: [],
            storagePath: options.storagePath,
            applyEvidencePersisted: false,
            commands: [],
        };
    }
    return {
        summary: 'To roll back this apply, revert the reported changed paths from version control; artifact changes and storage apply evidence are separate changed paths.',
        changedArtifactPaths: options.changedArtifactPaths,
        storagePath: options.storagePath,
        applyEvidencePersisted: options.applyEvidencePersisted,
        commands: [],
    };
}
function renderApplyDryRunText(result, rollbackGuidance) {
    const lines = [
        'Trace-link disposition apply dry-run',
        'Write-capable command: true',
        'Dry-run: true',
        `Selected dispositions: ${result.selectedDispositionCount}`,
        `Proposed updates: ${result.proposalCount}`,
        '',
    ];
    if (result.preview.proposals.length === 0) {
        lines.push('Proposed migrated-target updates: none.');
    }
    else {
        lines.push('Proposed migrated-target updates:');
        for (const proposal of result.preview.proposals) {
            lines.push(`- ${proposal.sourceArtifactPath} ${proposal.traceField}: ${proposal.oldTargetPath} -> ${proposal.newTargetPath}`);
        }
    }
    if (result.skippedPaths.length > 0) {
        lines.push('', `Skipped paths: ${result.skippedPaths.join(', ')}`);
    }
    if (result.unresolvedPaths.length > 0) {
        lines.push('', `Unresolved paths: ${result.unresolvedPaths.join(', ')}`);
    }
    lines.push('', `Apply evidence persisted: ${String(rollbackGuidance.applyEvidencePersisted)}`, `Rollback guidance: ${rollbackGuidance.summary}`, 'No files were written.');
    return `${lines.join('\n')}\n`;
}
function renderApplyText(result) {
    const lines = [
        'Trace-link disposition apply',
        'Write-capable command: true',
        'Dry-run: false',
        `Selected dispositions: ${result.preview.selectedDispositionCount}`,
        `Proposed updates: ${result.preview.proposals.length}`,
        `Changed paths: ${result.changedPaths.length === 0 ? 'none' : result.changedPaths.join(', ')}`,
        `Apply evidence persisted: ${String(result.rollbackGuidance.applyEvidencePersisted)}`,
    ];
    if (result.skippedPaths.length > 0) {
        lines.push(`Skipped paths: ${result.skippedPaths.join(', ')}`);
    }
    if (result.conflictPaths.length > 0) {
        lines.push(`Conflict paths: ${result.conflictPaths.join(', ')}`);
    }
    if (result.unresolvedPaths.length > 0) {
        lines.push(`Unresolved paths: ${result.unresolvedPaths.join(', ')}`);
    }
    lines.push(`Rollback guidance: ${result.rollbackGuidance.summary}`);
    for (const command of result.rollbackGuidance.commands) {
        lines.push(`Rollback command: ${command}`);
    }
    return `${lines.join('\n')}\n`;
}
function uniqueEvidencePaths(evidence) {
    const paths = evidence
        .map((item) => item.sourceArtifactPath ?? item.dispositionTargetPath ?? item.dispositionId)
        .filter((value) => value !== undefined);
    return [...new Set(paths)];
}
function normalizeOptionalRepositoryPath(input, fieldName) {
    if (input === undefined || input === null || input === '') {
        return null;
    }
    if (typeof input !== 'string') {
        throw (0, errors_1.createUsageError)(`Link disposition apply ${fieldName} must be a repository-relative path.`, []);
    }
    try {
        return (0, paths_1.parseRepositoryRelativePath)(input).normalizedPath;
    }
    catch (error) {
        throw (0, errors_1.createUsageError)(`Link disposition apply ${fieldName} must be a repository-relative path.`, [
            error instanceof Error ? error.message : String(error),
        ]);
    }
}
function normalizeOptionalString(input, fieldName) {
    if (input === undefined || input === null || input === '') {
        return undefined;
    }
    if (typeof input !== 'string') {
        throw (0, errors_1.createUsageError)(`Link disposition apply ${fieldName} must be a string.`, []);
    }
    return input;
}
function getRootCommand(commandInstance) {
    let current = commandInstance;
    while (current.parent !== null && current.parent !== undefined) {
        current = current.parent;
    }
    return (current ?? commandInstance);
}
function resolveRepositoryContext(commandInstance) {
    const rootCommand = getRootCommand(commandInstance);
    if (rootCommand.configRepositoryContext) {
        return rootCommand.configRepositoryContext;
    }
    return (0, runtime_contract_1.createRepositoryContext)((0, runtime_contract_1.detectRepositoryRoot)(), false);
}
