/**
 * Chat gateway — unified surface for bot-style chat connections (v0.7).
 *
 * Three concrete adapters plug into this interface: Telegram, Discord,
 * Slack. Each is independently activatable via a token in
 * `~/.opensquid/config.json` `chat_connections.<platform>.bot_token`.
 * Adapters that don't have a token configured are skipped at startup —
 * opensquid works fine with zero, one, or all three chat platforms wired.
 *
 * Scope decisions (per [[find-simple-solutions]]):
 * - Single bot owner per platform (no multi-tenancy, no OAuth flows).
 *   The user runs their OWN bot with their OWN token.
 * - Bot owner is the only authorized chat participant by default.
 *   Allowlist of additional chat/channel/user ids in config.
 * - Outbound-only connection mechanism (long-poll, WebSocket, Socket
 *   Mode) — no inbound HTTP server, no webhook URL, no public
 *   ingress required. Works behind any NAT.
 * - Text-only messages in v0.7. Images / files / inline buttons
 *   deferred until concrete user-need surfaces.
 *
 * Why a gateway abstraction at all (vs per-platform code paths):
 * the opensquid-side MCP tool surface (`chat.send`, `chat.list_channels`,
 * the inbound-message → MCP-context bridge) should NOT care which
 * platform a message came from. The gateway normalizes incoming events
 * to a single `ChatMessage` shape and routes outgoing sends by
 * channel id.
 *
 * What is NOT in this file:
 * - Token storage / config loading (lives in `chat/config.ts`).
 * - The actual SDK calls (live in `chat/adapters/<platform>.ts`).
 * - The MCP tool wiring (lives in `chat/mcp-tools.ts`).
 *
 * What IS in this file: types + the `ChatGateway` orchestrator that
 * holds N adapters and presents a single send/subscribe API.
 */
// ---------------------------------------------------------------------
// Gateway orchestrator
// ---------------------------------------------------------------------
/**
 * Holds N adapters and presents the unified API. Construct with the
 * adapter array you want active for this opensquid process. Pre-wired
 * tokens live in config; the factory at `chat/factory.ts` builds the
 * adapter array based on which tokens are present.
 */
export class ChatGateway {
    adapters;
    handlers = [];
    started = false;
    constructor(adapters) {
        this.adapters = new Map(adapters.map((a) => [a.platform, a]));
    }
    /** Open all adapter connections in parallel. First failure surfaces. */
    async start() {
        if (this.started)
            return;
        // Wire incoming handler dispatch BEFORE starting connections so we
        // don't drop early messages.
        for (const adapter of this.adapters.values()) {
            adapter.onMessage((msg) => this.dispatch(msg));
        }
        await Promise.all([...this.adapters.values()].map((a) => a.start()));
        this.started = true;
    }
    /** Close all adapter connections. Errors are logged and swallowed
     * so shutdown is always best-effort. */
    async shutdown() {
        if (!this.started)
            return;
        await Promise.allSettled([...this.adapters.values()].map((a) => a.shutdown()));
        this.started = false;
    }
    /** Subscribe to inbound messages from any platform. */
    onMessage(handler) {
        this.handlers.push(handler);
    }
    /** Route an outbound message to the right adapter by `channel` prefix. */
    async send(message) {
        const platform = platformFromChannel(message.channel);
        const adapter = this.adapters.get(platform);
        if (!adapter) {
            throw new ChatGatewayError(`no adapter active for platform '${platform}' (channel '${message.channel}')`, "ensure the corresponding bot_token is set in ~/.opensquid/config.json chat_connections");
        }
        return adapter.send(message);
    }
    /** List the platforms currently wired up. Used by `chat.list_channels`. */
    activePlatforms() {
        return [...this.adapters.keys()];
    }
    /**
     * v0.7.2 — get the active adapter for a platform so daemon-internal
     * code paths (e.g. `chat-daemon-worker` RPC dispatch) can reach
     * platform-specific methods like Telegram's `createTopic`. Returns
     * undefined when the platform isn't activated.
     */
    getAdapter(platform) {
        return this.adapters.get(platform);
    }
    async dispatch(msg) {
        for (const handler of this.handlers) {
            try {
                await handler(msg);
            }
            catch (err) {
                const e = err instanceof Error ? err.message : String(err);
                // Don't let one handler break the dispatch chain.
                // eslint-disable-next-line no-console
                console.error(`[chat-gateway] handler error on ${msg.platform}: ${e}`);
            }
        }
    }
}
// ---------------------------------------------------------------------
// Errors
// ---------------------------------------------------------------------
export class ChatGatewayError extends Error {
    hint;
    constructor(message, hint) {
        super(message);
        this.hint = hint;
        this.name = "ChatGatewayError";
    }
}
// ---------------------------------------------------------------------
// ChannelId helpers
// ---------------------------------------------------------------------
export function formatChannelId(platform, nativeId) {
    return `${platform}:${nativeId}`;
}
export function platformFromChannel(channel) {
    const idx = channel.indexOf(":");
    if (idx === -1) {
        throw new ChatGatewayError(`malformed channel id '${channel}'`, "channel ids must be '<platform>:<native_id>', e.g. 'telegram:8075471258'");
    }
    const candidate = channel.slice(0, idx);
    if (candidate !== "telegram" && candidate !== "discord" && candidate !== "slack") {
        throw new ChatGatewayError(`unknown platform '${candidate}' in channel id '${channel}'`, "supported platforms: telegram, discord, slack");
    }
    return candidate;
}
export function nativeIdFromChannel(channel) {
    const idx = channel.indexOf(":");
    if (idx === -1) {
        throw new ChatGatewayError(`malformed channel id '${channel}'`);
    }
    return channel.slice(idx + 1);
}
