/**
 * publish command — manually publish a local evaluation report to the
 * Sanity Content Lake without re-running the pipeline.
 *
 * Reads a score-summary.json (defaulting to results/latest/score-summary.json),
 * builds provenance, writes the report to Sanity, and fans out to configured
 * sinks — exactly the same as the publish step in `ailf run`, but
 * standalone.
 *
 * Uses createAppContext() (composition root) for all infrastructure access.
 *
 * @example
 *   ailf publish                                        # default path
 *   ailf publish ./my-results/score-summary.json        # custom path
 *   ailf publish --tag "manual-2026-03-13"              # with a label
 *   ailf publish --dry-run                              # preview without writing
 *
 * @see packages/eval/src/composition-root.ts
 * @see docs/design-docs/report-store/architecture.md
 */
import { existsSync, readFileSync } from "fs";
import { dirname, resolve } from "path";
import { fileURLToPath } from "url";
import { Command } from "commander";
import { createAppContext } from "../composition-root.js";
import { addOutputDirOption } from "./shared/options.js";
import { getCallerCwd, resolveOutputDir } from "./shared/resolve-output-dir.js";
import { buildProvenance, } from "../pipeline/provenance.js";
import { generateReportTitle } from "../pipeline/report-title.js";
import { buildSlimReportSummary } from "../_vendor/ailf-core/index.js";
import { generateReportId, } from "../report-store.js";
import { withRetry } from "../sinks/retry.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = resolve(__dirname, "..", "..");
export function createPublishCommand() {
    const cmd = new Command("publish")
        .description("Publish a local evaluation report to the Sanity Content Lake")
        .argument("[summary-path]", "Path to score-summary.json")
        .option("-t, --tag <tag>", "Label for the published report")
        .option("-n, --dry-run", "Preview the report without writing to Sanity or sinks", false)
        .action(async (summaryPath, opts) => {
        const outputDir = resolveOutputDir(opts.outputDir);
        const effectivePath = summaryPath ?? resolve(outputDir, "score-summary.json");
        await runPublishCommand(effectivePath, outputDir, opts);
    });
    addOutputDirOption(cmd);
    return cmd;
}
// ---------------------------------------------------------------------------
// Provenance builder (from score summary, not full pipeline context)
// ---------------------------------------------------------------------------
/**
 * Build a ProvenanceInput from a ScoreSummary for manual publish.
 *
 * When publishing outside the pipeline, we reconstruct what we can from
 * the summary metadata and environment. Some fields (contextHash,
 * promptfooUrl) are not available for manual publishes.
 */
