/**
 * notifications/slack-adapter.ts
 *
 * SlackNotificationAdapter — the `NotificationChannelAdapter` for
 * `type: "slack"`. Posts notification content to arbitrary team channels via
 * the @AILF App bot token and `chat.postMessage`, reusing the Block Kit
 * formatters the webhook `SlackSink` uses. Unlike that sink (one channel bound
 * to a single `SLACK_WEBHOOK_URL`), this targets any channel the bot has been
 * invited to.
 *
 * `chat.postMessage` returns HTTP 200 even on logical failures, carrying the
 * real status in the body's `ok` / `error` fields. A `not_in_channel` error
 * maps to a `skipped` outcome — the fix is "invite the bot", which reads
 * differently from a genuine `failed`. The adapter never throws: a rejected
 * fetch or a non-2xx response becomes a `failed` outcome the service records.
 *
 * @see packages/core/src/ports/notification.ts — the port this implements
 * @see packages/eval/src/sinks/slack/format.ts — the shared formatters
 */
import { formatRegressionAlert, formatScoreSummary, formatWeeklyDigest, } from "../sinks/slack/format.js";
const CHAT_POST_MESSAGE_URL = "https://slack.com/api/chat.postMessage";
export class SlackNotificationAdapter {
    token;
    type = "slack";
    constructor(token) {
        this.token = token;
    }
    async deliver(target, content) {
        const { channel } = target;
        if (channel.type !== "slack") {
            return { reason: "unsupported_channel_type", status: "skipped" };
        }
        const message = renderMessage(content);
        let response;
        try {
            response = await fetch(CHAT_POST_MESSAGE_URL, {
                body: JSON.stringify({
                    blocks: message.blocks,
                    channel: channel.channelId,
                    text: message.text,
                }),
                headers: {
                    authorization: `Bearer ${this.token}`,
                    "content-type": "application/json; charset=utf-8",
                },
                method: "POST",
            });
        }
        catch (error) {
            return { error: errorMessage(error), status: "failed" };
        }
        if (!response.ok) {
            return {
                error: `Slack chat.postMessage returned HTTP ${response.status}`,
                status: "failed",
            };
        }
        let payload;
        try {
            payload = (await response.json());
        }
        catch (error) {
            return { error: errorMessage(error), status: "failed" };
        }
        if (payload.ok) {
            return { status: "delivered" };
        }
        // `not_in_channel` is operational, not a failure: the bot is configured
        // but has not been `/invite`d to the channel yet.
        if (payload.error === "not_in_channel") {
            return { reason: "not_in_channel", status: "skipped" };
        }
        return {
            error: `Slack chat.postMessage failed: ${payload.error ?? "unknown_error"}`,
            status: "failed",
        };
    }
}
/**
 * Pick the formatter matching the content kind. A report with regressions gets
 * the regression alert; otherwise the score summary. A digest always gets the
 * weekly-digest layout regardless of cadence.
 */
function renderMessage(content) {
    if (content.kind === "digest") {
        return formatWeeklyDigest(content.digest);
    }
    const { comparison } = content.report;
    const hasRegression = comparison !== undefined && comparison.regressed.length > 0;
    return hasRegression
        ? formatRegressionAlert(content.report)
        : formatScoreSummary(content.report);
}
function errorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
