/**
 * remote/gateway/adapters/telegram — Telegram Bot API gateway adapter.
 *
 * Inbound: long-polling `getUpdates` — no inbound port, the same dial-out
 * posture as the sovereign relay. Outbound: `sendMessage`.
 *
 * Zero-trust: `allowedChatIds` MUST be non-empty. It is BOTH the allowlist of
 * chats permitted to drive the agent AND the set of delivery targets — a
 * stranger who discovers the bot can neither control nor observe the session.
 *
 * @public @since 2.14.0 — preste remote-control T4
 */
import { NOOP_LOGGER } from "../types.js";
/** Telegram message text hard limit. */
const TELEGRAM_MAX_CHARS = 4096;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
/** Create a Telegram gateway adapter. */
export function createTelegramAdapter(opts) {
    const botToken = opts.botToken.trim();
    if (botToken === "") {
        throw new Error("telegram adapter: botToken is required");
    }
    const allowed = new Set(opts.allowedChatIds.map((c) => String(c).trim()));
    allowed.delete("");
    if (allowed.size === 0) {
        throw new Error("telegram adapter: allowedChatIds must be non-empty (zero-trust — an " +
            "empty allowlist would let any stranger who finds the bot drive the agent)");
    }
    const fetchImpl = opts.fetchImpl ?? fetch;
    const apiBase = (opts.apiBase ?? "https://api.telegram.org").replace(/\/+$/, "");
    const pollTimeoutSec = opts.pollTimeoutSec ?? 25;
    const log = opts.log ?? NOOP_LOGGER;
    let running = false;
    let offset = 0;
    let loopDone = Promise.resolve();
    let pollAbort = null;
    async function tgCall(method, body, signal) {
        const res = await fetchImpl(`${apiBase}/bot${botToken}/${method}`, {
            method: "POST",
            headers: { "content-type": "application/json" },
            body: JSON.stringify(body ?? {}),
            ...(signal ? { signal } : {}),
        });
        const json = (await res.json());
        if (!json || json.ok !== true) {
            throw new Error(`telegram ${method}: ${json?.description ?? `HTTP ${res.status}`}`);
        }
        return json.result;
    }
    async function pollOnce(onMessage) {
        pollAbort = new AbortController();
        // Watchdog: a stuck poll is aborted slightly after the server-side limit.
        const watchdog = setTimeout(() => pollAbort?.abort(), (pollTimeoutSec + 15) * 1000);
        watchdog.unref?.();
        try {
            const updates = await tgCall("getUpdates", { offset, timeout: pollTimeoutSec, allowed_updates: ["message"] }, pollAbort.signal);
            for (const u of updates) {
                offset = Math.max(offset, u.update_id + 1);
                const m = u.message;
                if (!m || typeof m.text !== "string")
                    continue;
                const chatId = String(m.chat?.id ?? "");
                if (!allowed.has(chatId)) {
                    log(`telegram: ignored message from non-allowlisted chat ${chatId}`);
                    continue;
                }
                onMessage({
                    platform: "telegram",
                    chatId,
                    userId: String(m.from?.id ?? ""),
                    text: m.text,
                    ts: new Date().toISOString(),
                });
            }
        }
        finally {
            clearTimeout(watchdog);
        }
    }
    return {
        platform: "telegram",
        async start(onMessage) {
            if (running)
                return;
            running = true;
            loopDone = (async () => {
                while (running) {
                    try {
                        await pollOnce(onMessage);
                    }
                    catch (err) {
                        if (running) {
                            log(`telegram: poll error — ${err.message}`);
                            await sleep(2000);
                        }
                    }
                }
            })();
        },
        async deliver(text) {
            const body = text.length > TELEGRAM_MAX_CHARS ? `${text.slice(0, TELEGRAM_MAX_CHARS - 1)}…` : text;
            for (const chatId of allowed) {
                try {
                    await tgCall("sendMessage", { chat_id: chatId, text: body });
                }
                catch (err) {
                    log(`telegram: deliver to ${chatId} failed — ${err.message}`);
                }
            }
        },
        async stop() {
            running = false;
            pollAbort?.abort();
            try {
                await loopDone;
            }
            catch {
                /* loop already exiting */
            }
        },
    };
}
//# sourceMappingURL=telegram.js.map