"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.discoverTraceLinkDispositionAffectedArtifacts = discoverTraceLinkDispositionAffectedArtifacts;
const output_1 = require("../output");
const model_1 = require("./model");
const validation_links_discovery_1 = require("../planning/validation-links-discovery");
function discoverTraceLinkDispositionAffectedArtifacts(options) {
    const discovery = (0, validation_links_discovery_1.discoverValidationLinksArtifacts)({
        repositoryRoot: options.repositoryRoot,
        artifactPath: options.artifactPath,
        planningRootPath: options.planningRootPath,
        specIndexPath: options.specIndexPath
    });
    const { dispositions, malformed } = normalizeDiscoveryDispositions(options.dispositions);
    const references = dispositions.flatMap((disposition) => discoverDispositionReferences(discovery, disposition));
    const skipped = [
        ...malformed,
        ...discovery.evidence.map((evidence) => ({
            reason: "artifact-not-parsed",
            sourceArtifactPath: evidence.path,
            artifactFamily: evidence.family,
            parseStatus: evidence.status,
            message: evidence.message,
            noWriteRequired: true
        })),
        ...createNoMatchEvidence(dispositions, references)
    ];
    return {
        readOnly: true,
        localOnly: true,
        repositoryRoot: options.repositoryRoot,
        artifact: options.artifactPath ?? null,
        traversalBoundaries: output_1.TRACE_LINK_TRAVERSAL_BOUNDARIES,
        affectedArtifacts: groupReferencesByArtifact(references),
        references,
        skipped,
        summary: {
            dispositionCount: dispositions.length,
            affectedArtifactCount: new Set(references.map((reference) => reference.sourceArtifactPath)).size,
            affectedReferenceCount: references.length,
            skippedEvidenceCount: skipped.length,
            malformedDispositionCount: malformed.length,
            noMatchDispositionCount: skipped.filter((evidence) => evidence.reason === "no-matching-trace-link").length
        }
    };
}
function normalizeDiscoveryDispositions(dispositions) {
    const normalized = [];
    const malformed = [];
    for (const disposition of dispositions) {
        if (isTraceLinkDisposition(disposition)) {
            try {
                (0, model_1.normalizeTraceLinkDispositionTarget)(disposition.target.normalizedPath);
                if (disposition.status === "migrated") {
                    (0, model_1.normalizeTraceLinkDispositionTarget)(disposition.replacementTarget.normalizedPath);
                }
                normalized.push(disposition);
            }
            catch (error) {
                malformed.push(createMalformedDispositionEvidence(disposition.id, formatErrorMessage(error)));
            }
            continue;
        }
        malformed.push(createMalformedDispositionEvidence(undefined, "Disposition input is not a valid trace-link disposition record."));
    }
    return { dispositions: normalized, malformed };
}
function discoverDispositionReferences(discovery, disposition) {
    return discovery.artifacts
        .filter((artifact) => artifact.parseStatus === "parsed")
        .flatMap((artifact) => artifact.traceFields.flatMap((field) => discoverFieldReferences(artifact, field, disposition)));
}
function discoverFieldReferences(artifact, field, disposition) {
    return field.values.flatMap((value) => extractTraceValues(value).flatMap((traceValue) => {
        const normalizedTargetPath = normalizeTraceValue(traceValue);
        if (normalizedTargetPath !== disposition.target.normalizedPath) {
            return [];
        }
        return [{
                dispositionId: disposition.id,
                dispositionStatus: disposition.status,
                dispositionTargetPath: disposition.target.normalizedPath,
                sourceArtifactPath: artifact.path,
                artifactFamily: artifact.family,
                traceField: field.field,
                relationship: field.relationship,
                originalValue: traceValue,
                normalizedTargetPath,
                noWriteRequired: true
            }];
    }));
}
function extractTraceValues(value) {
    if (typeof value === "string") {
        return [value];
    }
    if (isRecord(value) && typeof value.ref === "string" && value.ref !== "") {
        return [value.ref];
    }
    return [];
}
function normalizeTraceValue(value) {
    try {
        return (0, model_1.normalizeTraceLinkDispositionTarget)(value).normalizedPath;
    }
    catch {
        return null;
    }
}
function createNoMatchEvidence(dispositions, references) {
    const matchedDispositionIds = new Set(references.map((reference) => reference.dispositionId));
    return dispositions
        .filter((disposition) => !matchedDispositionIds.has(disposition.id))
        .map((disposition) => ({
        reason: "no-matching-trace-link",
        dispositionId: disposition.id,
        dispositionTargetPath: disposition.target.normalizedPath,
        message: `No supported planning trace-link field references '${disposition.target.normalizedPath}'.`,
        noWriteRequired: true
    }));
}
function groupReferencesByArtifact(references) {
    const groups = new Map();
    for (const reference of references) {
        const current = groups.get(reference.sourceArtifactPath) ?? [];
        current.push(reference);
        groups.set(reference.sourceArtifactPath, current);
    }
    return [...groups.entries()].map(([path, artifactReferences]) => ({
        path,
        artifactFamily: artifactReferences[0].artifactFamily,
        references: artifactReferences
    }));
}
function createMalformedDispositionEvidence(dispositionId, message) {
    return {
        reason: "disposition-malformed",
        dispositionId,
        message,
        noWriteRequired: true
    };
}
function isTraceLinkDisposition(value) {
    return isRecord(value) &&
        typeof value.id === "string" &&
        (value.status === "retired" || value.status === "deleted" || value.status === "migrated") &&
        isRecord(value.target) &&
        typeof value.target.normalizedPath === "string" &&
        typeof value.createdAt === "string" &&
        typeof value.updatedAt === "string" &&
        (value.status !== "migrated" ||
            (isRecord(value.replacementTarget) && typeof value.replacementTarget.normalizedPath === "string"));
}
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function formatErrorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
