import { listPairingChannels, notifyPairingApproved } from "../channels/plugins/pairing.js";
import { normalizeChannelId } from "../channels/plugins/index.js";
import { loadConfig } from "../config/config.js";
import { resolvePairingIdLabel } from "../pairing/pairing-labels.js";
import { approveChannelPairingCode, listChannelPairingRequests, } from "../pairing/pairing-store.js";
import { defaultRuntime } from "../runtime.js";
import { formatDocsLink } from "../terminal/links.js";
import { renderTable } from "../terminal/table.js";
import { theme } from "../terminal/theme.js";
import { formatCliCommand } from "./command-format.js";
/** Parse channel, allowing extension channels not in core registry. */
function parseChannel(raw, channels) {
    const value = (typeof raw === "string"
        ? raw
        : typeof raw === "number" || typeof raw === "boolean"
            ? String(raw)
            : "")
        .trim()
        .toLowerCase();
    if (!value)
        throw new Error("Channel required");
    const normalized = normalizeChannelId(value);
    if (normalized) {
        if (!channels.includes(normalized)) {
            throw new Error(`Channel ${normalized} does not support pairing`);
        }
        return normalized;
    }
    // Allow extension channels: validate format but don't require registry
    if (/^[a-z][a-z0-9_-]{0,63}$/.test(value))
        return value;
    throw new Error(`Invalid channel: ${value}`);
}
async function notifyApproved(channel, id) {
    const cfg = loadConfig();
    await notifyPairingApproved({ channelId: channel, id, cfg });
}
export function registerPairingCli(program) {
    const channels = listPairingChannels();
    const pairing = program
        .command("pairing")
        .description("安全 DM 配对（批准入站请求）")
        .addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/pairing", "docs.clawd.bot/cli/pairing")}\n`);
    pairing
        .command("list")
        .description("List pending pairing requests")
        .option("--channel <channel>", `Channel (${channels.join(", ")})`)
        .argument("[channel]", `Channel (${channels.join(", ")})`)
        .option("--json", "Print JSON", false)
        .action(async (channelArg, opts) => {
        const channelRaw = opts.channel ?? channelArg;
        if (!channelRaw) {
            throw new Error(`Channel required. Use --channel <channel> or pass it as the first argument (expected one of: ${channels.join(", ")})`);
        }
        const channel = parseChannel(channelRaw, channels);
        const requests = await listChannelPairingRequests(channel);
        if (opts.json) {
            defaultRuntime.log(JSON.stringify({ channel, requests }, null, 2));
            return;
        }
        if (requests.length === 0) {
            defaultRuntime.log(theme.muted(`No pending ${channel} pairing requests.`));
            return;
        }
        const idLabel = resolvePairingIdLabel(channel);
        const tableWidth = Math.max(60, (process.stdout.columns ?? 120) - 1);
        defaultRuntime.log(`${theme.heading("Pairing requests")} ${theme.muted(`(${requests.length})`)}`);
        defaultRuntime.log(renderTable({
            width: tableWidth,
            columns: [
                { key: "Code", header: "Code", minWidth: 10 },
                { key: "ID", header: idLabel, minWidth: 12, flex: true },
                { key: "Meta", header: "Meta", minWidth: 8, flex: true },
                { key: "Requested", header: "Requested", minWidth: 12 },
            ],
            rows: requests.map((r) => ({
                Code: r.code,
                ID: r.id,
                Meta: r.meta ? JSON.stringify(r.meta) : "",
                Requested: r.createdAt,
            })),
        }).trimEnd());
    });
    pairing
        .command("approve")
        .description("Approve a pairing code and allow that sender")
        .option("--channel <channel>", `Channel (${channels.join(", ")})`)
        .argument("<codeOrChannel>", "Pairing code (or channel when using 2 args)")
        .argument("[code]", "Pairing code (when channel is passed as the 1st arg)")
        .option("--notify", "Notify the requester on the same channel", false)
        .action(async (codeOrChannel, code, opts) => {
        const channelRaw = opts.channel ?? codeOrChannel;
        const resolvedCode = opts.channel ? codeOrChannel : code;
        if (!opts.channel && !code) {
            throw new Error(`Usage: ${formatCliCommand("openclaw-cn pairing approve <channel> <code>")} (or: ${formatCliCommand("openclaw-cn pairing approve --channel <channel> <code>")})`);
        }
        if (opts.channel && code != null) {
            throw new Error(`Too many arguments. Use: ${formatCliCommand("openclaw-cn pairing approve --channel <channel> <code>")}`);
        }
        const channel = parseChannel(channelRaw, channels);
        const approved = await approveChannelPairingCode({
            channel,
            code: String(resolvedCode),
        });
        if (!approved) {
            throw new Error(`No pending pairing request found for code: ${String(resolvedCode)}`);
        }
        defaultRuntime.log(`${theme.success("Approved")} ${theme.muted(channel)} sender ${theme.command(approved.id)}.`);
        if (!opts.notify)
            return;
        await notifyApproved(channel, approved.id).catch((err) => {
            defaultRuntime.log(theme.warn(`Failed to notify requester: ${String(err)}`));
        });
    });
}
