/**
 * WhatsApp Bridge — connects to WhatsApp via Baileys.
 *
 * Architecture (production-grade, modeled after Evolution API + OpenClaw):
 *
 * 1. CRASH, DON'T ZOMBIE: when Baileys can't reconnect after exhausting
 *    retries, process.exit(1) — let the parent (Python health loop) restart.
 *    A zombie HTTP server that responds 200 but can't send/receive is worse
 *    than a dead process.
 *
 * 2. TWO-TIER HEALTH: /status returns HTTP 503 when WA socket is not open.
 *    The parent's health loop distinguishes "process dead" (no HTTP) from
 *    "process alive but WA disconnected" (503) and acts on both.
 *
 * 3. SOCKET CLEANUP: every startWhatsApp() call closes the previous socket's
 *    event listeners before creating a new one. No listener accumulation.
 *
 * 4. PERSISTED FAILURE STATE: bridge writes connect/disconnect events to
 *    a state file so the parent can track cumulative failures across restarts
 *    and enforce ban-protection cooldowns.
 *
 * 5. FULL SESSION WIPE on auth failure: delete the entire session directory
 *    contents, not just creds.json. Partial cleanup leaves ghost state.
 *
 * Config via env vars:
 *   WHATSAPP_MODE        = "dedicated" | "personal" (default: personal)
 *   WHATSAPP_ALLOWED     = comma-separated numbers (empty = allow all)
 *   WHATSAPP_BRIDGE_PORT = HTTP port (default: 8787)
 *   AGNTSPACE_BACKEND    = backend URL (default: http://127.0.0.1:8000)
 */

import makeWASocket, {
  DisconnectReason,
  useMultiFileAuthState,
  fetchLatestBaileysVersion,
  downloadContentFromMessage,
} from "@whiskeysockets/baileys";
import { createServer } from "http";
import {
  existsSync,
  mkdirSync,
  readdirSync,
  unlinkSync,
  readFileSync,
  writeFileSync,
} from "fs";
import { join } from "path";
import pino from "pino";
import { appendFileSync } from "fs";

// ---------------------------------------------------------------------------
// Config
// ---------------------------------------------------------------------------

const AGNTSPACE_HOME =
  process.env.AGNTSPACE_HOME ||
  join(process.env.HOME || process.env.USERPROFILE, ".agntspace");
const SESSION_DIR = join(AGNTSPACE_HOME, "data", "whatsapp-session");
const STATE_FILE = join(AGNTSPACE_HOME, "data", "whatsapp-bridge-state.json");
const BACKEND_URL = process.env.AGNTSPACE_BACKEND || "http://127.0.0.1:8000";
const BRIDGE_PORT = parseInt(process.env.WHATSAPP_BRIDGE_PORT || "8787", 10);
let MODE = process.env.WHATSAPP_MODE || "personal";
const ALLOWED_NUMBERS = (process.env.WHATSAPP_ALLOWED || "")
  .split(",")
  .filter(Boolean);

const logger = pino({ level: "warn" });
const sentByBridge = new Set(); // track message IDs we sent, to avoid loops

// ---------------------------------------------------------------------------
// Mutable connection state
// ---------------------------------------------------------------------------

let sock = null;
let currentQR = null;
let connected = false;
let selfJid = null;
let reconnectAttempts = 0;
let lastConnectedAt = null;          // ISO timestamp of last successful open
let disconnectedSince = null;        // ISO timestamp when we went disconnected
let exitReason = null;               // why the bridge is exiting (for parent)

const MAX_RECONNECT_ATTEMPTS = 10;
const MAX_RECONNECT_DELAY = 60000;   // 1 minute cap per attempt

// ---------------------------------------------------------------------------
// Logging
// ---------------------------------------------------------------------------

function bridgeLog(msg) {
  const line = `[${new Date().toISOString()}] ${msg}\n`;
  console.log(msg);
  try {
    appendFileSync(join(AGNTSPACE_HOME, "whatsapp-bridge.log"), line);
  } catch {
    // Best-effort logging
  }
}

// ---------------------------------------------------------------------------
// Persisted state — survives process restarts so parent can track cumulative
// failures and enforce ban-protection cooldowns.
// ---------------------------------------------------------------------------

