/**
 * Remote Questions — /nerf remote command
 */
import { AuthStorage } from "@nerf/pi-coding-agent";
import { Editor, Key, matchesKey, truncateToWidth } from "@nerf/pi-tui";
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { dirname, join } from "node:path";
import { getGlobalNerfPreferencesPath, loadEffectiveNerfPreferences } from "../nerf-engine/preferences.js";
import { getRemoteConfigStatus, isValidChannelId, resolveRemoteConfig } from "./config.js";
import { maskEditorLine, sanitizeError } from "../shared/mod.js";
import { getLatestPromptSummary } from "./status.js";
export async function handleRemote(subcommand, ctx, _pi) {
    const trimmed = subcommand.trim();
    if (trimmed === "slack")
        return handleSetupSlack(ctx);
    if (trimmed === "discord")
        return handleSetupDiscord(ctx);
    if (trimmed === "telegram")
        return handleSetupTelegram(ctx);
    if (trimmed === "status")
        return handleRemoteStatus(ctx);
    if (trimmed === "disconnect")
        return handleDisconnect(ctx);
    return handleRemoteMenu(ctx);
}
async function handleSetupSlack(ctx) {
    const token = await promptMaskedInput(ctx, "Slack Bot Token", "Paste your xoxb-... token");
    if (!token)
        return void ctx.ui.notify("Slack setup cancelled.", "info");
    if (!token.startsWith("xoxb-"))
        return void ctx.ui.notify("Invalid token format — Slack bot tokens start with xoxb-.", "warning");
    ctx.ui.notify("Validating token...", "info");
    const auth = await fetchJson("https://slack.com/api/auth.test", { headers: { Authorization: `Bearer ${token}` } });
    if (!auth?.ok)
        return void ctx.ui.notify("Token validation failed — check the token and app install.", "error");
    const channels = await listSlackChannels(token);
    const MANUAL_OPTION = "Enter channel ID manually";
    let channelId;
    if (!channels || channels.length === 0) {
        ctx.ui.notify("Could not list Slack channels — falling back to manual entry.", "warning");
        channelId = await promptSlackChannelId(ctx) ?? "";
    }
    else {
        const channelOptions = [...channels.map((channel) => channel.label), MANUAL_OPTION];
        const selectedChannel = await ctx.ui.select("Select a Slack channel", channelOptions);
        if (!selectedChannel)
            return void ctx.ui.notify("Slack setup cancelled.", "info");
        if (selectedChannel === MANUAL_OPTION) {
            channelId = await promptSlackChannelId(ctx) ?? "";
        }
        else {
            const chosen = channels.find((channel) => channel.label === selectedChannel);
            if (!chosen)
                return void ctx.ui.notify("Slack setup cancelled.", "info");
            channelId = chosen.id;
        }
    }
    if (!channelId)
        return void ctx.ui.notify("Slack setup cancelled.", "info");
    const send = await fetchJson("https://slack.com/api/chat.postMessage", {
        method: "POST",
        headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json; charset=utf-8" },
        body: JSON.stringify({ channel: channelId, text: "NERF remote questions connected." }),
    });
    if (!send?.ok)
        return void ctx.ui.notify(`Could not send to channel: ${send?.error ?? "unknown error"}`, "error");
    saveProviderToken("slack_bot", token);
    process.env.SLACK_BOT_TOKEN = token;
    saveRemoteQuestionsConfig("slack", channelId);
    ctx.ui.notify(`Slack connected — remote questions enabled for channel ${channelId}.`, "info");
}
async function handleSetupDiscord(ctx) {
    const token = await promptMaskedInput(ctx, "Discord Bot Token", "Paste your bot token");
    if (!token)
        return void ctx.ui.notify("Discord setup cancelled.", "info");
    ctx.ui.notify("Validating token...", "info");
    const headers = { Authorization: `Bot ${token}` };
    const auth = await fetchJson("https://discord.com/api/v10/users/@me", { headers });
    if (!auth?.id)
        return void ctx.ui.notify("Token validation failed — check the bot token.", "error");
    // Fetch guilds the bot is a member of
    const guilds = await fetchJson("https://discord.com/api/v10/users/@me/guilds", { headers });
    if (!Array.isArray(guilds) || guilds.length === 0) {
        return void ctx.ui.notify("Bot is not in any Discord servers.", "error");
    }
    let guildId;
    let guildName;
    if (guilds.length === 1) {
        guildId = guilds[0].id;
        guildName = guilds[0].name;
    }
    else {
        const guildOptions = guilds.map((g) => g.name);
        const selectedGuild = await ctx.ui.select("Select a Discord server", guildOptions);
        if (!selectedGuild)
            return void ctx.ui.notify("Discord setup cancelled.", "info");
        const chosen = guilds.find((g) => g.name === selectedGuild);
        if (!chosen)
            return void ctx.ui.notify("Discord setup cancelled.", "info");
        guildId = chosen.id;
        guildName = chosen.name;
    }
    // Fetch text and announcement channels in the selected guild
    ctx.ui.notify(`Fetching channels for ${guildName}...`, "info");
    const allChannels = await fetchJson(`https://discord.com/api/v10/guilds/${guildId}/channels`, { headers });
    const textChannels = Array.isArray(allChannels)
        ? allChannels.filter((ch) => ch.type === 0 || ch.type === 5)
        : [];
    const MANUAL_OPTION = "Enter channel ID manually";
    let channelId;
    if (textChannels.length === 0) {
        ctx.ui.notify("No text channels found — falling back to manual entry.", "warning");
        const manualId = await promptInput(ctx, "Channel ID", "Paste the Discord channel ID (e.g. 1234567890123456789)");
        if (!manualId)
            return void ctx.ui.notify("Discord setup cancelled.", "info");
        if (!isValidChannelId("discord", manualId))
            return void ctx.ui.notify("Invalid Discord channel ID format — expected 17-20 digit numeric ID.", "error");
        channelId = manualId;
    }
    else {
        const channelOptions = [...textChannels.map((ch) => `#${ch.name}`), MANUAL_OPTION];
        const selectedChannel = await ctx.ui.select("Select a channel", channelOptions);
        if (!selectedChannel)
            return void ctx.ui.notify("Discord setup cancelled.", "info");
        if (selectedChannel === MANUAL_OPTION) {
            const manualId = await promptInput(ctx, "Channel ID", "Paste the Discord channel ID (e.g. 1234567890123456789)");
            if (!manualId)
                return void ctx.ui.notify("Discord setup cancelled.", "info");
            if (!isValidChannelId("discord", manualId))
                return void ctx.ui.notify("Invalid Discord channel ID format — expected 17-20 digit numeric ID.", "error");
            channelId = manualId;
        }
        else {
            const chosenChannel = textChannels.find((ch) => `#${ch.name}` === selectedChannel);
            if (!chosenChannel)
                return void ctx.ui.notify("Discord setup cancelled.", "info");
            channelId = chosenChannel.id;
        }
    }
    const sendResponse = await fetch(`https://discord.com/api/v10/channels/${channelId}/messages`, {
        method: "POST",
        headers: { ...headers, "Content-Type": "application/json" },
        body: JSON.stringify({ content: "NERF remote questions connected." }),
        signal: AbortSignal.timeout(15_000),
    });
    if (!sendResponse.ok) {
        const body = await sendResponse.text().catch(() => "");
        return void ctx.ui.notify(`Could not send to channel (HTTP ${sendResponse.status}): ${sanitizeError(body).slice(0, 200)}`, "error");
    }
    saveProviderToken("discord_bot", token);
    process.env.DISCORD_BOT_TOKEN = token;
    saveRemoteQuestionsConfig("discord", channelId);
    ctx.ui.notify(`Discord connected — remote questions enabled for channel ${channelId}.`, "info");
}
async function handleSetupTelegram(ctx) {
    const token = await promptMaskedInput(ctx, "Telegram Bot Token", "Paste your bot token from @BotFather");
    if (!token)
        return void ctx.ui.notify("Telegram setup cancelled.", "info");
    if (!/^\d+:[A-Za-z0-9_-]+$/.test(token))
        return void ctx.ui.notify("Invalid token format — Telegram bot tokens look like 123456789:ABCdefGHI...", "warning");
    ctx.ui.notify("Validating token...", "info");
    const auth = await fetchJson(`https://api.telegram.org/bot${token}/getMe`);
    if (!auth?.ok || !auth?.result?.id)
        return void ctx.ui.notify("Token validation failed — check the bot token.", "error");
    const chatId = await promptInput(ctx, "Chat ID", "Paste the Telegram chat ID (e.g. -1001234567890)");
    if (!chatId)
        return void ctx.ui.notify("Telegram setup cancelled.", "info");
    if (!isValidChannelId("telegram", chatId))
        return void ctx.ui.notify("Invalid Telegram chat ID format — expected a numeric ID (can be negative for groups).", "error");
    const send = await fetchJson(`https://api.telegram.org/bot${token}/sendMessage`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ chat_id: chatId, text: "NERF remote questions connected." }),
    });
    if (!send?.ok)
        return void ctx.ui.notify(`Could not send to chat: ${send?.description ?? "unknown error"}`, "error");
    saveProviderToken("telegram_bot", token);
    process.env.TELEGRAM_BOT_TOKEN = token;
    saveRemoteQuestionsConfig("telegram", chatId);
    ctx.ui.notify(`Telegram connected — remote questions enabled for chat ${chatId}.`, "info");
}
async function handleRemoteStatus(ctx) {
    const status = getRemoteConfigStatus();
    const config = resolveRemoteConfig();
    if (!config) {
        ctx.ui.notify(status, status.includes("disabled") ? "warning" : "info");
        return;
    }
    const latestPrompt = getLatestPromptSummary();
    const lines = [status];
    if (latestPrompt) {
        lines.push(`Last prompt: ${latestPrompt.id}`);
        lines.push(`  status: ${latestPrompt.status}`);
        if (latestPrompt.updatedAt)
            lines.push(`  updated: ${new Date(latestPrompt.updatedAt).toLocaleString()}`);
    }
    ctx.ui.notify(lines.join("\n"), "info");
}
async function handleDisconnect(ctx) {
    const prefs = loadEffectiveNerfPreferences();
    const channel = prefs?.preferences.remote_questions?.channel;
    if (!channel)
        return void ctx.ui.notify("No remote channel configured — nothing to disconnect.", "info");
    removeRemoteQuestionsConfig();
    const providerMap = { slack: "slack_bot", discord: "discord_bot", telegram: "telegram_bot" };
    removeProviderToken(providerMap[channel] ?? channel);
    if (channel === "slack")
        delete process.env.SLACK_BOT_TOKEN;
    if (channel === "discord")
        delete process.env.DISCORD_BOT_TOKEN;
    if (channel === "telegram")
        delete process.env.TELEGRAM_BOT_TOKEN;
    ctx.ui.notify(`Remote questions disconnected (${channel}).`, "info");
}
async function handleRemoteMenu(ctx) {
    const config = resolveRemoteConfig();
    const latestPrompt = getLatestPromptSummary();
    const lines = config
        ? [
            `Remote questions: ${config.channel} configured`,
            `  Timeout: ${config.timeoutMs / 60000}m, poll: ${config.pollIntervalMs / 1000}s`,
            latestPrompt ? `  Last prompt: ${latestPrompt.id} (${latestPrompt.status})` : "  No remote prompts recorded yet",
            "",
            "Commands:",
            "  /nerf remote status",
            "  /nerf remote disconnect",
            "  /nerf remote slack",
            "  /nerf remote discord",
            "  /nerf remote telegram",
        ]
        : [
            "No remote question channel configured.",
            "",
            "Commands:",
            "  /nerf remote slack",
            "  /nerf remote discord",
            "  /nerf remote telegram",
            "  /nerf remote status",
        ];
    ctx.ui.notify(lines.join("\n"), "info");
}
async function fetchJson(url, init) {
    try {
        const response = await fetch(url, { ...init, signal: AbortSignal.timeout(15_000) });
        return await response.json();
    }
    catch {
        return null;
    }
}
async function listSlackChannels(token) {
    const headers = { Authorization: `Bearer ${token}` };
    const channels = [];
    let cursor = "";
    do {
        const params = new URLSearchParams({
            exclude_archived: "true",
            limit: "200",
            types: "public_channel,private_channel",
        });
        if (cursor)
            params.set("cursor", cursor);
        const response = await fetchJson(`https://slack.com/api/users.conversations?${params.toString()}`, { headers });
        if (!response?.ok || !Array.isArray(response.channels)) {
            return channels.length > 0 ? channels.map(({ id, label }) => ({ id, label })) : null;
        }
        for (const channel of response.channels) {
            if (!channel.id || !channel.name)
                continue;
            channels.push({
                id: channel.id,
                name: channel.name,
                label: channel.is_private ? `[private] ${channel.name}` : `#${channel.name}`,
            });
        }
        cursor = typeof response.response_metadata?.next_cursor === "string"
            ? response.response_metadata.next_cursor
            : "";
    } while (cursor);
    channels.sort((a, b) => a.name.localeCompare(b.name));
    return channels.map(({ id, label }) => ({ id, label }));
}
async function promptSlackChannelId(ctx) {
    const channelId = await promptInput(ctx, "Channel ID", "Paste the Slack channel ID (e.g. C0123456789)");
    if (!channelId)
        return null;
    if (!isValidChannelId("slack", channelId)) {
        ctx.ui.notify("Invalid Slack channel ID format — expected 9-12 uppercase alphanumeric characters.", "error");
        return null;
    }
    return channelId;
}
function getAuthStorage() {
    const authPath = join(process.env.HOME ?? "", ".nerf", "agent", "auth.json");
    mkdirSync(dirname(authPath), { recursive: true });
    return AuthStorage.create(authPath);
}
function saveProviderToken(provider, token) {
    const auth = getAuthStorage();
    auth.set(provider, { type: "api_key", key: token });
}
function removeProviderToken(provider) {
    const auth = getAuthStorage();
    auth.remove(provider);
}
export function saveRemoteQuestionsConfig(channel, channelId) {
    const prefsPath = getGlobalNerfPreferencesPath();
    const block = [
        "remote_questions:",
        `  channel: ${channel}`,
        `  channel_id: \"${channelId}\"`,
        "  timeout_minutes: 5",
        "  poll_interval_seconds: 5",
    ].join("\n");
    const content = existsSync(prefsPath) ? readFileSync(prefsPath, "utf-8") : "";
    const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
    let next = content;
    if (fmMatch) {
        let frontmatter = fmMatch[1];
        const regex = /remote_questions:[\s\S]*?(?=\n[a-zA-Z_]|\n---|$)/;
        frontmatter = regex.test(frontmatter) ? frontmatter.replace(regex, block) : `${frontmatter.trimEnd()}\n${block}`;
        next = `---\n${frontmatter}\n---${content.slice(fmMatch[0].length)}`;
    }
    else {
        next = `---\n${block}\n---\n\n${content}`;
    }
    mkdirSync(dirname(prefsPath), { recursive: true });
    writeFileSync(prefsPath, next, "utf-8");
}
function removeRemoteQuestionsConfig() {
    const prefsPath = getGlobalNerfPreferencesPath();
    if (!existsSync(prefsPath))
        return;
    const content = readFileSync(prefsPath, "utf-8");
    const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
    if (!fmMatch)
        return;
    const frontmatter = fmMatch[1].replace(/remote_questions:[\s\S]*?(?=\n[a-zA-Z_]|\n---|$)/, "").trim();
    const next = frontmatter ? `---\n${frontmatter}\n---${content.slice(fmMatch[0].length)}` : content.slice(fmMatch[0].length).replace(/^\n+/, "");
    writeFileSync(prefsPath, next, "utf-8");
}
async function promptMaskedInput(ctx, label, hint) {
    if (!ctx.hasUI)
        return null;
    return ctx.ui.custom((tui, theme, _kb, done) => {
        let cachedLines;
        const editorTheme = {
            borderColor: (s) => theme.fg("accent", s),
            selectList: {
                selectedPrefix: (t) => theme.fg("accent", t),
                selectedText: (t) => theme.fg("accent", t),
                description: (t) => theme.fg("muted", t),
                scrollInfo: (t) => theme.fg("dim", t),
                noMatch: (t) => theme.fg("warning", t),
            },
        };
        const editor = new Editor(tui, editorTheme, { paddingX: 1 });
        const refresh = () => { cachedLines = undefined; tui.requestRender(); };
        const handleInput = (data) => {
            if (matchesKey(data, Key.enter))
                return done(editor.getText().trim() || null);
            if (matchesKey(data, Key.escape))
                return done(null);
            editor.handleInput(data);
            refresh();
        };
        const render = (width) => {
            if (cachedLines)
                return cachedLines;
            const lines = [];
            const add = (s) => lines.push(truncateToWidth(s, width));
            add(theme.fg("accent", "─".repeat(width)));
            add(theme.fg("accent", theme.bold(` ${label}`)));
            add(theme.fg("muted", `  ${hint}`));
            lines.push("");
            add(theme.fg("muted", " Enter value:"));
            for (const line of editor.render(width - 2))
                add(theme.fg("text", maskEditorLine(line)));
            lines.push("");
            add(theme.fg("dim", " enter to confirm  |  esc to cancel"));
            add(theme.fg("accent", "─".repeat(width)));
            cachedLines = lines;
            return lines;
        };
        return { render, handleInput, invalidate: () => { cachedLines = undefined; } };
    });
}
async function promptInput(ctx, label, hint) {
    if (!ctx.hasUI)
        return null;
    return ctx.ui.custom((tui, theme, _kb, done) => {
        let cachedLines;
        const editorTheme = {
            borderColor: (s) => theme.fg("accent", s),
            selectList: {
                selectedPrefix: (t) => theme.fg("accent", t),
                selectedText: (t) => theme.fg("accent", t),
                description: (t) => theme.fg("muted", t),
                scrollInfo: (t) => theme.fg("dim", t),
                noMatch: (t) => theme.fg("warning", t),
            },
        };
        const editor = new Editor(tui, editorTheme, { paddingX: 1 });
        const refresh = () => { cachedLines = undefined; tui.requestRender(); };
        const handleInput = (data) => {
            if (matchesKey(data, Key.enter))
                return done(editor.getText().trim() || null);
            if (matchesKey(data, Key.escape))
                return done(null);
            editor.handleInput(data);
            refresh();
        };
        const render = (width) => {
            if (cachedLines)
                return cachedLines;
            const lines = [];
            const add = (s) => lines.push(truncateToWidth(s, width));
            add(theme.fg("accent", "─".repeat(width)));
            add(theme.fg("accent", theme.bold(` ${label}`)));
            add(theme.fg("muted", `  ${hint}`));
            lines.push("");
            add(theme.fg("muted", " Enter value:"));
            for (const line of editor.render(width - 2))
                add(theme.fg("text", line));
            lines.push("");
            add(theme.fg("dim", " enter to confirm  |  esc to cancel"));
            add(theme.fg("accent", "─".repeat(width)));
            cachedLines = lines;
            return lines;
        };
        return { render, handleInput, invalidate: () => { cachedLines = undefined; } };
    });
}
