/**
 * Per-event Block Kit template assemblers. Each composes the shared section
 * builders into a SlackMessage. Report templates are chosen by a severity
 * cascade and wrapped in a single colored attachment (the urgency bar). Pure;
 * no I/O, no env.
 */
import { deriveEvents } from "../match.js";
import { actionButton, actions, context, divider, header, linkButton, markdown, noiseArrow, overflow, runUrl, scoreBar, scoreDot, section, signedDelta, SLACK_ACK_ACTION_ID, trendsUrl, } from "./block-kit.js";
import { areaScoresSection, comparisonSection, errorsSection, failureModesSection, footerSection, overallSection, provenanceSection, recommendationsSection, reportActions, } from "./sections.js";
const COLOR = {
    completed: "#2eb67d",
    regressed: "#e01e5a",
    breached: "#e01e5a",
    errored: "#ecb22e",
    degraded: "#ecb22e",
};
/**
 * Pick the primary template by severity:
 * degraded > breached > errored > regressed > completed.
 */
export function selectReportTemplate(report) {
    const events = new Set(deriveEvents(report));
    if (events.has("eval.failed"))
        return "degraded";
    if (events.has("eval.threshold-breached"))
        return "breached";
    if (events.has("eval.tests-errored"))
        return "errored";
    if (events.has("eval.score-regressed"))
        return "regressed";
    return "completed";
}
function belowCriticalCallout(report) {
    const bc = report.summary.belowCritical;
    if (bc.length === 0)
        return [];
    const named = bc
        .map((a) => {
        const s = report.summary.scores.find((x) => x.feature === a);
        return `*${a}* \`${s ? Math.round(s.totalScore) : "?"}\``;
    })
        .join(", ");
    return [section(`:rotating_light: *Below critical threshold:* ${named}`)];
}
function completedBody(report, options) {
    const o = report.summary.overall;
    const trend = report.comparison
        ? `   ·   ${noiseArrow(report.comparison.deltas.overall, report.comparison.noiseThreshold)} ${signedDelta(Math.round(report.comparison.deltas.overall))} vs last run`
        : "";
    return [
        header(":white_check_mark: Literacy eval complete"),
        section(`*${report.title ?? report.provenance.source.name}* scored *${Math.round(o.avgScore)}* / 100  ${scoreDot(o.avgScore)}${trend}`),
        ...provenanceSection(report, options),
        ...areaScoresSection(report, options),
        ...recommendationsSection(report),
        footerSection(report),
        reportActions(report, options, {}),
    ];
}
export function regressedBody(report, options) {
    const c = report.comparison;
    const regNames = (c?.areas ?? [])
        .filter((a) => a.change === "regressed")
        .map((a) => `*${a.area}*`)
        .join(", ");
    return [
        header(":chart_with_downwards_trend: Score regressed — review before merge"),
        section(`:small_red_triangle_down: Overall *${signedDelta(Math.round(c?.deltas.overall ?? 0))}* (now *${Math.round(report.summary.overall.avgScore)}* / 100)${regNames ? ` — regressed in ${regNames}` : ""}`),
        ...comparisonSection(report),
        ...belowCriticalCallout(report),
        ...errorsSection(report),
        ...provenanceSection(report, options),
        ...areaScoresSection(report, options, "Current standing"),
        ...recommendationsSection(report),
        footerSection(report),
        reportActions(report, options, {
            primaryLabel: "View regression report",
            ackLabel: "Acknowledge regression",
            ackDanger: true,
            rerun: true,
        }),
    ];
}
function breachedBody(report, options) {
    const area = report.summary.belowCritical[0] ?? report.summary.lowestArea;
    const s = report.summary.scores.find((x) => x.feature === area);
    const detail = s
        ? `\nCeiling ${Math.round(s.ceilingScore)} · doc-coverage ${Math.round(s.docCoverage)} · doc-quality gap ${Math.round(s.docQualityGap)}`
        : "";
    return [
        header(":rotating_light: Critical literacy threshold breached"),
        section(`:red_circle: *${area}* fell to *${s ? Math.round(s.totalScore) : "?"}* / 100 — below the critical threshold.${detail}`),
        ...failureModesSection(report),
        ...errorsSection(report),
        ...recommendationsSection(report),
        ...areaScoresSection(report, options, "Other areas in this run"),
        ...provenanceSection(report, options),
        footerSection(report),
        reportActions(report, options, { ackDanger: true, rerun: true }),
    ];
}
function erroredBody(report, options) {
    const es = report.summary.errorSummary;
    const rate = es ? Math.round(es.errorRate * 100) : 0;
    return [
        header(":warning: Eval completed with errored tests"),
        section(`:warning: ${es ? `*${es.erroredTestCount}* of *${es.totalTestCount}*` : "Some"} tests errored (${rate}%) — scores below reflect only the tests that ran.`),
        ...errorsSection(report),
        ...areaScoresSection(report, options, "Scores for tests that ran"),
        ...recommendationsSection(report),
        ...provenanceSection(report, options),
        footerSection(report),
        reportActions(report, options, { ackDanger: true, rerun: true }),
    ];
}
function degradedBody(report, options) {
    const blocks = [header(":warning: Report published — degraded")];
    if (report.degraded) {
        blocks.push(section(report.degraded.detail));
        blocks.push(context(`Unavailable: ${report.degraded.missing.map((m) => `\`${m}\``).join(" · ")}${report.validity ? `   ·   Validity *${report.validity.status}*` : ""}`));
    }
    blocks.push(section("*Scores were still computed:*"));
    blocks.push(...areaScoresSection(report, options));
    blocks.push(...overallSection(report, options));
    blocks.push(...provenanceSection(report, options));
    blocks.push(footerSection(report));
    blocks.push(reportActions(report, options, { rerun: true }));
    return blocks;
}
const BODY = {
    completed: completedBody,
    regressed: regressedBody,
    breached: breachedBody,
    errored: erroredBody,
    degraded: degradedBody,
};
export function reportMessage(report, options) {
    const template = selectReportTemplate(report);
    const blocks = BODY[template](report, options);
    const overall = Math.round(report.summary.overall.avgScore);
    const es = report.summary.errorSummary;
    const fallback = {
        completed: `Literacy eval complete: ${report.provenance.source.name} scored ${overall}/100`,
        regressed: `Score regressed: ${report.provenance.source.name} now ${overall}/100`,
        breached: `Critical threshold breached: ${report.summary.belowCritical.join(", ")}`,
        errored: `Eval completed with errored tests: ${es ? `${es.erroredTestCount}/${es.totalTestCount}` : "some"} tests errored on ${report.provenance.source.name}`,
        degraded: `Report published (degraded): ${report.provenance.source.name}`,
    };
    return {
        text: fallback[template],
        attachments: [{ color: COLOR[template], blocks }],
    };
}
export function digestMessage(digest, cadence, options) {
    const cadenceLabel = cadence === "daily" ? "Daily" : "Weekly";
    const trendEmoji = digest.overallTrend === "improving"
        ? ":chart_with_upwards_trend:"
        : digest.overallTrend === "regressing"
            ? ":chart_with_downwards_trend:"
            : "▬";
    const blocks = [
        header(`:calendar: ${cadenceLabel} AI literacy digest`),
        context(`${digest.lookbackStart.slice(0, 10)} → ${digest.lookbackEnd.slice(0, 10)}  ·  ${digest.reportCount} reports`),
        section(`${trendEmoji}  *Overall ${Math.round(digest.overallLatest)}* / 100   ${noiseArrow(digest.overallDelta, 1)} *${signedDelta(Math.round(digest.overallDelta))}* this ${cadence === "daily" ? "day" : "week"}  ·  _${digest.overallTrend}_`),
        divider(),
    ];
    if (digest.areaTrends.length > 0) {
        const lines = digest.areaTrends
            .map((t) => `${scoreDot(t.lastScore)} *${t.area}*  \`${scoreBar(t.lastScore)}\` *${Math.round(t.lastScore)}*   ${noiseArrow(t.scoreDelta, 1)} ${signedDelta(Math.round(t.scoreDelta))}  _(${t.reportCount} runs)_`)
            .join("\n");
        blocks.push(section(lines));
    }
    // Cost reflects fresh spend only: `digest.totalCost` already excludes
    // reports served from the remote cache (their cost was inherited from a
    // source run, not spent this window). When any were excluded, footnote the
    // count so the figure is not misread as the window's total spend
    // (W-cache-provenance-notifications).
    const costSegment = digest.totalCost !== undefined
        ? `   ·   :money_with_wings: $${digest.totalCost.toFixed(2)} spent${digest.cachedReportCount ? ` (excl. ${digest.cachedReportCount} cache-served)` : ""}`
        : "";
    blocks.push(context(`:chart_with_upwards_trend: improved ${digest.improved.join(", ") || "—"}   ·   :chart_with_downwards_trend: regressed ${digest.regressed.join(", ") || "—"}   ·   ▬ stable ${digest.stable.join(", ") || "—"}${costSegment}`));
    const els = [
        linkButton("Open dashboard", trendsUrl(options.dashboardBaseUrl), {
            style: "primary",
        }),
    ];
    if (options.actions?.overflow === true) {
        els.push(overflow([
            { text: "Switch to daily", value: "daily" },
            { text: "Pause digest", value: "pause" },
        ], "overflow"));
    }
    blocks.push(actions(els));
    return {
        text: `${cadenceLabel} AI literacy digest: ${Math.round(digest.overallLatest)} (${signedDelta(Math.round(digest.overallDelta))})`,
        blocks,
    };
}
export function adminAlertMessage(alert, options) {
    const fields = [
        alert.areas?.length ? `*Areas*\n${alert.areas.join(", ")}` : null,
        alert.tasks?.length ? `*Tasks*\n${alert.tasks.join(", ")}` : null,
        alert.dataset ? `*Dataset*\n${alert.dataset}` : null,
        alert.requestedBy ? `*Requested by*\n${alert.requestedBy}` : null,
        alert.failedAt ? `*Failed at*\n${alert.failedAt}` : null,
        `*Request ID*\n\`${alert.requestId}\``,
    ].filter((s) => s !== null);
    const blocks = [
        header(":rotating_light: Eval run failed"),
        section(`Request \`${alert.requestId}\` failed *before publishing a report*.`),
        section(`*Error*\n\`\`\`${alert.error}\`\`\``),
        {
            type: "section",
            fields: fields.map((f) => ({ type: "mrkdwn", text: f })),
        },
        context(":gear: Gateway admin alert  ·  not a team subscription  ·  routed from `ailf.adminAlertConfig`"),
    ];
    const url = alert.dashboardUrl ?? runUrl(options.dashboardBaseUrl, alert.requestId);
    const els = [
        linkButton("Open request", url, { style: "primary" }),
    ];
    if (options.actions?.retry === true) {
        els.push(actionButton("Retry run", `retry_${alert.requestId}`, {
            actionId: "retry",
            confirm: {
                title: { type: "plain_text", text: "Retry this run?" },
                text: {
                    type: "mrkdwn",
                    text: "Re-submits the failed eval request to the gateway. A new run ID is created.",
                },
                confirm: { type: "plain_text", text: "Retry run" },
                deny: { type: "plain_text", text: "Cancel" },
            },
        }));
    }
    if (options.actions?.acknowledge !== false) {
        els.push(actionButton("Acknowledge", alert.requestId, {
            actionId: SLACK_ACK_ACTION_ID,
            style: "danger",
            confirm: {
                title: { type: "plain_text", text: "Acknowledge failure?" },
                text: {
                    type: "mrkdwn",
                    text: "Marks this run failure as seen and records an ack on the request.",
                },
                confirm: { type: "plain_text", text: "Acknowledge" },
                deny: { type: "plain_text", text: "Cancel" },
                style: "danger",
            },
        }));
    }
    if (options.actions?.overflow === true) {
        els.push(overflow([
            { text: "Copy request ID", value: "copy_req" },
            { text: "View gateway logs", value: "logs" },
        ], "overflow"));
    }
    blocks.push(actions(els));
    return {
        text: `Eval run failed: ${alert.requestId} — ${alert.error}`,
        attachments: [{ color: "#e01e5a", blocks }],
    };
}
/**
 * The `markdown` block's cumulative text caps at 12,000 chars per message;
 * bound the answer body under that with headroom for the surrounding blocks.
 */
