/**
 * Deterministic CAE evaluation — scope match, layered task/command merge, conflicts, acks, shadow hints.
 */
import { createHash } from "node:crypto";
import { CAE_GLOBAL_TASK_ID, canonicalizeEvaluationContextForHash } from "./evaluation-context-builder.js";
import { findCaeEnforcementBlock } from "./cae-enforcement-allowlist.js";
const AGG_TIGHTNESS = {
    policy: 4,
    think: 6,
    do: 5,
    review: 7
};
const TASK_LAYER_COMMAND = "__cae_task_layer__";
function shortDigest(parts) {
    const h = createHash("sha256").update(parts.join("|")).digest("hex");
    return h.slice(0, 40);
}
function asFamily(v) {
    if (v === "policy" || v === "think" || v === "do" || v === "review")
        return v;
    return null;
}
function matchCondition(ctx, cond) {
    const kind = cond.kind;
    if (kind === "always")
        return true;
    if (kind === "phaseKey" && typeof cond.value === "string") {
        return ctx.task.phaseKey === cond.value;
    }
    if (kind === "commandName") {
        const m = cond.match;
        const v = typeof cond.value === "string" ? cond.value : "";
        const name = ctx.command.name ?? "";
        if (m === "exact")
            return name === v;
        if (m === "prefix")
            return v.length > 0 && name.startsWith(v);
        return false;
    }
    if (kind === "commandArgEquals" && typeof cond.path === "string") {
        const expected = cond.value;
        if (expected !== null && typeof expected !== "string" && typeof expected !== "number" && typeof expected !== "boolean") {
            return false;
        }
        return ctx.command.argHints?.[cond.path] === expected;
    }
    if (kind === "taskTag") {
        const values = cond.values;
        const match = cond.match === "all" ? "all" : "any";
        if (!Array.isArray(values) || values.length === 0)
            return false;
        const tags = new Set(ctx.task.tags ?? []);
        if (match === "all") {
            return values.every((t) => typeof t === "string" && tags.has(t));
        }
        return values.some((t) => typeof t === "string" && tags.has(t));
    }
    if (kind === "taskIdPattern" && typeof cond.pattern === "string") {
        try {
            const re = new RegExp(cond.pattern);
            return re.test(ctx.task.taskId);
        }
        catch {
            return false;
        }
    }
    if (kind === "agentFailureSignal") {
        const signals = ctx.agentSignals;
        if (!signals)
            return false;
        const field = cond.field;
        if (typeof field !== "string")
            return false;
        const operator = cond.operator;
        const expected = cond.value;
        const actual = signals[field];
        if (operator === ">=") {
            return typeof actual === "number" && typeof expected === "number" && actual >= expected;
        }
        if (operator === "==") {
            return actual === expected;
        }
        if (operator === "in") {
            if (!Array.isArray(expected))
                return false;
            if (typeof actual === "number") {
                return expected.some((v) => v === actual);
            }
            return typeof actual === "string" && expected.some((v) => v === actual);
        }
        return false;
    }
    return false;
}
function activationMatches(ctx, act) {
    const state = act.lifecycleState;
    if (state !== "active")
        return false;
    const scope = act.scope;
    const conds = scope?.conditions;
    if (!Array.isArray(conds) || conds.length === 0)
        return false;
    for (const c of conds) {
        if (!c || typeof c !== "object" || Array.isArray(c))
            return false;
        if (!matchCondition(ctx, c))
            return false;
    }
    return true;
}
function artifactIdsFromActivation(act) {
    const refs = act.artifactRefs;
    if (!refs?.length)
        return [];
    return refs.map((r) => r.artifactId).filter((id) => typeof id === "string" && id.length > 0);
}
function sortActs(acts) {
    return [...acts].sort((a, b) => {
        const pa = Number(a.priority) || 0;
        const pb = Number(b.priority) || 0;
        if (pb !== pa)
            return pb - pa;
        const ida = String(a.activationId ?? "");
        const idb = String(b.activationId ?? "");
        return ida.localeCompare(idb);
    });
}
function collectByFamily(context, reg) {
    const byFamily = {
        policy: [],
        think: [],
        do: [],
        review: []
    };
    for (const act of reg.activations) {
        if (!activationMatches(context, act))
            continue;
        const fam = asFamily(act.family);
        if (!fam)
            continue;
        byFamily[fam].push(act);
    }
    return byFamily;
}
function rowsFromActs(acts, fam) {
    const out = [];
    for (const act of sortActs(acts)) {
        const aids = artifactIdsFromActivation(act);
        if (aids.length === 0)
            continue;
        out.push({
            activationId: act.activationId,
            family: fam,
            priority: act.priority,
            aggregateTightness: AGG_TIGHTNESS[fam],
            artifactIds: aids
        });
    }
    return out;
}
function mergeLayeredFamilies(taskByFam, cmdByFam) {
    const out = {
        policy: [],
        think: [],
        do: [],
        review: []
    };
    for (const fam of ["policy", "think", "do", "review"]) {
        const taskRows = rowsFromActs(taskByFam[fam], fam);
        const cmdRows = rowsFromActs(cmdByFam[fam], fam);
        const byId = new Map();
        for (const r of taskRows) {
            byId.set(String(r.activationId), r);
        }
        for (const r of cmdRows) {
            const id = String(r.activationId);
            const prev = byId.get(id);
            if (!prev) {
                byId.set(id, r);
                continue;
            }
            const pp = Number(prev.priority) || 0;
            const cp = Number(r.priority) || 0;
            if (cp > pp) {
                byId.set(id, r);
            }
        }
        const merged = [...byId.values()].sort((a, b) => {
            const p = Number(b.priority) - Number(a.priority);
            if (p !== 0)
                return p;
            return String(a.activationId).localeCompare(String(b.activationId));
        });
        out[fam] = merged;
    }
    return out;
}
function singlePassFamilies(byFamily) {
    const out = {
        policy: [],
        think: [],
        do: [],
        review: []
    };
    for (const fam of ["policy", "think", "do", "review"]) {
        out[fam] = rowsFromActs(byFamily[fam], fam);
    }
    return out;
}
function artifactSetKey(ids) {
    return [...ids].sort((a, b) => a.localeCompare(b)).join("|");
}
function detectSameFamilyConflicts(byFamily) {
    const entries = [];
    for (const fam of ["policy", "think", "do", "review"]) {
        const acts = sortActs(byFamily[fam]);
        const byPri = new Map();
        for (const a of acts) {
            const p = Number(a.priority) || 0;
            const g = byPri.get(p) ?? [];
            g.push(a);
            byPri.set(p, g);
        }
        for (const [_pri, group] of byPri) {
            if (group.length < 2)
                continue;
            const keys = new Set(group.map((g) => artifactSetKey(artifactIdsFromActivation(g))));
            if (keys.size <= 1)
                continue;
            entries.push({
                kind: "same_family_tie",
                activationIds: group.map((g) => String(g.activationId)),
                resolution: "shadow",
                detail: `divergent artifacts same priority in ${fam}`
            });
        }
    }
    return entries;
}
function stripAdvisoryWhenPolicyClaimsArtifact(familiesOut) {
    const policyArts = new Set();
    for (const row of familiesOut.policy) {
        for (const id of row.artifactIds ?? [])
            policyArts.add(id);
    }
    for (const fam of ["think", "do", "review"]) {
        familiesOut[fam] = familiesOut[fam].filter((row) => {
            const aids = row.artifactIds ?? [];
            return !aids.some((a) => policyArts.has(a));
        });
    }
}
function readAckStrength(act) {
    const ack = act.acknowledgement;
    const s = ack?.strength;
    if (s === "none" ||
        s === "surface" ||
        s === "recommend" ||
        s === "ack_required" ||
        s === "satisfy_required") {
        return s;
    }
    return null;
}
function buildPendingAcknowledgements(byFamily, mergedActs) {
    const pending = [];
    for (const fam of ["policy", "think", "do", "review"]) {
        for (const act of byFamily[fam]) {
            if (!mergedActs.has(String(act.activationId)))
                continue;
            const st = readAckStrength(act);
            if (!st || st === "none")
                continue;
            const ack = act.acknowledgement;
            const token = typeof ack?.token === "string" ? ack.token : act.activationId;
            pending.push({
                activationId: act.activationId,
                strength: st,
                ackToken: token
            });
        }
    }
    return pending;
}
function mergedActivationIds(familiesOut) {
    const s = new Set();
    for (const fam of ["policy", "think", "do", "review"]) {
        for (const row of familiesOut[fam]) {
            s.add(String(row.activationId));
        }
    }
    return s;
}
function buildTaskLayerContext(ctx) {
    return {
        ...ctx,
        command: {
            name: TASK_LAYER_COMMAND,
            moduleId: "context-activation"
        }
    };
}
/**
 * Evaluate registry activations against a v1 evaluation context.
 */
