// Discord adapter for DewLink — bot token over the gateway websocket (receive) + REST (send).
// Emits the same @@DEWLINK@@ events as the other channels; each Discord channel/DM = a conversation.
//
// Env: DISCORD_BOT_TOKEN

import WebSocket from "ws";
import { emitEvent } from "./events.ts";

const TOKEN = process.env.DISCORD_BOT_TOKEN || "";
const API = "https://discord.com/api/v10";
// GUILDS | GUILD_MESSAGES | DIRECT_MESSAGES | MESSAGE_CONTENT
const INTENTS = (1 << 0) | (1 << 9) | (1 << 12) | (1 << 15);

let ws: WebSocket | null = null;
let seq: number | null = null;
let heartbeat: ReturnType<typeof setInterval> | null = null;
let running = false;

function log(level: "info" | "warn" | "error", msg: string) {
  emitEvent({ type: "log", level, msg: `[discord] ${msg}` });
}

export async function startDiscord() {
  if (!TOKEN) { log("error", "missing DISCORD_BOT_TOKEN"); emitEvent({ type: "status", state: "logged_out" }); return; }
  running = true;
  connect();
}

function connect() {
  emitEvent({ type: "status", state: "connecting" });
  ws = new WebSocket("wss://gateway.discord.gg/?v=10&encoding=json");
  ws.on("message", (raw: WebSocket.RawData) => {
    let p: any;
    try { p = JSON.parse(raw.toString()); } catch { return; }
    if (p.s != null) seq = p.s;
    if (p.op === 10) {                       // HELLO → start heartbeat + identify
      const interval = p.d.heartbeat_interval;
      if (heartbeat) clearInterval(heartbeat);
      heartbeat = setInterval(() => ws?.send(JSON.stringify({ op: 1, d: seq })), interval);
      ws?.send(JSON.stringify({
        op: 2,
        d: { token: TOKEN, intents: INTENTS, properties: { os: "macos", browser: "dewx", device: "dewx" } },
      }));
    } else if (p.op === 0) {                  // dispatch
      if (p.t === "READY") emitEvent({ type: "status", state: "open", user: p.d?.user?.username ?? null });
      else if (p.t === "MESSAGE_CREATE") handleMessage(p.d);
    } else if (p.op === 7 || p.op === 9) {    // reconnect / invalid session
      reconnect();
    }
  });
  ws.on("close", () => { emitEvent({ type: "status", state: "close" }); if (running) reconnect(); });
  ws.on("error", (e: any) => log("warn", `ws: ${e?.message}`));
}

function reconnect() {
  if (heartbeat) { clearInterval(heartbeat); heartbeat = null; }
  try { ws?.removeAllListeners(); ws?.close(); } catch { /* ignore */ }
  if (running) setTimeout(connect, 3000);
}

function handleMessage(m: any) {
  if (!m || !m.channel_id) return;
  const author = m.author || {};
  if (author.bot) return;   // skip bot/self echoes
  const name = author.global_name || author.username || String(m.channel_id);
  const text = m.content
    || (m.attachments?.length ? "[Attachment]" : (m.embeds?.length ? "[Embed]" : "[Message]"));
  emitEvent({
    type: "message",
    convExternalId: String(m.channel_id),
    convTitle: name,
    direction: "IN",
    body: text,
    sender: name,
    ts: m.timestamp ? new Date(m.timestamp).getTime() : Date.now(),
    providerMsgId: `dc:${m.id}`,
    historical: false,
  });
}

/** React to a message with a unicode emoji (the bot's own reaction). User-initiated. */
export async function reactDiscord(channelId: string, messageId: string, emoji: string) {
  const e = encodeURIComponent(emoji);
  const res = await fetch(`${API}/channels/${channelId}/messages/${messageId}/reactions/${e}/@me`, {
    method: "PUT",
    headers: { Authorization: `Bot ${TOKEN}` },
  });
  if (!res.ok && res.status !== 204) throw new Error(`discord react ${res.status}`);
}

export async function sendDiscord(channelId: string, text: string) {
  const res = await fetch(`${API}/channels/${channelId}/messages`, {
    method: "POST",
    headers: { Authorization: `Bot ${TOKEN}`, "Content-Type": "application/json" },
    body: JSON.stringify({ content: text }),
  });
  if (!res.ok) throw new Error(`discord send ${res.status}`);
  return res.json();
}
