/**
 * Todoist Idea Capture (kb#170 component 1) — registers `/idea <text>` on
 * Adolf's Matrix channel.
 *
 * Same reasoning as quota-command-openclaw-plugin (kb#62): OpenClaw's
 * native-command dispatch (`api.registerCommand`) runs BEFORE the agent
 * turn, so this never spends a Kimi turn. That property is not incidental
 * here — it's the whole point of kb#170's "encoder-only, not an LLM call"
 * design: classification runs on bge-m3 (agap-mcp/src/classifier.js), and
 * routing the capture through a native command means the ENTIRE
 * capture -> classify -> Todoist path costs zero model tokens, not just the
 * classification step.
 *
 * This plugin does no classification itself — it POSTs the raw text to
 * agap-mcp's /capture-idea endpoint (same container agap-mcp already
 * exposes at :3100 for the MCP tool surface; this is a second, plain-REST
 * entry point to the same todoistCaptureIdea() function, added because a
 * native command handler is simplest calling plain JSON over HTTP rather
 * than speaking MCP JSON-RPC to invoke its own tool). See agap-mcp/src/
 * capture.js for the classify+create logic and agap-mcp/src/server.js for
 * the /capture-idea route.
 *
 * Gating: requireAuth: true (the registerCommand default) restricts the
 * command to the same Matrix DM allowlist (channels.matrix.dm.allowFrom in
 * openclaw.json) that already gates every other interaction with Adolf —
 * no separate tier needed, this creates a task in the operator's own
 * Todoist inbox, not a privileged/destructive action.
 */
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";

// agap-mcp is a sibling reached via host.docker.internal, same mapping
// openclaw.json's mcp.servers.agap.url already uses for this container.
const CAPTURE_URL = "http://host.docker.internal:3100/capture-idea";
const FETCH_TIMEOUT_MS = 15000; // bge-m3 embed + Todoist create can take a few seconds

// kb#180: agap-mcp's :3100 listener is authenticated now — /capture-idea is
// no longer an open REST endpoint (it never should have been: it reaches
// Todoist writes from any LAN peer). This plugin runs inside the adolf
// container, so it presents Adolf's own agap-mcp bearer token, injected as
// AGAP_MCP_TOKEN by openai/docker-compose.yml from .env (never inlined
// here). If the var is unset the request goes out unauthenticated and
// agap-mcp answers 401 — a visible failure of /idea, not a silent one.
const AGAP_MCP_TOKEN = process.env.AGAP_MCP_TOKEN || "";

async function captureIdea(text) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
  try {
    const res = await fetch(CAPTURE_URL, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        ...(AGAP_MCP_TOKEN ? { Authorization: `Bearer ${AGAP_MCP_TOKEN}` } : {}),
      },
      body: JSON.stringify({ text }),
      signal: controller.signal,
    });
    const body = await res.json().catch(() => ({}));
    if (!res.ok) throw new Error(body?.error || `agap-mcp /capture-idea HTTP ${res.status}`);
    return body;
  } finally {
    clearTimeout(timer);
  }
}

function formatReply({ task, classification }) {
  const bits = [
    `area=${classification.area.label}`,
    `urgency=${classification.urgency.label}`,
  ];
  if (classification.decompose.label === "needs-decomposition") bits.push("требует декомпозиции в Kanboard");
  if (classification.area.ambiguous) bits.push("область — неточно, уточни при ревью");
  return `Записал в Todoist: «${task.content}» (${bits.join(", ")}).`;
}

export default definePluginEntry({
  id: "todoist-capture",
  name: "Todoist Idea Capture",
  description:
    "LLM-free /idea command: classifies free text via agap-mcp (local bge-m3, no Kimi call) and creates a labelled Todoist task.",
  register(api) {
    api.registerCommand({
      name: "idea",
      description: "Capture an idea/quick task -> classified (area/urgency/decompose) and filed in Todoist. No Kimi call.",
      acceptsArgs: true,
      requireAuth: true,
      handler: async (ctx) => {
        const text = (ctx.args || "").trim();
        if (!text) {
          return { text: "Использование: /idea <текст идеи>", suppressReply: true };
        }
        try {
          const result = await captureIdea(text);
          return { text: formatReply(result), suppressReply: true };
        } catch (e) {
          api.logger?.warn?.(`todoist-capture: capture failed (${e?.message || e})`);
          return { text: `Не удалось захватить идею: ${e?.message || e}`, suppressReply: true };
        }
      },
    });
  },
});
