/**
 * schedules/digest.ts
 *
 * Weekly digest engine — aggregates evaluation reports from a time window
 * and computes trend summaries for notification delivery.
 *
 * The digest answers: "How did our AI literacy scores trend this week?"
 *
 * Data flow:
 * 1. Query Sanity for all reports in the lookback window
 * 2. Group by feature area
 * 3. Compute trend direction and magnitude per area
 * 4. Identify regressions, improvements, and stable areas
 * 5. Return a structured DigestSummary for formatting
 *
 * @see docs/design-docs/report-store/notifications.md
 * @see docs/design-docs/report-store/implementation.md — Phase 5
 */
import { NOISE_THRESHOLD } from "../_vendor/ailf-shared/index.js";
import { getSanityClient } from "../sanity/client.js";
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const REPORT_TYPE = "ailf.report";
// ---------------------------------------------------------------------------
// GROQ query
// ---------------------------------------------------------------------------
const DIGEST_QUERY = `*[_type == $type && completedAt >= $since] | order(completedAt asc) {
  "reportId": reportId,
  "completedAt": completedAt,
  "mode": provenance.mode,
  "sourceName": provenance.source.name,
  "overallScore": summary.overall.avgScore,
  "durationMs": durationMs,
  "tag": tag,
  "scores": summary.scores[] {
    "feature": feature,
    "totalScore": totalScore
  }
}`;
/**
 * Build a digest summary from a chronologically-ordered list of reports.
 *
 * Pure function — no I/O, fully testable.
 */
export function buildDigestSummary(reports, lookbackStart, lookbackEnd, noiseThreshold = NOISE_THRESHOLD) {
    // Collect all unique areas across all reports
    const areaSet = new Set();
    for (const report of reports) {
        for (const score of report.scores) {
            areaSet.add(score.feature);
        }
    }
    // Build per-area trends
    const areaTrends = [];
    for (const area of [...areaSet].sort()) {
        const areaScores = reports
            .map((r) => {
            const s = r.scores.find((sc) => sc.feature === area);
            return s ? s.totalScore : null;
        })
            .filter((s) => s !== null);
        if (areaScores.length === 0)
            continue;
        const firstScore = areaScores[0];
        const lastScore = areaScores[areaScores.length - 1];
        const delta = lastScore - firstScore;
        const trend = classifyTrend(delta, noiseThreshold);
        areaTrends.push({
            area,
            firstScore,
            lastScore,
            reportCount: areaScores.length,
            scoreDelta: delta,
            trend,
        });
    }
    // Overall trend from report-level overall scores
    const firstOverall = reports[0].overallScore;
    const lastOverall = reports[reports.length - 1].overallScore;
    const overallDelta = lastOverall - firstOverall;
    const overallTrend = classifyTrend(overallDelta, noiseThreshold);
    // Classify areas
    const improved = areaTrends
        .filter((t) => t.trend === "improving")
        .map((t) => t.area);
    const regressed = areaTrends
        .filter((t) => t.trend === "regressing")
        .map((t) => t.area);
    const stable = areaTrends
        .filter((t) => t.trend === "stable")
        .map((t) => t.area);
    return {
        areaTrends,
        improved,
        lookbackEnd: lookbackEnd.toISOString(),
        lookbackStart: lookbackStart.toISOString(),
        overallDelta,
        overallLatest: lastOverall,
        overallTrend,
        regressed,
        reportCount: reports.length,
        stable,
    };
}
// ---------------------------------------------------------------------------
// Trend computation
// ---------------------------------------------------------------------------
/**
 * Classify a score delta into a trend direction.
 */
export function classifyTrend(delta, threshold = NOISE_THRESHOLD) {
    if (delta > threshold)
        return "improving";
    if (delta < -threshold)
        return "regressing";
    return "stable";
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
 * Generate a digest summary from recent evaluation reports.
 *
 * Queries the Sanity Content Lake for all reports within the lookback
 * window and computes trend analysis per area.
 *
 * @returns DigestSummary, or null if no reports found or on error
 */
export async function generateDigest(options = {}) {
    const lookbackDays = options.lookbackDays ?? 7;
    const threshold = options.noiseThreshold ?? NOISE_THRESHOLD;
    const client = options.client ??
        getSanityClient({
            ...(options.dataset ? { dataset: options.dataset } : {}),
            ...(options.projectId ? { projectId: options.projectId } : {}),
            ...(options.token ? { token: options.token } : {}),
        });
    const now = new Date();
    const since = new Date(now.getTime() - lookbackDays * 24 * 60 * 60 * 1000);
    try {
        const reports = await client.fetch(DIGEST_QUERY, {
            since: since.toISOString(),
            type: REPORT_TYPE,
        });
        if (!reports || reports.length === 0) {
            console.log("  ℹ️  No reports found in the lookback window");
            return null;
        }
        return buildDigestSummary(reports, since, now, threshold);
    }
    catch (error) {
        console.warn("  ⚠️  Failed to fetch digest reports:", error instanceof Error ? error.message : String(error));
        return null;
    }
}
