/**
 * notify-sweep command — scheduled daily/weekly digest delivery to team
 * Slack channels (D0055 Phase 4, M6).
 *
 * Serves the `daily` and `weekly` cadence channels that the immediate per-report
 * path (M5) does not. It queries a fixed-lookback window of reports (1 day for
 * `daily`, 7 for `weekly` — no per-channel watermark), runs each report through
 * the pure matcher to decide which cadence channels it belongs to (honoring the
 * channel's scope + events filters), builds a per-channel digest from that
 * channel's matched reports, and posts one roll-up per channel via the bot-token
 * Slack adapter.
 *
 * Per-channel scoping is the whole point of the sweep (the M5 `dispatchDigest`
 * primitive is cadence-only): a `repos`-scoped channel sees a roll-up of only
 * its repos' reports. The matcher is reused verbatim so scope/event/cadence
 * semantics live in one place (D0045) rather than being re-implemented against
 * the slim digest projection.
 *
 * Triggered by two crons in `.github/workflows/scheduled-eval.yml`. Without
 * `SLACK_BOT_TOKEN` the command is a no-op, mirroring the composition root's
 * `loadSinks`-style gate.
 *
 * @see packages/core/src/notifications/match.ts — the pure matcher
 * @see packages/eval/src/notifications/service.ts — dispatchScopedDigests
 * @see packages/eval/src/commands/weekly-digest.ts — the command pattern cloned
 */
import { Command } from "commander";
import { channelKey, matchReportToChannels, } from "../_vendor/ailf-core/index.js";
import { buildDigestSummary, } from "../schedules/digest.js";
/** Fixed lookback per cadence — no watermark (design doc decision). */
const DEFAULT_LOOKBACK_DAYS = {
    daily: 1,
    weekly: 7,
};
const MS_PER_DAY = 24 * 60 * 60 * 1000;
/**
 * Core sweep: window → match → per-channel digest → dispatch. Pure of I/O —
 * every side effect arrives through `deps`. Returns the resolved per-channel
 * roll-ups (and, when not a dry-run, their delivery outcomes).
 */
export async function runNotifySweep(deps, opts) {
    const log = deps.log ?? (() => { });
    const lookbackDays = opts.lookbackDays ?? DEFAULT_LOOKBACK_DAYS[opts.cadence];
    const now = deps.now();
    const since = new Date(now.getTime() - lookbackDays * MS_PER_DAY);
    const sinceIso = since.toISOString();
    const [teams, reports] = await Promise.all([
        deps.loadTeams(),
        deps.loadReports(sinceIso),
    ]);
    log(`  Window: last ${lookbackDays} day(s) since ${sinceIso} — ` +
        `${reports.length} report(s), ${teams.length} subscribed team(s)`);
    // Group windowed reports by the channel each one matches at this cadence.
    // `matchReportToChannels` applies the cadence, events, and scope filters and
    // dedups per report, so a `repos`-scoped channel only collects its repos'
    // reports. `reports` is chronological (oldest-first), so each group's report
    // list is ordered for the digest's first/last deltas.
    const groups = new Map();
    for (const report of reports) {
        for (const target of matchReportToChannels(report, teams, opts.cadence)) {
            const key = channelKey(target.channel);
            const group = groups.get(key) ?? { reports: [], target };
            group.reports.push(report);
            groups.set(key, group);
        }
    }
    // Build one digest per channel from its scoped subset.
    const items = [];
    const channels = [];
    for (const [key, group] of groups) {
        if (group.reports.length === 0)
            continue;
        const entries = group.reports.map(toDigestReportEntry);
        const digest = buildDigestSummary(entries, since, now);
        items.push({ digest, target: group.target });
        channels.push({
            channelKey: key,
            reportCount: group.reports.length,
            team: group.target.team.slug,
        });
        log(`  • ${key} (${group.target.team.slug}) ← ${group.reports.length} report(s)`);
    }
    const base = {
        cadence: opts.cadence,
        channels,
        sinceIso,
        windowReportCount: reports.length,
    };
    if (items.length === 0) {
        log("  Nothing to send — no channel matched a report in the window.");
        return { ...base, dispatched: [] };
    }
    if (opts.dryRun) {
        log(`  --dry-run: ${items.length} channel roll-up(s) computed, none sent.`);
        return { ...base, dispatched: [] };
    }
    const dispatched = await deps.dispatch(items, opts.cadence);
    return { ...base, dispatched };
}
/**
 * Project a full Report onto the slim `DigestReportEntry` the digest engine
 * aggregates. `buildDigestSummary` consumes only `scores` + `overallScore`
 * (plus chronological order); the remaining fields carry through for parity
 * with the weekly-digest projection.
 */