function loadBridgeState() {
  try {
    if (existsSync(STATE_FILE)) {
      return JSON.parse(readFileSync(STATE_FILE, "utf8"));
    }
  } catch {
    // Corrupted file — start fresh
  }
  return {
    cumulative_failures: 0,       // total failed restart cycles
    last_failure_ts: null,        // ISO — when last failure cycle ended
    last_connected_ts: null,      // ISO — when WA was last successfully open
    last_exit_reason: null,       // why the previous process exited
    session_wipes: 0,             // how many times we wiped the session dir
    last_session_wipe_ts: null,
    connect_disconnect_events: 0, // events in current hour window
    hour_window_start: null,      // ISO — start of current hour tracking window
  };
}

function saveBridgeState(state) {
  try {
    const dir = join(AGNTSPACE_HOME, "data");
    if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
    writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
  } catch (err) {
    bridgeLog(`Failed to save bridge state: ${err.message}`);
  }
}

function recordConnectEvent(state) {
  const now = new Date().toISOString();
  const hourAgo = new Date(Date.now() - 3600_000).toISOString();

  // Reset hour window if stale
  if (!state.hour_window_start || state.hour_window_start < hourAgo) {
    state.hour_window_start = now;
    state.connect_disconnect_events = 0;
  }
  state.connect_disconnect_events++;
  state.last_connected_ts = now;
  saveBridgeState(state);
}

function recordDisconnectEvent(state, reason) {
  const now = new Date().toISOString();
  const hourAgo = new Date(Date.now() - 3600_000).toISOString();

  if (!state.hour_window_start || state.hour_window_start < hourAgo) {
    state.hour_window_start = now;
    state.connect_disconnect_events = 0;
  }
  state.connect_disconnect_events++;
  state.cumulative_failures++;
  state.last_failure_ts = now;
  state.last_exit_reason = reason;
  saveBridgeState(state);
}

// ---------------------------------------------------------------------------
// Session wipe — full directory, not just creds.json
// ---------------------------------------------------------------------------

function wipeSessionDir(state) {
  bridgeLog("Wiping entire session directory for fresh QR scan...");
  try {
    for (const f of readdirSync(SESSION_DIR)) {
      try {
        unlinkSync(join(SESSION_DIR, f));
      } catch {
        // Best-effort per file
      }
    }
    state.session_wipes++;
    state.last_session_wipe_ts = new Date().toISOString();
    saveBridgeState(state);
    bridgeLog("Session directory wiped.");
  } catch (err) {
    bridgeLog(`Session wipe error: ${err.message}`);
  }
}

// ---------------------------------------------------------------------------
// Socket cleanup — prevent event listener accumulation
// ---------------------------------------------------------------------------

function cleanupSocket() {
  if (sock) {
    try {
      sock.ev.removeAllListeners("connection.update");
      sock.ev.removeAllListeners("creds.update");
      sock.ev.removeAllListeners("messages.upsert");
      sock.end(undefined);
    } catch {
      // Socket might already be dead
    }
    sock = null;
  }
}

// ---------------------------------------------------------------------------
// Ban protection: if >10 connect/disconnect cycles in one hour, refuse to
// connect and exit with a specific code so the parent knows to cooldown.
// ---------------------------------------------------------------------------

const BAN_RISK_THRESHOLD = 10;
const EXIT_CODE_BAN_RISK = 2;       // parent reads this to start cooldown
const EXIT_CODE_EXHAUSTED = 1;      // normal "retry exhausted" exit

// ---------------------------------------------------------------------------
// Main WhatsApp connection
// ---------------------------------------------------------------------------

const bridgeState = loadBridgeState();

if (!existsSync(SESSION_DIR)) {
  mkdirSync(SESSION_DIR, { recursive: true });
}

