import { readFile } from 'node:fs/promises';
// ---------------------------------------------------------------------------
// Shared primitives
// ---------------------------------------------------------------------------
async function readJsonFile(filePath) {
    const raw = await readFile(filePath, 'utf8');
    return JSON.parse(raw);
}
function expectRecord(value, path) {
    if (!value || typeof value !== 'object' || Array.isArray(value)) {
        throw new Error(`${path} must be an object.`);
    }
    return value;
}
function expectArray(value, path) {
    if (!Array.isArray(value)) {
        throw new Error(`${path} must be an array.`);
    }
    return value;
}
function expectString(value, path) {
    if (typeof value !== 'string' || value.length === 0) {
        throw new Error(`${path} must be a non-empty string.`);
    }
    return value;
}
function optionalString(value, path) {
    if (value === undefined)
        return undefined;
    return expectString(value, path);
}
function optionalNumber(value, path) {
    if (value === undefined)
        return undefined;
    if (typeof value !== 'number' || Number.isNaN(value)) {
        throw new Error(`${path} must be a number.`);
    }
    return value;
}
function optionalBoolean(value, path) {
    if (value === undefined)
        return undefined;
    if (typeof value !== 'boolean') {
        throw new Error(`${path} must be a boolean.`);
    }
    return value;
}
function optionalStringArray(value, path) {
    if (value === undefined)
        return undefined;
    return expectArray(value, path).map((item, index) => expectString(item, `${path}[${index}]`));
}
function optionalPositiveNumber(value, path) {
    const parsed = optionalNumber(value, path);
    if (parsed !== undefined && parsed <= 0) {
        throw new Error(`${path} must be greater than 0.`);
    }
    return parsed;
}
function optionalRate(value, path) {
    const parsed = optionalNumber(value, path);
    if (parsed !== undefined && (parsed < 0 || parsed > 1)) {
        throw new Error(`${path} must be between 0 and 1.`);
    }
    return parsed;
}
function optionalScore(value, path) {
    const parsed = optionalNumber(value, path);
    if (parsed !== undefined && (parsed < 0 || parsed > 100)) {
        throw new Error(`${path} must be between 0 and 100.`);
    }
    return parsed;
}
function ensureUnique(values, path) {
    const seen = new Set();
    for (const value of values) {
        if (seen.has(value)) {
            throw new Error(`${path} contains duplicate value: ${value}`);
        }
        seen.add(value);
    }
}
// ---------------------------------------------------------------------------
// Retrieval eval fixture
// ---------------------------------------------------------------------------
export async function loadRetrievalEvalFixture(filePath) {
    return parseRetrievalEvalFixture(await readJsonFile(filePath), filePath);
}
export function parseRetrievalEvalFixture(value, source = 'retrieval eval fixture') {
    const fixture = expectRecord(value, source);
    const name = expectString(fixture.name, `${source}.name`);
    const project = expectString(fixture.project, `${source}.project`);
    const knowledge = expectArray(fixture.knowledge, `${source}.knowledge`).map((item, index) => (parseRetrievalKnowledge(item, `${source}.knowledge[${index}]`)));
    const feedbackEvents = fixture.feedbackEvents === undefined
        ? undefined
        : expectArray(fixture.feedbackEvents, `${source}.feedbackEvents`).map((item, index) => (parseRetrievalFeedbackEvent(item, `${source}.feedbackEvents[${index}]`)));
    const relations = fixture.relations === undefined
        ? undefined
        : expectArray(fixture.relations, `${source}.relations`).map((item, index) => (parseRetrievalRelation(item, `${source}.relations[${index}]`)));
    const atoms = fixture.atoms === undefined
        ? undefined
        : expectArray(fixture.atoms, `${source}.atoms`).map((item, index) => (parseAtom(item, `${source}.atoms[${index}]`)));
    const cases = expectArray(fixture.cases, `${source}.cases`).map((item, index) => (parseRetrievalCase(item, `${source}.cases[${index}]`)));
    ensureUnique(knowledge.map((item) => item.evalId), `${source}.knowledge[].evalId`);
    ensureUnique(cases.map((item) => item.id), `${source}.cases[].id`);
    return { name, project, knowledge, atoms, feedbackEvents, relations, cases };
}
function parseRetrievalKnowledge(value, path) {
    const item = expectRecord(value, path);
    return {
        evalId: expectString(item.evalId, `${path}.evalId`),
        project: optionalString(item.project, `${path}.project`),
        sourceType: expectString(item.sourceType, `${path}.sourceType`),
        sourceUri: expectString(item.sourceUri, `${path}.sourceUri`),
        sourceTitle: optionalString(item.sourceTitle, `${path}.sourceTitle`),
        itemType: expectString(item.itemType, `${path}.itemType`),
        title: expectString(item.title, `${path}.title`),
        summary: optionalString(item.summary, `${path}.summary`),
        content: expectString(item.content, `${path}.content`),
        trustLevel: optionalNumber(item.trustLevel, `${path}.trustLevel`),
        labels: item.labels,
        references: item.references,
        metadata: item.metadata,
        freshnessAt: optionalString(item.freshnessAt, `${path}.freshnessAt`),
        status: optionalString(item.status, `${path}.status`),
    };
}
function parseAtom(value, path) {
    const item = expectRecord(value, path);
    return {
        evalId: expectString(item.evalId, `${path}.evalId`),
        project: optionalString(item.project, `${path}.project`),
        claim: expectString(item.claim, `${path}.claim`),
        type: expectString(item.type, `${path}.type`),
        evidence: item.evidence,
        trigger: item.trigger,
        verification: item.verification,
        producedBy: optionalString(item.producedBy, `${path}.producedBy`),
        tier: optionalString(item.tier, `${path}.tier`),
        reuseCount: optionalNumber(item.reuseCount, `${path}.reuseCount`),
        lastReusedAt: optionalString(item.lastReusedAt, `${path}.lastReusedAt`),
        status: optionalString(item.status, `${path}.status`),
        scope: optionalString(item.scope, `${path}.scope`),
        teamId: optionalString(item.teamId, `${path}.teamId`),
    };
}
function parseRetrievalCase(value, path) {
    const item = expectRecord(value, path);
    return {
        id: expectString(item.id, `${path}.id`),
        prompt: expectString(item.prompt, `${path}.prompt`),
        project: optionalString(item.project, `${path}.project`),
        taskType: optionalString(item.taskType, `${path}.taskType`),
        files: optionalStringArray(item.files, `${path}.files`),
        symbols: optionalStringArray(item.symbols, `${path}.symbols`),
        errors: optionalStringArray(item.errors, `${path}.errors`),
        noiseTolerance: optionalString(item.noiseTolerance, `${path}.noiseTolerance`),
        tokenBudget: optionalNumber(item.tokenBudget, `${path}.tokenBudget`),
        expectedKnowledgeIds: optionalStringArray(item.expectedKnowledgeIds, `${path}.expectedKnowledgeIds`),
        expectedSelectedKnowledgeIds: optionalStringArray(item.expectedSelectedKnowledgeIds, `${path}.expectedSelectedKnowledgeIds`),
        unexpectedKnowledgeIds: optionalStringArray(item.unexpectedKnowledgeIds, `${path}.unexpectedKnowledgeIds`),
        rejectedKnowledgeIds: optionalStringArray(item.rejectedKnowledgeIds, `${path}.rejectedKnowledgeIds`),
        minConfidence: optionalNumber(item.minConfidence, `${path}.minConfidence`),
        expectedContextFitStatus: optionalString(item.expectedContextFitStatus, `${path}.expectedContextFitStatus`),
        minContextFitScore: optionalNumber(item.minContextFitScore, `${path}.minContextFitScore`),
        expectedClassification: parseExpectedClassification(item.expectedClassification, `${path}.expectedClassification`),
    };
}
function parseRetrievalFeedbackEvent(value, path) {
    const item = expectRecord(value, path);
    return {
        feedbackType: expectString(item.feedbackType, `${path}.feedbackType`),
        prompt: optionalString(item.prompt, `${path}.prompt`),
        project: optionalString(item.project, `${path}.project`),
        knowledgeIds: optionalStringArray(item.knowledgeIds, `${path}.knowledgeIds`),
        reason: optionalString(item.reason, `${path}.reason`),
        metadata: item.metadata,
        expectedKnowledgeGap: parseExpectedKnowledgeGap(item.expectedKnowledgeGap, `${path}.expectedKnowledgeGap`),
    };
}
function parseExpectedKnowledgeGap(value, path) {
    if (value === undefined)
        return undefined;
    const item = expectRecord(value, path);
    return {
        status: optionalString(item.status, `${path}.status`),
        promptIncludes: optionalString(item.promptIncludes, `${path}.promptIncludes`),
        reasonIncludes: optionalString(item.reasonIncludes, `${path}.reasonIncludes`),
        missingSignals: optionalStringArray(item.missingSignals, `${path}.missingSignals`),
        contextPackIdRequired: optionalBoolean(item.contextPackIdRequired, `${path}.contextPackIdRequired`),
    };
}
function parseRetrievalRelation(value, path) {
    const item = expectRecord(value, path);
    return {
        fromEvalId: expectString(item.fromEvalId, `${path}.fromEvalId`),
        relationType: expectString(item.relationType, `${path}.relationType`),
        targetKind: expectString(item.targetKind, `${path}.targetKind`),
        toEvalId: expectString(item.toEvalId, `${path}.toEvalId`),
        confidence: optionalNumber(item.confidence, `${path}.confidence`),
        inferred: item.inferred === undefined ? undefined : Boolean(item.inferred),
    };
}
function parseExpectedClassification(value, path) {
    if (value === undefined)
        return undefined;
    const item = expectRecord(value, path);
    return {
        files: optionalStringArray(item.files, `${path}.files`),
        symbols: optionalStringArray(item.symbols, `${path}.symbols`),
        errors: optionalStringArray(item.errors, `${path}.errors`),
        technologies: optionalStringArray(item.technologies, `${path}.technologies`),
        businessAreas: optionalStringArray(item.businessAreas, `${path}.businessAreas`),
        taskType: optionalString(item.taskType, `${path}.taskType`),
    };
}
// ---------------------------------------------------------------------------
// Context-mapping fixture
// ---------------------------------------------------------------------------
const VALID_TAXONS = [
    'nl_to_code',
    'code_to_code',
    'text_to_text_doc',
    'hybrid',
];
export async function loadContextMappingFixture(filePath) {
    return parseContextMappingFixture(await readJsonFile(filePath), filePath);
}
export function parseContextMappingFixture(value, source = 'context-mapping fixture') {
    const fixture = expectRecord(value, source);
    const name = expectString(fixture.name, `${source}.name`);
    const project = expectString(fixture.project, `${source}.project`);
    const knowledge = expectArray(fixture.knowledge, `${source}.knowledge`).map((item, index) => (parseContextMappingKnowledge(item, `${source}.knowledge[${index}]`)));
    const distractors = fixture.distractors === undefined
        ? undefined
        : expectArray(fixture.distractors, `${source}.distractors`).map((item, index) => (parseContextMappingKnowledge(item, `${source}.distractors[${index}]`)));
    const feedbackEvents = fixture.feedbackEvents === undefined
        ? undefined
        : expectArray(fixture.feedbackEvents, `${source}.feedbackEvents`).map((item, index) => (parseContextMappingFeedbackEvent(item, `${source}.feedbackEvents[${index}]`)));
    const relations = fixture.relations === undefined
        ? undefined
        : expectArray(fixture.relations, `${source}.relations`).map((item, index) => (parseContextMappingRelation(item, `${source}.relations[${index}]`)));
    const cases = expectArray(fixture.cases, `${source}.cases`).map((item, index) => (parseContextMappingCase(item, `${source}.cases[${index}]`)));
    ensureUnique(knowledge.map((item) => item.evalId), `${source}.knowledge[].evalId`);
    if (distractors) {
        ensureUnique(distractors.map((item) => item.evalId), `${source}.distractors[].evalId`);
    }
    ensureUnique(cases.map((item) => item.id), `${source}.cases[].id`);
    return { name, project, knowledge, distractors, feedbackEvents, relations, cases };
}
function parseContextMappingKnowledge(value, path) {
    const item = expectRecord(value, path);
    return {
        evalId: expectString(item.evalId, `${path}.evalId`),
        project: optionalString(item.project, `${path}.project`),
        sourceType: expectString(item.sourceType, `${path}.sourceType`),
        sourceUri: expectString(item.sourceUri, `${path}.sourceUri`),
        sourceTitle: optionalString(item.sourceTitle, `${path}.sourceTitle`),
        itemType: expectString(item.itemType, `${path}.itemType`),
        title: expectString(item.title, `${path}.title`),
        summary: optionalString(item.summary, `${path}.summary`),
        content: expectString(item.content, `${path}.content`),
        trustLevel: optionalNumber(item.trustLevel, `${path}.trustLevel`),
        labels: item.labels,
        references: item.references,
        metadata: item.metadata,
        freshnessAt: optionalString(item.freshnessAt, `${path}.freshnessAt`),
    };
}
function parseContextMappingFeedbackEvent(value, path) {
    const item = expectRecord(value, path);
    return {
        feedbackType: expectString(item.feedbackType, `${path}.feedbackType`),
        prompt: optionalString(item.prompt, `${path}.prompt`),
        project: optionalString(item.project, `${path}.project`),
        knowledgeIds: optionalStringArray(item.knowledgeIds, `${path}.knowledgeIds`),
        reason: optionalString(item.reason, `${path}.reason`),
        metadata: item.metadata,
    };
}
function parseContextMappingRelation(value, path) {
    const item = expectRecord(value, path);
    return {
        fromEvalId: expectString(item.fromEvalId, `${path}.fromEvalId`),
        relationType: expectString(item.relationType, `${path}.relationType`),
        targetKind: expectString(item.targetKind, `${path}.targetKind`),
        toEvalId: expectString(item.toEvalId, `${path}.toEvalId`),
        confidence: optionalNumber(item.confidence, `${path}.confidence`),
        inferred: item.inferred === undefined ? undefined : Boolean(item.inferred),
    };
}
function parseContextMappingCase(value, path) {
    const item = expectRecord(value, path);
    const taxonRaw = expectString(item.taxon, `${path}.taxon`);
    if (!VALID_TAXONS.includes(taxonRaw)) {
        throw new Error(`${path}.taxon must be one of: ${VALID_TAXONS.join(', ')} (got "${taxonRaw}").`);
    }
    return {
        id: expectString(item.id, `${path}.id`),
        prompt: expectString(item.prompt, `${path}.prompt`),
        taxon: taxonRaw,
        project: optionalString(item.project, `${path}.project`),
        taskType: optionalString(item.taskType, `${path}.taskType`),
        files: optionalStringArray(item.files, `${path}.files`),
        symbols: optionalStringArray(item.symbols, `${path}.symbols`),
        errors: optionalStringArray(item.errors, `${path}.errors`),
        tokenBudget: optionalNumber(item.tokenBudget, `${path}.tokenBudget`),
        expectedRelevantKnowledgeIds: optionalStringArray(item.expectedRelevantKnowledgeIds, `${path}.expectedRelevantKnowledgeIds`),
        directEvidenceKnowledgeIds: optionalStringArray(item.directEvidenceKnowledgeIds, `${path}.directEvidenceKnowledgeIds`),
        adjacentEvidenceKnowledgeIds: optionalStringArray(item.adjacentEvidenceKnowledgeIds, `${path}.adjacentEvidenceKnowledgeIds`),
        forbiddenKnowledgeIds: optionalStringArray(item.forbiddenKnowledgeIds, `${path}.forbiddenKnowledgeIds`),
        noiseDistractorIds: optionalStringArray(item.noiseDistractorIds, `${path}.noiseDistractorIds`),
        expectedEntities: parseContextMappingExpectedEntities(item.expectedEntities, `${path}.expectedEntities`),
        expectedFitStatus: optionalString(item.expectedFitStatus, `${path}.expectedFitStatus`),
    };
}
function parseContextMappingExpectedEntities(value, path) {
    if (value === undefined)
        return undefined;
    const item = expectRecord(value, path);
    return {
        files: optionalStringArray(item.files, `${path}.files`),
        symbols: optionalStringArray(item.symbols, `${path}.symbols`),
    };
}
// ---------------------------------------------------------------------------
// Knowledge-completeness fixture
// ---------------------------------------------------------------------------
export async function loadKnowledgeCompletenessFixture(filePath) {
    return parseKnowledgeCompletenessFixture(await readJsonFile(filePath), filePath);
}
export function parseKnowledgeCompletenessFixture(value, source = 'knowledge completeness fixture') {
    const fixture = expectRecord(value, source);
    const name = expectString(fixture.name, `${source}.name`);
    const project = expectString(fixture.project, `${source}.project`);
    const knowledge = fixture.knowledge === undefined
        ? undefined
        : expectArray(fixture.knowledge, `${source}.knowledge`).map((item, index) => (parseCompletenessKnowledge(item, `${source}.knowledge[${index}]`)));
    const cases = expectArray(fixture.cases, `${source}.cases`).map((item, index) => (parseCompletenessCase(item, `${source}.cases[${index}]`)));
    if (knowledge) {
        ensureUnique(knowledge.map((item) => item.evalId), `${source}.knowledge[].evalId`);
    }
    ensureUnique(cases.map((item) => item.id), `${source}.cases[].id`);
    return { name, project, knowledge, cases };
}
function parseCompletenessKnowledge(value, path) {
    const item = expectRecord(value, path);
    return {
        evalId: expectString(item.evalId, `${path}.evalId`),
        project: optionalString(item.project, `${path}.project`),
        sourceType: expectString(item.sourceType, `${path}.sourceType`),
        sourceUri: expectString(item.sourceUri, `${path}.sourceUri`),
        sourceTitle: optionalString(item.sourceTitle, `${path}.sourceTitle`),
        itemType: expectString(item.itemType, `${path}.itemType`),
        title: expectString(item.title, `${path}.title`),
        summary: optionalString(item.summary, `${path}.summary`),
        content: expectString(item.content, `${path}.content`),
        trustLevel: optionalNumber(item.trustLevel, `${path}.trustLevel`),
        labels: item.labels,
        references: item.references,
        metadata: item.metadata,
        freshnessAt: optionalString(item.freshnessAt, `${path}.freshnessAt`),
    };
}
function parseCompletenessCase(value, path) {
    const item = expectRecord(value, path);
    const requiredFacts = expectArray(item.requiredFacts, `${path}.requiredFacts`).map((fact, index) => (parseRequiredFact(fact, `${path}.requiredFacts[${index}]`)));
    const requiredSources = item.requiredSources === undefined
        ? undefined
        : expectArray(item.requiredSources, `${path}.requiredSources`).map((source, index) => (parseRequiredSource(source, `${path}.requiredSources[${index}]`)));
    const forbiddenItems = item.forbiddenItems === undefined
        ? undefined
        : expectArray(item.forbiddenItems, `${path}.forbiddenItems`).map((forbidden, index) => (parseForbiddenItem(forbidden, `${path}.forbiddenItems[${index}]`)));
    if (requiredFacts.length === 0 && (!requiredSources || requiredSources.length === 0)) {
        throw new Error(`${path} must define requiredFacts or requiredSources.`);
    }
    return {
        id: expectString(item.id, `${path}.id`),
        prompt: expectString(item.prompt, `${path}.prompt`),
        project: optionalString(item.project, `${path}.project`),
        taskType: optionalString(item.taskType, `${path}.taskType`),
        files: optionalStringArray(item.files, `${path}.files`),
        symbols: optionalStringArray(item.symbols, `${path}.symbols`),
        errors: optionalStringArray(item.errors, `${path}.errors`),
        tokenBudget: optionalNumber(item.tokenBudget, `${path}.tokenBudget`),
        modes: parseCompletenessModes(item.modes, `${path}.modes`),
        requiredFacts,
        requiredSources,
        forbiddenItems,
        minCompleteness: optionalRate(item.minCompleteness, `${path}.minCompleteness`),
        minSourceCoverage: optionalRate(item.minSourceCoverage, `${path}.minSourceCoverage`),
        maxNoiseRate: optionalRate(item.maxNoiseRate, `${path}.maxNoiseRate`),
        minKnowledgeGainScore: optionalScore(item.minKnowledgeGainScore, `${path}.minKnowledgeGainScore`),
    };
}
function parseRequiredFact(value, path) {
    const item = expectRecord(value, path);
    const terms = optionalStringArray(item.terms, `${path}.terms`);
    const sourceRefs = optionalStringArray(item.sourceRefs, `${path}.sourceRefs`);
    if ((!terms || terms.length === 0) && (!sourceRefs || sourceRefs.length === 0)) {
        throw new Error(`${path} must define terms or sourceRefs.`);
    }
    return {
        id: expectString(item.id, `${path}.id`),
        description: optionalString(item.description, `${path}.description`),
        weight: optionalPositiveNumber(item.weight, `${path}.weight`),
        terms,
        sourceRefs,
    };
}
function parseRequiredSource(value, path) {
    const item = expectRecord(value, path);
    return {
        type: expectRequiredSourceType(item.type, `${path}.type`),
        value: expectString(item.value, `${path}.value`),
        labelType: optionalString(item.labelType, `${path}.labelType`),
    };
}
function parseForbiddenItem(value, path) {
    const item = expectRecord(value, path);
    return {
        type: expectForbiddenItemType(item.type, `${path}.type`),
        value: expectString(item.value, `${path}.value`),
        labelType: optionalString(item.labelType, `${path}.labelType`),
    };
}
function parseCompletenessModes(value, path) {
    if (value === undefined)
        return undefined;
    return expectArray(value, path).map((mode, index) => {
        const parsed = expectString(mode, `${path}[${index}]`);
        if (parsed !== 'fixture' && parsed !== 'live') {
            throw new Error(`${path}[${index}] must be "fixture" or "live".`);
        }
        return parsed;
    });
}
function expectRequiredSourceType(value, path) {
    const parsed = expectString(value, path);
    if (!['file', 'symbol', 'label', 'knowledge', 'ref'].includes(parsed)) {
        throw new Error(`${path} must be file, symbol, label, knowledge, or ref.`);
    }
    return parsed;
}
function expectForbiddenItemType(value, path) {
    const parsed = expectString(value, path);
    if (!['title', 'id', 'label', 'ref', 'any'].includes(parsed)) {
        throw new Error(`${path} must be title, id, label, ref, or any.`);
    }
    return parsed;
}