export function evaluateActivationBundle(context, reg, options) {
    const evalMode = options?.evalMode === "shadow" ? "shadow" : "live";
    const layered = options?.layered !== false && context.task.taskId !== CAE_GLOBAL_TASK_ID;
    let familiesOut;
    let unionByFamily;
    if (layered) {
        const taskCtx = buildTaskLayerContext(context);
        const taskFam = collectByFamily(taskCtx, reg);
        const cmdFam = collectByFamily(context, reg);
        unionByFamily = { policy: [], think: [], do: [], review: [] };
        for (const fam of ["policy", "think", "do", "review"]) {
            const m = new Map();
            for (const a of [...taskFam[fam], ...cmdFam[fam]]) {
                m.set(String(a.activationId), a);
            }
            unionByFamily[fam] = [...m.values()];
        }
        familiesOut = mergeLayeredFamilies(taskFam, cmdFam);
    }
    else {
        unionByFamily = collectByFamily(context, reg);
        familiesOut = singlePassFamilies(unionByFamily);
    }
    stripAdvisoryWhenPolicyClaimsArtifact(familiesOut);
    const conflictEntries = detectSameFamilyConflicts(unionByFamily);
    const mergedIds = mergedActivationIds(familiesOut);
    const pendingAcknowledgements = buildPendingAcknowledgements(unionByFamily, mergedIds);
    const ctxCanon = canonicalizeEvaluationContextForHash(context);
    const bundleId = `cae.bundle.${shortDigest([ctxCanon, reg.registryDigest, layered ? "L1" : "L0"])}`;
    const traceId = `cae.trace.${shortDigest([bundleId, "trace", evalMode])}`;
    const bundle = {
        schemaVersion: 1,
        bundleId,
        evaluationPipelineMode: evalMode,
        families: {
            policy: familiesOut.policy,
            think: familiesOut.think,
            do: familiesOut.do,
            review: familiesOut.review
        },
        pendingAcknowledgements,
        conflictShadowSummary: {
            evalMode,
            entries: conflictEntries
        },
        traceId,
        explanationRef: `explain:${traceId.slice(-24)}`
    };
    const cmdName = context.command.name ?? "";
    const block = findCaeEnforcementBlock(cmdName, bundle);
    if (evalMode === "shadow") {
        const wouldActivate = [];
        for (const fam of ["policy", "think", "do", "review"]) {
            for (const act of sortActs(unionByFamily[fam])) {
                wouldActivate.push({
                    activationId: act.activationId,
                    family: fam,
                    artifactIds: artifactIdsFromActivation(act)
                });
            }
        }
        const wouldRequireAck = pendingAcknowledgements.map((p) => ({
            activationId: p.activationId,
            strength: p.strength,
            ackToken: p.ackToken
        }));
        const wouldEnforce = [];
        if (block) {
            wouldEnforce.push({
                activationId: "cae.enforcement.pilot",
                lane: "enforcement",
                commandName: cmdName
            });
        }
        let usefulnessSignal = "absent";
        if (conflictEntries.length > 2)
            usefulnessSignal = "noisy";
        else if (conflictEntries.length > 0 || familiesOut.policy.length > 0)
            usefulnessSignal = "useful";
        bundle.shadowObservation = {
            wouldActivate,
            wouldRequireAck,
            wouldEnforce,
            usefulnessSignal
        };
    }
    const famCounts = {
        policy: familiesOut.policy.length,
        think: familiesOut.think.length,
        do: familiesOut.do.length,
        review: familiesOut.review.length
    };
    const trace = {
        schemaVersion: 1,
        traceId,
        bundleId,
        anchors: {
            evaluationContextContentHash: shortDigest([ctxCanon]),
            registryContentHash: reg.registryDigest.slice(0, 64)
        },
        events: [
            {
                seq: 0,
                eventType: "cae.trace.eval.summary",
                payload: {
                    evalMode,
                    layered,
                    familyCounts: famCounts,
                    candidateCount: reg.activations.length,
                    conflictCount: conflictEntries.length
                }
            },
            {
                seq: 1,
                eventType: "cae.trace.layer.merge",
                payload: { layered, taskLayerCommand: TASK_LAYER_COMMAND }
            },
            {
                seq: 2,
                eventType: "cae.trace.ack.summary",
                payload: { pendingAckCount: pendingAcknowledgements.length }
            }
        ]
    };
    if (block) {
        trace.events.push({
            seq: 3,
            eventType: "cae.trace.enforcement.probe",
            payload: { commandName: cmdName, blockId: block.id, matched: true }
        });
    }
    return { bundle, trace, traceId };
}
