/**
 * top-recommendations card — LLM-driven actionable suggestion generator.
 *
 * Model: claude-opus-4-6 (high-stakes per AI-SPEC §4 model routing)
 * Version: top-recommendations@0.1.0
 *
 * Mitigations:
 * - failure-mode #2: few-shot examples in system prompt; body ≥40 chars
 * - failure-mode #5: docSlug refined against report manifest allow-list
 *
 * Schema is in the D0045 trust-boundary scan root; `satisfies` clause is
 * mandatory (cards/ is a SCAN_ROOT in check-trust-boundary-satisfies.ts).
 *
 * @see .planning/phases/05-diagnosis-engine-cli-llm-cards/05-AI-SPEC.md §3
 * @see .planning/phases/05-diagnosis-engine-cli-llm-cards/05-AI-SPEC.md §4
 */
import { z } from "zod";
import { modelId as mkModelId } from "../../../ports/llm-client.js";
import { buildTopRecommendationsPrompt, buildDocSlugAllowList, } from "../prompt-builders.js";
// ---------------------------------------------------------------------------
// Body schema (D0045 trust boundary — satisfies required)
// ---------------------------------------------------------------------------
/**
 * Module-level schema: static shape check only.
 * Per-call: an additive `.refine()` over the allow-list (built inside the
 * generator so it closes over the runtime `report`).
 *
 * AI-SPEC §3 Pitfall 1: per-call schemas are LOCAL to the generator,
 * NOT at module scope.
 */
export const TopRecommendationsBodySchema = z.object({
    summary: z.string().min(1).max(500),
    suggestions: z
        .array(z.object({
        title: z.string().min(1).max(200),
        body: z
            .string()
            .min(40, "suggestion.body must be ≥40 characters")
            .refine((b) => /`[^`]+`/.test(b), "suggestion.body must contain at least one backtick-delimited artifact"),
        priority: z.enum(["high", "medium", "low"]),
        docSlug: z.string().min(1),
        sectionHeading: z.string().nullable(),
    }))
        .min(1)
        .max(5),
});
// ---------------------------------------------------------------------------
// Generator
// ---------------------------------------------------------------------------
const CARD_MODEL = mkModelId("anthropic:claude-opus-4-6");
export const generateTopRecommendations = async (report, ctx) => {
    // C1: no LLM → missing
    if (!ctx.llm) {
        return {
            status: "missing",
            cardType: "top-recommendations",
            reason: "no LLMClient wired",
        };
    }
    // Build allow-list from the runtime report
    const allowList = buildDocSlugAllowList(report);
    // Short-circuit BEFORE the LLM call when the allow-list is empty — the shape
    // a degraded run produces (enrichment never wrote `documentManifest`, and
    // per-score `documents[]` carry no slugs). With an empty allow-list the
    // system prompt instructs the model to omit every recommendation, so it
    // returns `suggestions: []` and trips the `.min(1)` schema, surfacing as a
    // degraded card with a raw ZodError. Degrade gracefully to "missing"
    // instead — same structural seam the sibling attribution cards use.
    if (allowList.size === 0) {
        return {
            status: "missing",
            cardType: "top-recommendations",
            reason: "no doc-slug allow-list for this run (enrichment incomplete)",
        };
    }
    // Per-call schema: additive docSlug allow-list refine (AI-SPEC §3 Pitfall 1)
    const PerCallSchema = z.object({
        summary: z.string().min(1).max(500),
        suggestions: z
            .array(z.object({
            title: z.string().min(1).max(200),
            body: z
                .string()
                .min(40, "suggestion.body must be ≥40 characters")
                .refine((b) => /`[^`]+`/.test(b), "suggestion.body must contain at least one backtick-delimited artifact"),
            priority: z.enum(["high", "medium", "low"]),
            docSlug: z
                .string()
                .min(1)
                .refine((slug) => allowList.has(slug), {
                message: "suggestion.docSlug is not in the report document manifest allow-list",
            }),
            sectionHeading: z.string().nullable(),
        }))
            .min(1)
            .max(5),
    });
    const prompt = buildTopRecommendationsPrompt(report, allowList);
    // Destructure `cost` and `model` from the LLMClient return —
    // already provided per llm-client.ts:139-144, previously discarded.
    const { value, usage, cost, model } = await ctx.llm.completeStructured({
        model: CARD_MODEL,
        prompt: `${prompt.system}\n\n${prompt.user}`,
        schema: PerCallSchema,
        temperature: 0.1,
        maxTokens: 2000,
        context: {
            feature: "diagnosis",
            runId: ctx.runId,
            cardId: "top-recommendations",
        },
    });
    return {
        status: "ready",
        cardType: "top-recommendations",
        body: value,
        meta: {
            cardVersion: "top-recommendations@0.1.0",
            tokenUsage: { input: usage.promptTokens, output: usage.completionTokens },
            generatedAt: new Date().toISOString(),
            cost,
            model,
        },
    };
};