export function toDigestReportEntry(report) {
    return {
        completedAt: report.completedAt,
        durationMs: report.durationMs,
        mode: report.provenance.mode,
        overallScore: report.summary.overall.avgScore,
        reportId: report.id,
        scores: report.summary.scores.map((s) => ({
            feature: s.feature,
            totalScore: s.totalScore,
        })),
        sourceName: report.provenance.source.name,
        ...(report.tag !== undefined ? { tag: report.tag } : {}),
    };
}
/**
 * `report notify-sweep --cadence <daily|weekly>` — registered under the
 * `report` group beside `digest`. Wires the real adapters (Content Lake team
 * source, report store, Slack bot-token service) and delegates to
 * `runNotifySweep`.
 */
export function createNotifySweepCommand() {
    return new Command("notify-sweep")
        .description("Post a daily/weekly digest roll-up to team Slack channels at that cadence")
        .requiredOption("-c, --cadence <cadence>", "Delivery cadence to sweep: daily or weekly")
        .option("-n, --dry-run", "Compute and print roll-ups without posting to Slack", false)
        .option("--lookback <days>", "Override the lookback window in days (default: 1 for daily, 7 for weekly)", parseInt)
        .action(async (opts) => {
        const cadence = opts.cadence;
        if (cadence !== "daily" && cadence !== "weekly") {
            console.error(`  ✗ --cadence must be "daily" or "weekly" (got "${cadence}")`);
            process.exit(1);
        }
        console.log();
        console.log(`=== AILF Notify Sweep (${cadence}) ===`);
        console.log();
        const botToken = process.env.SLACK_BOT_TOKEN;
        if (!botToken && !opts.dryRun) {
            // No bot token → delivery is disabled, exactly as the composition
            // root gates `ctx.notifications`. Nothing to post; exit cleanly.
            console.log("  ℹ️  SLACK_BOT_TOKEN not set — notification delivery is disabled. Skipping.");
            return;
        }
        const { getAilfSanityClient } = await import("../sanity/client.js");
        const { ContentLakeTeamSubscriptionSource } = await import("../notifications/team-subscription-source.js");
        const { ReportStore } = await import("../report-store.js");
        const reportToken = process.env.AILF_REPORT_SANITY_API_TOKEN ??
            process.env.SANITY_API_TOKEN ??
            undefined;
        const client = getAilfSanityClient({
            ...(reportToken ? { token: reportToken } : {}),
        });
        const source = new ContentLakeTeamSubscriptionSource(client);
        const reportStore = new ReportStore({ client });
        // Only build the delivery service when we will actually post — a
        // dry-run (or a missing token, already handled) sends nothing.
        let dispatch = async () => [];
        if (!opts.dryRun && botToken) {
            const { createNotificationService } = await import("../notifications/index.js");
            const { resolveDashboardBaseUrl } = await import("../sinks/slack/dashboard-links.js");
            const service = createNotificationService({
                client,
                dashboardBaseUrl: resolveDashboardBaseUrl(),
                token: botToken,
            });
            dispatch = (items, c) => service.dispatchScopedDigests(items, c);
        }
        const result = await runNotifySweep({
            dispatch,
            loadReports: (sinceIso) => reportStore.listReportsSince(sinceIso),
            loadTeams: () => source.loadSubscribedTeams(),
            log: (line) => console.log(line),
            now: () => new Date(),
        }, {
            cadence,
            dryRun: opts.dryRun,
            ...(opts.lookback !== undefined
                ? { lookbackDays: opts.lookback }
                : {}),
        });
        if (result.dispatched.length > 0) {
            const tally = { delivered: 0, failed: 0, skipped: 0 };
            for (const r of result.dispatched)
                tally[r.outcome.status] += 1;
            console.log();
            console.log(`  Sent: ${tally.delivered} delivered, ${tally.skipped} skipped, ${tally.failed} failed`);
        }
        console.log();
    });
}