function buildProvenanceFromSummary(summary, runId) {
    const areas = summary.scores.map((s) => s.feature);
    const mode = (process.env.EVAL_MODE ?? "literacy");
    const source = {
        baseUrl: summary.source?.baseUrl ?? "https://www.sanity.io/docs",
        dataset: summary.source?.dataset ?? process.env.SANITY_DATASET ?? "next",
        documentIds: [],
        llmsTxt: (summary.source?.baseUrl ?? "https://www.sanity.io/docs") + "/llms.txt",
        name: summary.source?.name ?? "production",
        perspective: summary.source?.perspective ??
            process.env.SANITY_PERSPECTIVE ??
            undefined,
        priorityDomain: "sanity.io",
        projectId: summary.source?.projectId ?? process.env.SANITY_PROJECT_ID ?? "3do82whm",
        studioOrigin: "https://admin.sanity.io",
        urls: [],
    };
    return {
        areas,
        mode,
        rootDir: ROOT,
        runId,
        source,
    };
}
// ---------------------------------------------------------------------------
// Command implementation
// ---------------------------------------------------------------------------
async function runPublishCommand(summaryPath, outputDir, opts) {
    // Wire up infrastructure via composition root
    const ctx = createAppContext({
        compareEnabled: false,
        gapAnalysisEnabled: false,
        mode: "literacy",
        noAutoScope: false,
        noCache: true,
        noRemoteCache: true,
        outputDir,
        publishEnabled: true,
        publishTag: opts.tag,
        rootDir: ROOT,
        searchMode: "open",
        skipEval: true,
        skipFetch: true,
        remote: false,
        apiUrl: "https://ailf-api.sanity.build",
    });
    const store = ctx.reportStore;
    const sinks = (ctx.sinks ?? []);
    console.log();
    console.log("=== Publish Report ===");
    console.log();
    // -----------------------------------------------------------------------
    // 1. Resolve and read the score summary
    // -----------------------------------------------------------------------
    const resolvedPath = resolve(getCallerCwd(), summaryPath);
    if (!existsSync(resolvedPath)) {
        console.error(`  ✖ File not found: ${resolvedPath}`);
        console.error();
        console.error("  Hint: Run `ailf run` first to generate results,");
        console.error("  or provide a path to an existing score-summary.json.");
        process.exit(1);
    }
    let summary;
    try {
        summary = JSON.parse(readFileSync(resolvedPath, "utf-8"));
    }
    catch (err) {
        console.error(`  ✖ Failed to parse ${resolvedPath}: ${err instanceof Error ? err.message : String(err)}`);
        process.exit(1);
    }
    // Basic validation — a ScoreSummary must have scores
    if (!summary.scores || !Array.isArray(summary.scores)) {
        console.error("  ✖ Invalid score-summary.json: missing `scores` array.");
        process.exit(1);
    }
    const areaCount = summary.scores.length;
    const overallScore = summary.overall.avgScore;
    console.log(`  File:     ${resolvedPath}`);
    console.log(`  Areas:    ${areaCount} (${summary.scores.map((s) => s.feature).join(", ")})`);
    console.log(`  Overall:  ${overallScore.toFixed(1)}`);
    if (opts.tag) {
        console.log(`  Tag:      ${opts.tag}`);
    }
    console.log(`  Mode:     ${opts.dryRun ? "dry run (no writes)" : "live"}`);
    console.log();
    // -----------------------------------------------------------------------
    // 2. Build provenance
    // -----------------------------------------------------------------------
    const provenanceInput = buildProvenanceFromSummary(summary, ctx.runId);
    const provenance = buildProvenance(provenanceInput);
    // -----------------------------------------------------------------------
    // 3. Create report
    // -----------------------------------------------------------------------
    const now = new Date().toISOString();
    if (!store) {
        console.warn("  ⚠️  No Sanity API token found. Set AILF_REPORT_SANITY_API_TOKEN");
        console.warn("  or SANITY_API_TOKEN in your .env file.");
        if (!opts.dryRun) {
            console.warn("  Cannot publish without a token. Exiting.");
            process.exit(1);
        }
    }
    // Auto-compare against most recent comparable baseline
    const autoCompareResult = opts.dryRun || !store
        ? null
        : await store.autoCompare(summary, provenance, now);
    const comparison = autoCompareResult?.comparison ?? null;
    // Record which report we compared against in lineage
    if (autoCompareResult) {
        provenance.lineage = {
            ...provenance.lineage,
            comparedAgainst: autoCompareResult.baselineReportId,
        };
    }
    const reportId = generateReportId();
    const title = generateReportTitle({ provenance });
    // W0051 Slice 3: slim the summary at publish time.
    const slimSummary = buildSlimReportSummary(summary, provenance.mode);
    const report = {
        comparison: comparison ?? undefined,
        completedAt: now,
        durationMs: 0, // manual publish — no pipeline duration
        id: reportId,
        provenance,
        summary: slimSummary,
        tag: opts.tag,
        title,
    };
    // -----------------------------------------------------------------------
    // 4. Dry run — print preview and exit
    // -----------------------------------------------------------------------
    if (opts.dryRun) {
        console.log("  --- Report Preview ---");
        console.log(`  ID:          ${reportId}`);
        console.log(`  Completed:   ${now}`);
        console.log(`  Mode:        ${provenance.mode}`);
        console.log(`  Source:      ${provenance.source.name}`);
        console.log(`  Models:      ${provenance.models.map((m) => m.id).join(", ")}`);
        console.log(`  Grader:      ${provenance.graderModel}`);
        console.log(`  Trigger:     ${provenance.trigger.type}`);
        if (opts.tag) {
            console.log(`  Tag:         ${opts.tag}`);
        }
        console.log();
        if (sinks.length > 0) {
            console.log(`  Sinks:       ${sinks.map((s) => s.name).join(", ")}`);
        }
        else {
            console.log("  Sinks:       none configured");
        }
        console.log();
        console.log("  ✔ Dry run complete. No data was written.");
        return;
    }
    // -----------------------------------------------------------------------
    // 5. Write to Sanity (system of record)
    // -----------------------------------------------------------------------
    console.log("  Writing to Sanity Content Lake...");
    const sanityResult = store ? await store.write(report) : null;
    if (sanityResult) {
        console.log(`  ✅ Report written: ${sanityResult}`);
    }
    else {
        console.warn("  ⚠️  Sanity write failed (see warnings above)");
    }
    // -----------------------------------------------------------------------
    // 6. Auto-comparison result
    // -----------------------------------------------------------------------
    if (comparison) {
        const delta = comparison.deltas.overall;
        const sign = delta >= 0 ? "+" : "";
        console.log(`  📊 Auto-compared vs baseline: ${sign}${delta.toFixed(1)}`);
    }
    // -----------------------------------------------------------------------
    // 7. Fan out to sinks
    // -----------------------------------------------------------------------
    if (sinks.length > 0) {
        console.log();
        console.log(`  Delivering to ${sinks.length} sink(s)...`);
        const settled = await Promise.allSettled(sinks.map(async (sink) => {
            const result = await withRetry(() => sink.publish(report));
            return { name: sink.name, result };
        }));
        for (const outcome of settled) {
            if (outcome.status === "fulfilled") {
                const { name, result } = outcome.value;
                if (result.status === "failed") {
                    console.warn(`  ⚠️  Sink ${name} failed: ${result.error}`);
                }
                else if (result.status === "skipped") {
                    console.log(`  ⏭️  Sink ${name} skipped: ${result.reason}`);
                }
                else {
                    console.log(`  ✅ Sink ${name} delivered${result.detail ? ` (${result.detail})` : ""}`);
                }
            }
            else {
                const error = outcome.reason instanceof Error
                    ? outcome.reason.message
                    : String(outcome.reason);
                console.warn(`  ⚠️  Sink delivery error: ${error}`);
            }
        }
    }
    // -----------------------------------------------------------------------
    // 8. Summary
    // -----------------------------------------------------------------------
    console.log();
    if (sanityResult) {
        console.log(`  ✔ Published report ${reportId}`);
    }
    else {
        console.log("  ✖ Report was not persisted to Sanity.");
        process.exit(1);
    }
}