async function startWhatsApp() {
  // Ban protection check
  if (bridgeState.connect_disconnect_events >= BAN_RISK_THRESHOLD) {
    const hourAgo = new Date(Date.now() - 3600_000).toISOString();
    if (bridgeState.hour_window_start && bridgeState.hour_window_start >= hourAgo) {
      bridgeLog(
        `BAN RISK: ${bridgeState.connect_disconnect_events} connect/disconnect cycles ` +
        `in the last hour. Refusing to connect. Exiting with code ${EXIT_CODE_BAN_RISK}.`
      );
      exitReason = "ban_risk_cooldown";
      bridgeState.last_exit_reason = exitReason;
      saveBridgeState(bridgeState);
      process.exit(EXIT_CODE_BAN_RISK);
    }
  }

  // Clean up previous socket before creating a new one
  cleanupSocket();

  const { state, saveCreds } = await useMultiFileAuthState(SESSION_DIR);

  // Fetch latest Baileys version with retry
  let version;
  for (let attempt = 0; attempt < 3; attempt++) {
    try {
      ({ version } = await fetchLatestBaileysVersion());
      break;
    } catch (err) {
      bridgeLog(
        `fetchLatestBaileysVersion failed (attempt ${attempt + 1}): ${err.message}`
      );
      if (attempt === 2) {
        version = [2, 3000, 1015901307];
        bridgeLog("Using fallback Baileys version");
      } else {
        await new Promise((r) => setTimeout(r, 3000 * (attempt + 1)));
      }
    }
  }

  sock = makeWASocket({
    version,
    auth: state,
    logger,
    browser: ["AgntSpace", "Desktop", "1.0.0"],
  });

  sock.ev.on("connection.update", (update) => {
    const { connection, lastDisconnect, qr } = update;

    if (qr) {
      currentQR = qr;
      console.log("[WhatsApp] QR code ready — scan in Settings");
    }

    if (connection === "close") {
      connected = false;
      currentQR = null;
      disconnectedSince = disconnectedSince || new Date().toISOString();

      const statusCode = lastDisconnect?.error?.output?.statusCode;
      const errorMsg = lastDisconnect?.error?.message || "";

      // Detect unrecoverable auth failures
      const isAuthFailure =
        statusCode === DisconnectReason.loggedOut ||
        statusCode === 401 ||
        errorMsg.includes("Bad MAC") ||
        errorMsg.includes("Bad encrypted message") ||
        errorMsg.includes("Failed to decrypt");

      if (isAuthFailure) {
        bridgeLog(
          `Auth failure (status=${statusCode}, msg="${errorMsg.substring(0, 100)}")`
        );
        recordDisconnectEvent(bridgeState, `auth_failure:${statusCode}`);
        cleanupSocket();
        wipeSessionDir(bridgeState);

        // Check if we've wiped too many times recently (possible account issue)
        if (bridgeState.session_wipes > 3) {
          bridgeLog(
            `Session wiped ${bridgeState.session_wipes} times. Possible account issue. Exiting.`
          );
          exitReason = "repeated_auth_failure";
          bridgeState.last_exit_reason = exitReason;
          saveBridgeState(bridgeState);
          process.exit(EXIT_CODE_EXHAUSTED);
        }

        // Restart after delay for fresh QR
        setTimeout(startWhatsApp, 3000);
        return;
      }

      // Transient disconnect — reconnect with exponential backoff
      reconnectAttempts++;
      recordDisconnectEvent(bridgeState, `transient:${statusCode || "unknown"}`);

      if (reconnectAttempts > MAX_RECONNECT_ATTEMPTS) {
        bridgeLog(
          `Exhausted ${MAX_RECONNECT_ATTEMPTS} reconnect attempts. ` +
          `Exiting with code ${EXIT_CODE_EXHAUSTED} for parent to restart.`
        );
        exitReason = "reconnect_exhausted";
        bridgeState.last_exit_reason = exitReason;
        saveBridgeState(bridgeState);
        // CRITICAL: exit instead of leaving a zombie HTTP server
        process.exit(EXIT_CODE_EXHAUSTED);
      }

      const delay = Math.min(
        3000 * Math.pow(2, reconnectAttempts - 1),
        MAX_RECONNECT_DELAY
      );
      console.log(
        `[WhatsApp] Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...`
      );
      setTimeout(startWhatsApp, delay);
    }

    if (connection === "open") {
      connected = true;
      currentQR = null;
      reconnectAttempts = 0;
      disconnectedSince = null;
      lastConnectedAt = new Date().toISOString();
      selfJid = sock.user?.id;

      recordConnectEvent(bridgeState);
      // Reset session wipe counter on successful connect (session is good)
      bridgeState.session_wipes = 0;
      saveBridgeState(bridgeState);

      const selfNum = selfJid
        ?.replace(/:\d+@/, "@")
        .replace("@s.whatsapp.net", "");
      console.log(
        `[WhatsApp] Connected! (${MODE} mode, self: ${selfNum})`
      );
    }
  });

  sock.ev.on("creds.update", saveCreds);

  // Handle incoming messages
  sock.ev.on("messages.upsert", async ({ messages }) => {
    for (const msg of messages) {
      if (!msg.message) continue;

      // Skip messages sent by this bridge (prevents infinite loop)
      if (sentByBridge.has(msg.key.id)) {
        sentByBridge.delete(msg.key.id);
        continue;
      }

      const sender = msg.key.remoteJid;
      const fromMe = msg.key.fromMe;
      const isGroup = sender?.endsWith("@g.us");

      bridgeLog(
        `MSG: sender=${sender} fromMe=${fromMe} selfJid=${selfJid} id=${msg.key.id} type=${Object.keys(msg.message).join(",")}`
      );

      // Skip status broadcasts and group messages (for now)
      if (!sender || sender === "status@broadcast" || isGroup) continue;

      // Mode handling
      if (MODE === "personal") {
        // Personal mode: only process messages FROM ourselves (self-chat)
        if (!fromMe) {
          const senderNum = sender.replace("@s.whatsapp.net", "");
          if (
            ALLOWED_NUMBERS.length > 0 &&
            !ALLOWED_NUMBERS.includes(senderNum)
          ) {
            continue;
          }
          if (ALLOWED_NUMBERS.length === 0) {
            continue;
          }
        }
      } else {
        // Dedicated mode
        if (fromMe) {
          const selfNormalized = selfJid
            ? selfJid.replace(/:(\d+)@/, "@")
            : null;
          const isSelfChat =
            (selfNormalized && sender === selfNormalized) ||
            sender?.endsWith("@lid");
          if (!isSelfChat) {
            bridgeLog(`SKIP: outgoing to ${sender}`);
            continue;
          }
        }

        if (ALLOWED_NUMBERS.length > 0) {
          const senderNum = sender.replace("@s.whatsapp.net", "");
          if (!ALLOWED_NUMBERS.includes(senderNum)) {
            console.log(
              `[WhatsApp] Blocked: ${senderNum} (not in allowlist)`
            );
            continue;
          }
        }
      }

      // Extract text from various message types
      const text =
        msg.message.conversation ||
        msg.message.extendedTextMessage?.text ||
        msg.message.imageMessage?.caption ||
        msg.message.videoMessage?.caption ||
        msg.message.documentMessage?.caption ||
        "";

      // Check for voice message / audio
      const audioMsg = msg.message.audioMessage;
      let audioBase64 = null;
      let audioDuration = 0;

      if (audioMsg) {
        try {
          bridgeLog(
            `Voice message (${audioMsg.seconds}s, ${audioMsg.mimetype})`
          );
          const stream = await downloadContentFromMessage(audioMsg, "audio");
          const chunks = [];
          for await (const chunk of stream) {
            chunks.push(chunk);
          }
          const buffer = Buffer.concat(chunks);
          audioBase64 = buffer.toString("base64");
          audioDuration = audioMsg.seconds || 0;
        } catch (err) {
          bridgeLog(`Failed to download voice message: ${err.message}`);
        }
      }

      // Check for image
      const imageMsg = msg.message.imageMessage;
      let imageBase64 = null;
      let imageMimetype = null;

      if (imageMsg) {
        try {
          bridgeLog(
            `Image message (${imageMsg.mimetype}, ${imageMsg.fileLength || "?"} bytes)`
          );
          const stream = await downloadContentFromMessage(imageMsg, "image");
          const chunks = [];
          for await (const chunk of stream) {
            chunks.push(chunk);
          }
          const buffer = Buffer.concat(chunks);
          imageBase64 = buffer.toString("base64");
          imageMimetype = imageMsg.mimetype || "image/jpeg";
        } catch (err) {
          bridgeLog(`Failed to download image: ${err.message}`);
        }
      }

      // Check for document
      const docMsg = msg.message.documentMessage;
      let docName = null;
      let docBase64 = null;
      let docMimetype = null;

      if (docMsg) {
        docName = docMsg.fileName || "document";
        docMimetype = docMsg.mimetype || "application/octet-stream";
        bridgeLog(
          `Document: ${docName} (${docMimetype}, ${docMsg.fileLength || "?"} bytes)`
        );

        const extractable = [
          "application/pdf",
          "text/plain",
          "text/csv",
          "text/html",
          "text/markdown",
          "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
          "application/msword",
          "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
          "application/vnd.ms-excel",
        ];
        const maxSize = 10 * 1024 * 1024;
        const fileSize = docMsg.fileLength || 0;

        if (extractable.includes(docMimetype) && fileSize < maxSize) {
          try {
            const stream = await downloadContentFromMessage(
              docMsg,
              "document"
            );
            const chunks = [];
            for await (const chunk of stream) {
              chunks.push(chunk);
            }
            const buffer = Buffer.concat(chunks);
            docBase64 = buffer.toString("base64");
            bridgeLog(
              `Document downloaded: ${docName} (${buffer.length} bytes)`
            );
          } catch (err) {
            bridgeLog(`Failed to download document: ${err.message}`);
          }
        } else {
          bridgeLog(
            `Document skipped (${fileSize > maxSize ? "too large" : "unsupported format"})`
          );
        }
      }

      // Check for video
      const videoMsg = msg.message.videoMessage;
      let videoBase64 = null;
      let videoMimetype = null;

      if (videoMsg) {
        videoMimetype = videoMsg.mimetype || "video/mp4";
        const maxVideoSize = 20 * 1024 * 1024;
        const videoSize = videoMsg.fileLength || 0;
        bridgeLog(
          `Video message (${videoMimetype}, ${videoSize} bytes, ${videoMsg.seconds || "?"}s)`
        );

        if (videoSize < maxVideoSize) {
          try {
            const stream = await downloadContentFromMessage(
              videoMsg,
              "video"
            );
            const chunks = [];
            for await (const chunk of stream) {
              chunks.push(chunk);
            }
            const buffer = Buffer.concat(chunks);
            videoBase64 = buffer.toString("base64");
            bridgeLog(`Video downloaded: ${buffer.length} bytes`);
          } catch (err) {
            bridgeLog(`Failed to download video: ${err.message}`);
          }
        }
      }

      // Check for sticker
      const stickerMsg = msg.message.stickerMessage;
      let stickerBase64 = null;

      if (stickerMsg && !text && !imageBase64 && !audioBase64) {
        try {
          const stream = await downloadContentFromMessage(
            stickerMsg,
            "sticker"
          );
          const chunks = [];
          for await (const chunk of stream) {
            chunks.push(chunk);
          }
          const buffer = Buffer.concat(chunks);
          stickerBase64 = buffer.toString("base64");
          bridgeLog(
            `Sticker downloaded (${stickerMsg.mimetype}, ${buffer.length} bytes)`
          );
        } catch (err) {
          bridgeLog(`Failed to download sticker: ${err.message}`);
        }
      }

      // Skip if nothing to process
      if (
        !text &&
        !audioBase64 &&
        !imageBase64 &&
        !docMsg &&
        !videoMsg &&
        !stickerMsg
      )
        continue;

      // For LID self-chat, use our own phone number
      let senderNum;
      if (sender?.endsWith("@lid") && fromMe && selfJid) {
        senderNum = selfJid.replace(/:(\d+)@.*/, "");
      } else {
        senderNum = sender.replace("@s.whatsapp.net", "");
      }
      const contentDesc = text
        ? `"${text.substring(0, 60)}"`
        : imageBase64
          ? "[image]"
          : audioBase64
            ? `[voice ${audioDuration}s]`
            : videoBase64
              ? "[video]"
              : stickerBase64
                ? "[sticker]"
                : docName
                  ? `[doc: ${docName}]`
                  : "[unknown]";
      bridgeLog(
        `FORWARDING: from=${fromMe ? "self" : senderNum} ${contentDesc}`
      );

      // Forward to AgntSpace backend
      try {
        const payload = {
          sender: senderNum,
          text: text || "",
          from_me: fromMe,
          timestamp: new Date().toISOString(),
          message_id: msg.key.id,
        };

        if (audioBase64) {
          payload.audio_base64 = audioBase64;
          payload.audio_duration = audioDuration;
          payload.audio_mimetype = audioMsg.mimetype || "audio/ogg";
        }

        if (imageBase64) {
          payload.image_base64 = imageBase64;
          payload.image_mimetype = imageMimetype;
        }

        if (docMsg) {
          payload.document_name = docName;
          payload.document_mimetype = docMimetype;
          if (docBase64) {
            payload.document_base64 = docBase64;
          }
        }

        if (videoBase64) {
          payload.video_base64 = videoBase64;
          payload.video_mimetype = videoMimetype;
          payload.video_duration = videoMsg.seconds || 0;
        }

        if (stickerBase64) {
          payload.sticker_base64 = stickerBase64;
          payload.sticker_mimetype = stickerMsg.mimetype || "image/webp";
        }

        const res = await fetch(
          `${BACKEND_URL}/api/channels/whatsapp/incoming`,
          {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(payload),
          }
        );
        const data = await res.json();

        if (data.reply) {
          bridgeLog(
            `REPLY: to=${sender} text="${data.reply.substring(0, 80)}"`
          );
          const sent = await sock.sendMessage(sender, { text: data.reply });
          if (sent?.key?.id) sentByBridge.add(sent.key.id);
        } else if (data.error) {
          bridgeLog(`ERROR from backend: ${data.error}`);
        }
      } catch (err) {
        bridgeLog(`Backend error: ${err.message}`);
      }
    }
  });
}

