/**
 * Fake LLMClient for tests.
 *
 * Returns canned responses in insertion order and records each call so
 * consumers' tests can assert on prompts, models, and telemetry context.
 *
 * Exported alongside the real adapters so consuming-feature tests can
 * stub the LLM cleanly without a network round-trip.
 */
export class FakeLLMClient {
    calls = [];
    completeQueue;
    structuredQueue;
    /**
     * Per-cardId keyed responses. A single-value entry is returned on every
     * call for that cardId (repeated calls always get the same response). An
     * array-value entry is consumed in order; once exhausted, calls for that
     * cardId fall back to the FIFO structuredQueue.
     *
     * This is the substrate Plan 07's 17-fixture eval matrix uses to wire
     * deterministic responses to specific LLM cards.
     */
    keyedResponses;
    constructor(args = {}) {
        this.completeQueue = [...(args.completeResponses ?? [])];
        this.structuredQueue = [...(args.structuredResponses ?? [])];
        // Deep-copy arrays so the caller's fixture data is not mutated.
        const keyed = {};
        for (const [key, val] of Object.entries(args.keyedResponses ?? {})) {
            keyed[key] = Array.isArray(val) ? [...val] : val;
        }
        this.keyedResponses = keyed;
    }
    async complete(args) {
        this.calls.push({
            kind: "complete",
            model: args.model,
            prompt: args.prompt,
            ...(args.context ? { context: args.context } : {}),
        });
        const next = this.completeQueue.shift();
        if (!next) {
            throw new Error("FakeLLMClient: no more queued complete responses (call exceeded queue)");
        }
        return {
            text: next.text,
            usage: {
                promptTokens: next.promptTokens ?? 0,
                completionTokens: next.completionTokens ?? 0,
            },
            cost: next.cost ?? 0,
            model: args.model,
        };
    }
    async completeStructured(args) {
        // Record every call first so test assertions on this.calls are never
        // affected by which branch (keyed vs FIFO) handles the response.
        this.calls.push({
            kind: "completeStructured",
            model: args.model,
            prompt: args.prompt,
            ...(args.context ? { context: args.context } : {}),
        });
        let next;
        const cardId = args.context?.cardId;
        if (cardId !== undefined && cardId in this.keyedResponses) {
            const entry = this.keyedResponses[cardId];
            if (Array.isArray(entry)) {
                // Array-value: consume one entry per call. When exhausted, fall
                // through to the FIFO queue below.
                if (entry.length > 0) {
                    next = entry.shift();
                }
            }
            else {
                // Single-value: return the same response on every call.
                next = entry;
            }
        }
        if (next === undefined) {
            // FIFO fallback (existing behavior)
            next = this.structuredQueue.shift();
        }
        if (!next) {
            throw new Error("FakeLLMClient: no more queued structured responses (call exceeded queue)");
        }
        // Run through the consumer-supplied schema so consumers exercise the
        // parse-don't-validate path even with the fake.
        const value = args.schema.parse(next.value);
        return {
            value,
            usage: {
                promptTokens: next.promptTokens ?? 0,
                completionTokens: next.completionTokens ?? 0,
            },
            cost: next.cost ?? 0,
            model: args.model,
        };
    }
}
