import { DiscordGatewayClient, DiscordIntegration, NtfyIntegration, SlackIntegration, SlackSocketModeClient, } from '../integrations/index.js';
import { handleDiscordGatewayDispatchPayload, handleNtfySurfacePayload, handleSlackSurfacePayload, } from '../adapters/index.js';
import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
const DEFAULT_STATE = {
    running: false,
    metadata: {},
};
export class ChannelProviderRuntimeManager {
    deps;
    slackClient = null;
    discordClient = null;
    ntfyAbort = null;
    state = {
        slack: { ...DEFAULT_STATE, metadata: {} },
        discord: { ...DEFAULT_STATE, metadata: {} },
        ntfy: { ...DEFAULT_STATE, metadata: {} },
    };
    constructor(deps) {
        this.deps = deps;
    }
    async startConfigured() {
        const results = [];
        if (this.deps.configManager.get('surfaces.slack.enabled') && await this.resolveSlackAppToken()) {
            results.push(await this.start('slack'));
        }
        if (this.deps.configManager.get('surfaces.discord.enabled') && await this.resolveDiscordBotToken()) {
            results.push(await this.start('discord'));
        }
        if (this.deps.configManager.get('surfaces.ntfy.enabled') && this.resolveNtfyTopic()) {
            results.push(await this.start('ntfy'));
        }
        return results;
    }
    async start(surface) {
        if (surface === 'slack')
            return this.startSlack();
        if (surface === 'discord')
            return this.startDiscord();
        return this.startNtfy();
    }
    stop(surface) {
        if (surface === 'slack') {
            this.slackClient?.stop();
            this.slackClient = null;
            this.markStopped('slack');
            return this.result('slack', true, 'Slack Socket Mode runtime stopped.');
        }
        if (surface === 'discord') {
            this.discordClient?.stop();
            this.discordClient = null;
            this.markStopped('discord');
            return this.result('discord', true, 'Discord Gateway runtime stopped.');
        }
        this.ntfyAbort?.abort();
        this.ntfyAbort = null;
        this.markStopped('ntfy');
        return this.result('ntfy', true, 'ntfy JSON stream runtime stopped.');
    }
    stopAll() {
        this.stop('slack');
        this.stop('discord');
        this.stop('ntfy');
    }
    status(surface) {
        const state = this.state[surface];
        return {
            surface,
            running: state.running,
            configured: this.isConfigured(surface),
            transport: surface === 'slack' ? 'socket-mode' : surface === 'discord' ? 'gateway' : 'json-stream',
            ...(state.lastStartedAt ? { lastStartedAt: state.lastStartedAt } : {}),
            ...(state.lastStoppedAt ? { lastStoppedAt: state.lastStoppedAt } : {}),
            ...(state.lastError ? { lastError: state.lastError } : {}),
            metadata: { ...state.metadata },
        };
    }
    async startSlack() {
        if (this.slackClient?.isStarted) {
            return this.result('slack', true, 'Slack Socket Mode runtime is already running.');
        }
        const appToken = await this.resolveSlackAppToken();
        const botToken = await this.resolveSlackBotToken();
        if (!appToken) {
            this.markError('slack', 'Slack app-level token is required for Socket Mode.');
            return this.result('slack', false, 'Slack app-level token is required for Socket Mode.');
        }
        const slack = new SlackIntegration(await this.deps.serviceRegistry.resolveSecret('slack', 'webhookUrl') ?? process.env.SLACK_WEBHOOK_URL, botToken ?? undefined);
        const client = new SlackSocketModeClient({
            appToken,
            integration: slack,
            onEnvelope: (envelope) => this.handleSlackEnvelope(envelope, slack),
        });
        try {
            const connection = await client.start();
            if (!connection.ok) {
                const message = connection.error ?? 'Slack Socket Mode connection failed.';
                this.markError('slack', message);
                return this.result('slack', false, message);
            }
            this.slackClient = client;
            this.markStarted('slack', { socketMode: true });
            return this.result('slack', true, 'Slack Socket Mode runtime started.');
        }
        catch (error) {
            const message = summarizeError(error);
            this.markError('slack', message);
            return this.result('slack', false, message);
        }
    }
    async startDiscord() {
        if (this.discordClient?.isStarted) {
            return this.result('discord', true, 'Discord Gateway runtime is already running.');
        }
        const botToken = await this.resolveDiscordBotToken();
        if (!botToken) {
            this.markError('discord', 'Discord bot token is required for Gateway runtime.');
            return this.result('discord', false, 'Discord bot token is required for Gateway runtime.');
        }
        const discord = new DiscordIntegration(await this.deps.serviceRegistry.resolveSecret('discord', 'webhookUrl') ?? process.env.DISCORD_WEBHOOK_URL, botToken);
        const client = new DiscordGatewayClient({
            token: botToken,
            integration: discord,
            onDispatch: (dispatch) => this.handleDiscordDispatch(dispatch, discord),
        });
        try {
            const gateway = await client.start();
            this.discordClient = client;
            this.markStarted('discord', { gatewayUrl: gateway.url, shards: gateway.shards });
            return this.result('discord', true, 'Discord Gateway runtime started.');
        }
        catch (error) {
            const message = summarizeError(error);
            this.markError('discord', message);
            return this.result('discord', false, message);
        }
    }
    async startNtfy() {
        if (this.ntfyAbort) {
            return this.result('ntfy', true, 'ntfy JSON stream runtime is already running.');
        }
        const topic = this.resolveNtfyTopic();
        if (!topic) {
            this.markError('ntfy', 'ntfy topic is required for subscription runtime.');
            return this.result('ntfy', false, 'ntfy topic is required for subscription runtime.');
        }
        const abort = new AbortController();
        this.ntfyAbort = abort;
        const ntfy = new NtfyIntegration(String(this.deps.configManager.get('surfaces.ntfy.baseUrl') || 'https://ntfy.sh'), await this.resolveNtfyToken() ?? undefined);
        this.markStarted('ntfy', { topic });
        void ntfy.subscribeJsonStream(topic, (message) => this.handleNtfyMessage(message), {
            since: 'latest',
            signal: abort.signal,
        }).catch((error) => {
            if (abort.signal.aborted)
                return;
            const message = summarizeError(error);
            this.ntfyAbort = null;
            this.markError('ntfy', message);
            logger.warn('ChannelProviderRuntimeManager: ntfy stream failed', { error: message });
        });
        return this.result('ntfy', true, 'ntfy JSON stream runtime started.');
    }
    async handleSlackEnvelope(envelope, slack) {
        if (!envelope.payload || typeof envelope.payload !== 'object')
            return;
        await handleSlackSurfacePayload(envelope.payload, this.deps.buildSurfaceAdapterContext(), slack).catch((error) => {
            logger.warn('ChannelProviderRuntimeManager: Slack Socket Mode payload failed', {
                error: summarizeError(error),
            });
        });
    }
    async handleDiscordDispatch(dispatch, discord) {
        await handleDiscordGatewayDispatchPayload(dispatch, this.deps.buildSurfaceAdapterContext(), discord).catch((error) => {
            logger.warn('ChannelProviderRuntimeManager: Discord Gateway dispatch failed', {
                eventType: dispatch.t,
                error: summarizeError(error),
            });
        });
    }
    async handleNtfyMessage(message) {
        if (message.event !== 'message')
            return;
        await handleNtfySurfacePayload(message, this.deps.buildSurfaceAdapterContext()).catch((error) => {
            logger.warn('ChannelProviderRuntimeManager: ntfy message dispatch failed', {
                error: summarizeError(error),
            });
        });
    }
    async resolveSlackBotToken() {
        const serviceValue = await this.deps.serviceRegistry.resolveSecret('slack', 'primary');
        return serviceValue
            || String(this.deps.configManager.get('surfaces.slack.botToken') || '')
            || process.env.SLACK_BOT_TOKEN
            || null;
    }
    async resolveSlackAppToken() {
        return String(this.deps.configManager.get('surfaces.slack.appToken') || '')
            || process.env.SLACK_APP_TOKEN
            || null;
    }
    async resolveDiscordBotToken() {
        const serviceValue = await this.deps.serviceRegistry.resolveSecret('discord', 'primary');
        return serviceValue
            || String(this.deps.configManager.get('surfaces.discord.botToken') || '')
            || process.env.DISCORD_BOT_TOKEN
            || null;
    }
    async resolveNtfyToken() {
        const serviceValue = await this.deps.serviceRegistry.resolveSecret('ntfy', 'primary');
        return serviceValue
            || String(this.deps.configManager.get('surfaces.ntfy.token') || '')
            || process.env.NTFY_ACCESS_TOKEN
            || null;
    }
    resolveNtfyTopic() {
        return String(this.deps.configManager.get('surfaces.ntfy.topic') || '').trim();
    }
    isConfigured(surface) {
        if (surface === 'slack') {
            return Boolean(this.deps.configManager.get('surfaces.slack.appToken') || process.env.SLACK_APP_TOKEN);
        }
        if (surface === 'discord') {
            return Boolean(this.deps.configManager.get('surfaces.discord.botToken') || process.env.DISCORD_BOT_TOKEN);
        }
        return Boolean(this.resolveNtfyTopic());
    }
    markStarted(surface, metadata) {
        this.state[surface] = {
            running: true,
            lastStartedAt: Date.now(),
            metadata,
        };
    }
    markStopped(surface) {
        this.state[surface] = {
            ...this.state[surface],
            running: false,
            lastStoppedAt: Date.now(),
        };
    }
    markError(surface, error) {
        this.state[surface] = {
            ...this.state[surface],
            running: false,
            lastError: error,
        };
    }
    result(surface, ok, message) {
        return {
            ok,
            surface,
            status: this.status(surface),
            message,
        };
    }
}