const MARKDOWN_BLOCK_MAX = 11_800;
/** Notification/fallback `text` bounded well under Slack's text-field cap. */
const FALLBACK_TEXT_MAX = 3_000;
/**
 * Render an answer body as native markdown block(s). One block, unless the body
 * exceeds MARKDOWN_BLOCK_MAX — then truncate at a line boundary and add a
 * context note (the full answer stays readable via GET /v1/jobs/{jobId}).
 */
function answerBlocks(body) {
    if (body.length <= MARKDOWN_BLOCK_MAX)
        return [markdown(body)];
    const cut = body.lastIndexOf("\n", MARKDOWN_BLOCK_MAX);
    return [
        markdown(body.slice(0, cut > 0 ? cut : MARKDOWN_BLOCK_MAX)),
        context(":scissors: _Answer truncated to fit Slack's display limit._"),
    ];
}
export function askAnswerMessage(outcome, options) {
    if (outcome.status === "completed") {
        const hasAnswer = outcome.answer != null && outcome.answer.trim().length > 0;
        const body = hasAnswer
            ? outcome.answer
            : "I finished, but produced an empty answer. Please try rephrasing the question.";
        const blocks = [...answerBlocks(body)];
        if (hasAnswer && options.actions?.feedback === true) {
            const feedbackEls = [
                actionButton(":thumbsup: Helpful", "helpful", {
                    actionId: "feedback",
                }),
                actionButton(":thumbsdown: Not helpful", "not_helpful", {
                    actionId: "feedback",
                }),
            ];
            blocks.push(actions(feedbackEls));
        }
        return {
            text: body.length > FALLBACK_TEXT_MAX
                ? body.slice(0, FALLBACK_TEXT_MAX)
                : body,
            blocks,
            responseType: "in_channel",
        };
    }
    const reason = outcome.error ?? outcome.status;
    const note = `:warning: I couldn't answer that one.${reason ? ` (${reason})` : ""}`;
    return {
        text: note,
        blocks: [
            section(note),
            context(":point_right: Try rephrasing, or narrow to a specific product area."),
        ],
        responseType: "ephemeral",
    };
}
