/**
 * Chat gateway (T-CHAT-AS-TERMINAL CAT.1b) — thin outbound-dispatch +
 * lifecycle wrapper over the transport adapter map from `./factory.ts`.
 *
 * The legacy `src.legacy/chat/gateway.ts` carried a full inbound-dispatch fan
 * (`onMessage`) + a per-platform `ChatAdapter` abstraction. The new tree splits
 * those out: inbound is owned by the adapter's `subscribeTransport` →
 * `transport_inbox.routeAndWriteInbound` path (wired in the worker), so the
 * gateway's only jobs are (1) outbound `send` dispatch and (2) start/stop
 * fan-out. No inbound handler list lives here.
 *
 * The outbound `channel` string contract is LOAD-BEARING — it is the wire
 * format `src/mcp/chat-bridge-server.ts` + `src/runtime/agent_bridge/tools/
 * chat_send.ts` send over the daemon socket. `send()` parses it to an adapter
 * URI:
 *
 *   - `telegram:<chat_id>`                → telegram://<chat_id>
 *   - `telegram:<chat_id>:<thread_id>`    → telegram://<chat_id>/<thread_id>
 *   - explicit `threadId` arg overrides any thread suffix embedded in channel.
 *
 * Imports from: ./types. Imported by: ./daemon/{rpc_server,worker}.ts + tests.
 */
export class GatewayError extends Error {
    constructor(message) {
        super(message);
        this.name = 'GatewayError';
    }
}
/**
 * Parse the wire `channel` (+ optional explicit `threadId`) into a platform +
 * adapter URI. Telegram supports a `chat:thread` composite; an explicit
 * `threadId` arg takes precedence over the embedded suffix.
 */
export function parseChannel(channel, threadId) {
    const firstColon = channel.indexOf(':');
    if (firstColon === -1) {
        throw new GatewayError(`malformed channel '${channel}' — expected '<platform>:<native_id>'`);
    }
    const platformRaw = channel.slice(0, firstColon);
    if (platformRaw !== 'telegram' && platformRaw !== 'discord' && platformRaw !== 'slack') {
        throw new GatewayError(`unknown platform '${platformRaw}' in channel '${channel}'`);
    }
    const rest = channel.slice(firstColon + 1);
    if (platformRaw === 'telegram') {
        // rest is `<chat_id>` or `<chat_id>:<thread_id>`.
        const secondColon = rest.indexOf(':');
        const chatId = secondColon === -1 ? rest : rest.slice(0, secondColon);
        const embeddedThread = secondColon === -1 ? undefined : rest.slice(secondColon + 1);
        const thread = threadId ?? embeddedThread;
        const uri = thread !== undefined && thread.length > 0
            ? `telegram://${chatId}/${thread}`
            : `telegram://${chatId}`;
        return { platform: 'telegram', uri };
    }
    // discord / slack — the daemon does not activate these (factory skips them),
    // so a send here will fail on the missing adapter. URI is the native id.
    return { platform: platformRaw, uri: `${platformRaw}://${rest}` };
}
export class ChatGateway {
    adapters;
    createTopicFn;
    started = false;
    constructor(opts) {
        this.adapters = opts.adapters;
        if (opts.createTopic !== undefined)
            this.createTopicFn = opts.createTopic;
    }
    /** Start every adapter's long-poll loop (telegram is the only one wired). */
    async start() {
        if (this.started)
            return;
        await Promise.all([...this.adapters.values()].map((a) => {
            // `start` exists on every concrete adapter; the base interface omits
            // it (it's lifecycle, not the dispatch surface). Duck-type call.
            const lc = a;
            return typeof lc.start === 'function' ? lc.start() : Promise.resolve();
        }));
        this.started = true;
    }
    /** Stop every adapter. Best-effort — errors are swallowed. */
    async stop() {
        if (!this.started)
            return;
        await Promise.allSettled([...this.adapters.values()].map((a) => {
            const lc = a;
            return typeof lc.stop === 'function' ? lc.stop() : Promise.resolve();
        }));
        this.started = false;
    }
    /** Platforms currently activated (→ `list_channels` RPC). */
    activePlatforms() {
        return [...this.adapters.keys()];
    }
    /** The active adapter for a platform, or undefined. */
    getAdapter(platform) {
        return this.adapters.get(platform);
    }
    /** Dispatch an outbound message. Throws `GatewayError` on bad channel /
     *  missing adapter / adapter-reported send failure. */
    async send(params) {
        const { platform, uri } = parseChannel(params.channel, params.threadId);
        const adapter = this.adapters.get(platform);
        if (adapter === undefined) {
            throw new GatewayError(`no adapter active for platform '${platform}' (channel '${params.channel}')`);
        }
        let result;
        if (params.mediaPath !== undefined && params.mediaPath.length > 0) {
            // CAT.4 — media path. The adapter URI already carries the thread, so
            // sendPhoto re-derives it; text (when non-empty) becomes the caption.
            const photoAdapter = adapter;
            if (typeof photoAdapter.sendPhoto !== 'function') {
                throw new GatewayError(`platform '${platform}' does not support media send (channel '${params.channel}')`);
            }
            result = await photoAdapter.sendPhoto(uri, {
                path: params.mediaPath,
                ...(params.text.length > 0 ? { caption: params.text } : {}),
            });
        }
        else {
            result = await adapter.send(uri, { text: params.text });
        }
        if (!result.ok) {
            throw new GatewayError(result.error ?? `send failed for ${params.channel}`);
        }
        return { ok: true, platform, messageId: result.messageId ?? '', deliveredAt: new Date() };
    }
    /** Telegram `create_topic`. Throws when no createTopic seam is wired. */
    async createTopic(args) {
        if (this.createTopicFn === undefined) {
            throw new GatewayError('telegram adapter does not support topic creation (or not active)');
        }
        return this.createTopicFn({
            chatId: args.chatId,
            name: args.name,
            ...(args.iconColor !== undefined ? { iconColor: args.iconColor } : {}),
            ...(args.iconCustomEmojiId !== undefined
                ? { iconCustomEmojiId: args.iconCustomEmojiId }
                : {}),
        });
    }
}
//# sourceMappingURL=gateway.js.map