"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ACTION_RELEVANCE_RULE_REGISTRY = exports.ACTION_RELEVANCE_RULE_IDS = exports.ACTION_RELEVANCE_POSTURES = void 0;
exports.classifyFindingsForAction = classifyFindingsForAction;
exports.groupActionBlockers = groupActionBlockers;
exports.toActionRelevanceValidationFinding = toActionRelevanceValidationFinding;
const output_1 = require("../output");
const artifact_lifecycle_1 = require("../planning/artifact-lifecycle");
exports.ACTION_RELEVANCE_POSTURES = ["blocking", "warning", "informational"];
exports.ACTION_RELEVANCE_RULE_IDS = [
    "action-relevance.contradictory-evidence",
    "action-relevance.repository-wide-gate",
    "action-relevance.authorization",
    "action-relevance.runtime-setup",
    "action-relevance.transition-precondition",
    "action-relevance.direct-artifact",
    "action-relevance.dependency",
    "action-relevance.implementation-target",
    "action-relevance.ambiguous-applicable",
    "action-relevance.unrelated-history"
];
exports.ACTION_RELEVANCE_RULE_REGISTRY = Object.freeze([
    { id: "action-relevance.contradictory-evidence", description: "Contradictory applicability evidence fails closed." },
    { id: "action-relevance.repository-wide-gate", description: "Release and migration actions retain repository-wide gates." },
    { id: "action-relevance.authorization", description: "Authorization failures block write-capable actions." },
    { id: "action-relevance.runtime-setup", description: "Runtime/setup failures block actions that require the local runtime." },
    { id: "action-relevance.transition-precondition", description: "Lifecycle state resolved by the selected transition remains visible without blocking that transition." },
    { id: "action-relevance.direct-artifact", description: "Findings on the selected artifact apply directly." },
    { id: "action-relevance.dependency", description: "Findings on declared dependencies apply to the action." },
    { id: "action-relevance.implementation-target", description: "Findings on declared implementation targets apply to the action." },
    { id: "action-relevance.ambiguous-applicable", description: "Unlocated safety failures in the selected domain fail closed." },
    { id: "action-relevance.unrelated-history", description: "Unrelated findings remain audit context without blocking the action." }
]);
function classifyFindingsForAction(options) {
    const descriptor = (0, output_1.getRegisteredCommandDescriptor)(options.actionId.trim());
    if (descriptor === null) {
        throw new TypeError(`Unknown action descriptor '${options.actionId}'.`);
    }
    if (descriptor.availability !== "callable") {
        throw new TypeError(`Action descriptor '${options.actionId}' is ${descriptor.availability} and cannot be selected for execution.`);
    }
    const context = normalizeContext(options.context);
    const classifications = options.findings.map((finding) => classifyFinding(descriptor, finding, context));
    const blockers = classifications.filter((entry) => entry.relevance === "blocking");
    return {
        selectedAction: descriptorEvidence(descriptor),
        classifications,
        currentActionBlockers: blockers,
        blockerGroups: groupActionBlockers(blockers),
        fullDetailCommand: options.fullDetailCommand ?? "day-shift doctor --format json --detail full"
    };
}
function groupActionBlockers(blockers) {
    const groups = new Map();
    for (const blocker of blockers) {
        const existing = groups.get(blocker.originalFinding.code);
        if (existing === undefined) {
            groups.set(blocker.originalFinding.code, {
                code: blocker.originalFinding.code,
                count: 1,
                representative: blocker
            });
        }
        else {
            existing.count += 1;
        }
    }
    return [...groups.values()].sort((left, right) => left.code.localeCompare(right.code));
}
function toActionRelevanceValidationFinding(entry) {
    return {
        ...entry.finding,
        id: entry.finding.id ?? `${entry.ruleId}:${entry.originalFinding.code}`,
        ruleFamily: entry.ruleId,
        evidenceSource: `selected-action:${entry.selectedActionId}`,
        reason: entry.relevance,
        noWriteBoundary: entry.noWriteRecovery.join(" "),
        evidence: {
            category: "command-specific",
            details: {
                ...(entry.finding.evidence?.details ?? {}),
                actionRelevance: {
                    ruleId: entry.ruleId,
                    selectedActionId: entry.selectedActionId,
                    relevance: entry.relevance,
                    evidenceBasis: entry.evidenceBasis,
                    originalFinding: entry.originalFinding,
                    noWriteRecovery: entry.noWriteRecovery
                }
            }
        }
    };
}
function classifyFinding(descriptor, finding, context) {
    const findingPaths = collectFindingPaths(finding);
    const code = finding.code.toLowerCase();
    const repositoryGate = descriptor.artifactFamily === "release"
        || descriptor.capability === "migration"
        || descriptor.mutationPosture === "applies-migration";
    const selectedArtifactMatches = intersectPaths(findingPaths, context.selectedArtifactPath === null ? [] : [context.selectedArtifactPath]);
    const transitionPrecondition = selectedArtifactMatches.length === 0
        ? null
        : (0, artifact_lifecycle_1.resolveArtifactLifecycleTransitionPrecondition)(descriptor.command, finding.code);
    let ruleId;
    let relationship;
    let matchedPaths = [];
    let explanation;
    if (context.contradictoryEvidence) {
        ruleId = "action-relevance.contradictory-evidence";
        relationship = "contradictory";
        explanation = "The supplied applicability evidence is contradictory, so safety evidence is retained as current-action blocking evidence.";
    }
    else if (repositoryGate) {
        ruleId = "action-relevance.repository-wide-gate";
        relationship = "repository-wide-gate";
        explanation = "The selected descriptor is a release or migration action with an authoritative repository-wide gate.";
    }
    else if (descriptor.writeCapable && /(authori[sz]|permission|access|credential)/u.test(code)) {
        ruleId = "action-relevance.authorization";
        relationship = "authorization";
        explanation = "The finding reports authorization evidence for a write-capable selected action.";
    }
    else if (context.healthDomain === "runtime" || /(runtime|setup|config|node|git|template-resolution)/u.test(code)) {
        ruleId = "action-relevance.runtime-setup";
        relationship = "runtime-setup";
        explanation = "The finding is runtime/setup evidence required to execute the selected action.";
    }
    else if (transitionPrecondition !== null) {
        ruleId = "action-relevance.transition-precondition";
        relationship = "transition-precondition";
        matchedPaths = selectedArtifactMatches;
        explanation = transitionPrecondition.explanation;
    }
    else if ((matchedPaths = selectedArtifactMatches).length > 0) {
        ruleId = "action-relevance.direct-artifact";
        relationship = "direct-artifact";
        explanation = "The finding resolves directly to the selected artifact.";
    }
    else if ((matchedPaths = intersectPaths(findingPaths, context.dependencyPaths)).length > 0) {
        ruleId = "action-relevance.dependency";
        relationship = "dependency";
        explanation = "The finding resolves to a declared dependency of the selected action.";
    }
    else if ((matchedPaths = intersectPaths(findingPaths, context.implementationTargetPaths)).length > 0
        || finding.affectedPaths?.some((entry) => entry.role === "declared-target") === true) {
        ruleId = "action-relevance.implementation-target";
        relationship = "implementation-target";
        matchedPaths = matchedPaths.length > 0
            ? matchedPaths
            : finding.affectedPaths?.filter((entry) => entry.role === "declared-target").map((entry) => entry.path) ?? [];
        explanation = "The finding resolves to a declared implementation target of the selected action.";
    }
    else if (finding.severity === "error" && findingPaths.length === 0 && context.healthDomain !== "repository-audit") {
        ruleId = "action-relevance.ambiguous-applicable";
        relationship = "ambiguous-applicable";
        explanation = "The selected scoped domain produced safety evidence without enough location detail to prove it unrelated.";
    }
    else {
        ruleId = "action-relevance.unrelated-history";
        relationship = "unrelated-history";
        explanation = "No direct artifact, dependency, target, runtime, authorization, or repository-gate relationship was established.";
    }
    const applicable = ruleId !== "action-relevance.unrelated-history";
    const relevance = ruleId === "action-relevance.transition-precondition"
        ? transitionPrecondition?.relevance ?? "warning"
        : applicable && finding.severity === "error"
            ? "blocking"
            : finding.severity === "informational" ? "informational" : "warning";
    return {
        ruleId,
        selectedActionId: descriptor.command,
        relevance,
        evidenceBasis: {
            relationship,
            matchedPaths,
            descriptor: descriptorEvidence(descriptor),
            explanation
        },
        originalFinding: {
            id: finding.id ?? null,
            code: finding.code,
            severity: finding.severity,
            message: finding.message
        },
        noWriteRecovery: normalizeRecovery(finding),
        finding
    };
}
function normalizeContext(context = {}) {
    return {
        selectedArtifactPath: normalizePath(context.selectedArtifactPath ?? ""),
        dependencyPaths: (context.dependencyPaths ?? []).map(normalizePath),
        implementationTargetPaths: (context.implementationTargetPaths ?? []).map(normalizePath),
        healthDomain: context.healthDomain ?? "repository-audit",
        contradictoryEvidence: context.contradictoryEvidence ?? false
    };
}
function descriptorEvidence(descriptor) {
    return {
        command: descriptor.command,
        writeCapable: descriptor.writeCapable,
        capability: descriptor.capability,
        artifactFamily: descriptor.artifactFamily,
        authorization: descriptor.authorization,
        confirmation: descriptor.confirmation,
        selectionRequired: descriptor.selectionRequired,
        coordinationScope: descriptor.coordinationScope
    };
}
function collectFindingPaths(finding) {
    return [...new Set([
            finding.path,
            finding.artifactPath,
            ...(finding.affectedPaths?.map((entry) => entry.path) ?? []),
            ...(finding.artifacts?.map((entry) => entry.path) ?? [])
        ].filter((value) => typeof value === "string" && value.trim() !== "").map(normalizePath))];
}
function intersectPaths(left, right) {
    const expected = new Set(right.filter(Boolean).map(normalizePath));
    return [...new Set(left.map(normalizePath).filter((entry) => expected.has(entry)))];
}
function normalizePath(value) {
    return value.trim().replace(/\\/gu, "/").replace(/^\.\//u, "");
}
function normalizeRecovery(finding) {
    const recovery = finding.recovery === undefined
        ? []
        : typeof finding.recovery === "string" ? [finding.recovery] : [...finding.recovery];
    if (finding.recommendedCommand !== undefined)
        recovery.push(finding.recommendedCommand);
    recovery.push(finding.noWriteBoundary ?? "Review the evidence and rerun the selected read-only doctor scope; this classification authorizes no writes.");
    return [...new Set(recovery)];
}
