/**
 * 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`. Unlike the webhook
 * `SlackSink` (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/core/src/notifications/slack/factory.ts — the Block Kit factory
 */
import { buildSlackMessage, } from "../_vendor/ailf-core/index.js";
const CHAT_POST_MESSAGE_URL = "https://slack.com/api/chat.postMessage";
export class SlackNotificationAdapter {
    token;
    dashboardBaseUrl;
    type = "slack";
    constructor(token, dashboardBaseUrl) {
        this.token = token;
        this.dashboardBaseUrl = dashboardBaseUrl;
    }
    async deliver(target, content) {
        const { channel } = target;
        if (channel.type !== "slack") {
            return { reason: "unsupported_channel_type", status: "skipped" };
        }
        const message = renderMessage(content, this.dashboardBaseUrl);
        let response;
        try {
            response = await fetch(CHAT_POST_MESSAGE_URL, {
                body: JSON.stringify({
                    blocks: message.blocks,
                    attachments: message.attachments,
                    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",
        };
    }
}
/**
 * Build the Slack message for the given notification content via the
 * core factory. A report gets the report template (regression or score
 * summary, chosen by the factory). A digest gets the digest layout.
 */
function renderMessage(content, dashboardBaseUrl) {
    const options = { dashboardBaseUrl };
    if (content.kind === "digest") {
        return buildSlackMessage({ kind: "digest", digest: content.digest, cadence: content.cadence }, options);
    }
    return buildSlackMessage({ kind: "report", report: content.report }, options);
}
function errorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