// ---------------------------------------------------------------------------
// HTTP server — two-tier health
// ---------------------------------------------------------------------------

const server = createServer(async (req, res) => {
  const url = new URL(req.url, `http://localhost:${BRIDGE_PORT}`);
  res.setHeader("Content-Type", "application/json");
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");

  if (req.method === "OPTIONS") {
    res.writeHead(200);
    res.end();
    return;
  }

  if (url.pathname === "/status") {
    // TWO-TIER HEALTH: 200 only when WA is connected, 503 otherwise.
    // Parent health loop uses HTTP status code to distinguish:
    //   200 = fully operational (WA connected)
    //   503 = process alive but WA disconnected (trigger circuit breaker)
    const statusCode = connected ? 200 : 503;
    res.writeHead(statusCode);
    res.end(
      JSON.stringify({
        connected,
        qr_available: !!currentQR,
        mode: MODE,
        self:
          selfJid
            ?.replace(/:\d+@/, "@")
            .replace("@s.whatsapp.net", "") || null,
        reconnect_attempts: reconnectAttempts,
        last_connected_at: lastConnectedAt,
        disconnected_since: disconnectedSince,
        exit_reason: exitReason,
        cumulative_failures: bridgeState.cumulative_failures,
        connect_disconnect_events_this_hour:
          bridgeState.connect_disconnect_events,
      })
    );
    return;
  }

  if (url.pathname === "/health") {
    // Liveness probe — always 200 if HTTP server is up
    res.writeHead(200);
    res.end(JSON.stringify({ alive: true, connected, pid: process.pid }));
    return;
  }

  if (url.pathname === "/qr") {
    res.writeHead(200);
    if (currentQR) {
      res.end(JSON.stringify({ qr: currentQR }));
    } else if (connected) {
      res.end(JSON.stringify({ qr: null, status: "connected" }));
    } else {
      res.end(JSON.stringify({ qr: null, status: "waiting" }));
    }
    return;
  }

  if (url.pathname === "/mode" && req.method === "POST") {
    let body = "";
    for await (const chunk of req) body += chunk;
    try {
      const { mode } = JSON.parse(body);
      if (mode === "personal" || mode === "dedicated") {
        MODE = mode;
        console.log(`[WhatsApp] Mode switched to: ${MODE}`);
        res.writeHead(200);
        res.end(JSON.stringify({ mode: MODE }));
      } else {
        res.writeHead(400);
        res.end(
          JSON.stringify({
            error: "Invalid mode. Use 'personal' or 'dedicated'",
          })
        );
      }
    } catch (err) {
      res.writeHead(400);
      res.end(JSON.stringify({ error: err.message }));
    }
    return;
  }

  if (url.pathname === "/send" && req.method === "POST") {
    let body = "";
    for await (const chunk of req) body += chunk;
    try {
      const { to, text } = JSON.parse(body);
      if (!sock || !connected) {
        res.writeHead(503);
        res.end(JSON.stringify({ error: "Not connected" }));
        return;
      }
      const jid = to.includes("@") ? to : `${to}@s.whatsapp.net`;
      const sent = await sock.sendMessage(jid, { text });
      if (sent?.key?.id) sentByBridge.add(sent.key.id);
      res.writeHead(200);
      res.end(JSON.stringify({ sent: true }));
    } catch (err) {
      res.writeHead(500);
      res.end(JSON.stringify({ error: err.message }));
    }
    return;
  }

  res.writeHead(404);
  res.end(JSON.stringify({ error: "Not found" }));
});

server.listen(BRIDGE_PORT, "127.0.0.1", () => {
  console.log(
    `[WhatsApp] Bridge on http://127.0.0.1:${BRIDGE_PORT} (${MODE} mode)`
  );
  startWhatsApp();
});

// ---------------------------------------------------------------------------
// Graceful shutdown — close socket without logging out (preserves session)
// ---------------------------------------------------------------------------

async function shutdown(signal) {
  console.log(`[WhatsApp] ${signal} received, shutting down gracefully...`);
  try {
    cleanupSocket();
  } catch (e) {
    console.log("[WhatsApp] Cleanup error (non-fatal):", e.message);
  }
  server.close();
  process.exit(0);
}

process.on("SIGTERM", () => shutdown("SIGTERM"));
process.on("SIGINT", () => shutdown("SIGINT"));
