"use strict";
// Módulo KARVAN (self-contained) para Oasis Mobile — inspirado en karvan-protocol
// (packages/core/addons/ephemeral.js): salas EFÍMERAS en RAM que se AUTODESTRUYEN por TTL.
// - Nada se escribe a disco. Los mensajes viven solo en memoria y desaparecen con la sala.
// - TTL de inactividad (idle) que se resetea con cada actividad + TTL absoluto que nunca se resetea.
// - Mailbox RAM para señalización WebRTC (data-channel) entre pares.
// No depende de @karvan/core ni de cripto nativa: es una capa local sobre el backend de Oasis.

const crypto = require("crypto");

const DEFAULT_IDLE_TTL_MS = 30 * 60 * 1000;      // 30 min sin actividad -> autodestrucción
const DEFAULT_ABS_TTL_MS = 2 * 60 * 60 * 1000;   // 2 h absoluto (tope)
const MAX_ABS_TTL_MS = 24 * 60 * 60 * 1000;      // nunca más de 24 h
const MAX_MSGS = 250;                            // anillo: solo los últimos N en RAM
const MAX_SIGNALS = 500;
const MAX_ROOMS = 100;
const MAX_MEMBERS = 50;                          // tope de miembros por sala (evita crecimiento sin límite del Set)
const MAX_SIGNAL_BYTES = 16384;                  // SDP/ICE son pequeños; rechaza payloads gigantes (anti-DoS de memoria)

module.exports = ({ idleTtlMs = DEFAULT_IDLE_TTL_MS, absTtlMs = DEFAULT_ABS_TTL_MS } = {}) => {
  const rooms = new Map(); // id -> room

  const publicRoom = (r) => ({
    id: r.id, title: r.title, createdAt: r.createdAt, expiresAt: r.expiresAt,
    count: r.messages.length, members: r.members.size,
  });

  const resetIdle = (room) => {
    if (room.idleTimer) clearTimeout(room.idleTimer);
    room.idleTimer = setTimeout(() => destroy(room.id), idleTtlMs);
    if (room.idleTimer.unref) room.idleTimer.unref();
  };

  const destroy = (id) => {
    const room = rooms.get(id);
    if (!room) return;
    clearTimeout(room.idleTimer);
    clearTimeout(room.absTimer);
    // AUTODESTRUCCIÓN: vaciar todo el material en RAM
    room.messages.length = 0;
    room.signals.length = 0;
    room.members.clear();
    room.remoteFeeds.clear();
    rooms.delete(id);
  };

  const createRoom = ({ title = "", ttlMinutes } = {}) => {
    if (rooms.size >= MAX_ROOMS) {
      // desalojar la más antigua para no crecer sin límite
      const oldest = [...rooms.values()].sort((a, b) => a.createdAt - b.createdAt)[0];
      if (oldest) destroy(oldest.id);
    }
    const id = crypto.randomBytes(9).toString("hex"); // 18 hex
    const createdAt = Date.now();
    let absMs = absTtlMs;
    const t = parseInt(ttlMinutes, 10);
    if (Number.isFinite(t) && t > 0) absMs = Math.min(t * 60000, MAX_ABS_TTL_MS);
    const room = {
      id,
      title: String(title || "").slice(0, 80),
      createdAt,
      expiresAt: createdAt + absMs,
      messages: [],
      signals: [],
      members: new Set(),
      remoteFeeds: new Set(),   // feeds SSB de otros nodos con esta sala (para relay cross-device por privados)
      seq: 0,
      sigSeq: 0,
      idleTimer: null,
      absTimer: null,
    };
    rooms.set(id, room);
    room.absTimer = setTimeout(() => destroy(id), absMs);
    if (room.absTimer.unref) room.absTimer.unref();
    resetIdle(room);
    return publicRoom(room);
  };

  // Fase 2 (cross-device): registra el feed SSB de otro participante (de una invitación) para relayarle.
  const addRemoteFeed = (id, feed) => {
    const room = rooms.get(id);
    if (!room || typeof feed !== "string" || !/^@[A-Za-z0-9+/=]{20,}\.ed25519$/.test(feed)) return false;
    if (room.remoteFeeds.has(feed) || room.remoteFeeds.size < MAX_MEMBERS) { room.remoteFeeds.add(feed); return true; }
    return false;
  };
  const getRemoteFeeds = (id) => { const r = rooms.get(id); return r ? [...r.remoteFeeds] : []; };
  // ¿hay ALGUNA sala con feeds remotos? (para no montar el relay si no hace falta)
  const anyRemoteRooms = () => { for (const r of rooms.values()) if (r.remoteFeeds.size) return true; return false; };

  // Fase 2 (invitar): unirse a una sala por su id creando un ESPEJO local (desde un enlace/invitación).
  // El id es la "capacidad": quien lo tiene puede unirse. Aún NO conecta con la sala del otro nodo
  // (eso es la señalización cross-device, pendiente); crea la sala local para poder abrir el enlace.
  const adoptRoom = ({ id, title = "", ttlMinutes } = {}) => {
    if (!id || typeof id !== "string" || !/^[0-9a-f]{6,64}$/.test(id)) return null;
    const existing = rooms.get(id);
    if (existing) return publicRoom(existing);
    if (rooms.size >= MAX_ROOMS) {
      const oldest = [...rooms.values()].sort((a, b) => a.createdAt - b.createdAt)[0];
      if (oldest) destroy(oldest.id);
    }
    const createdAt = Date.now();
    let absMs = absTtlMs;
    const t = parseInt(ttlMinutes, 10);
    if (Number.isFinite(t) && t > 0) absMs = Math.min(t * 60000, MAX_ABS_TTL_MS);
    const room = {
      id, title: String(title || "").slice(0, 80), createdAt, expiresAt: createdAt + absMs,
      messages: [], signals: [], members: new Set(), remoteFeeds: new Set(), seq: 0, sigSeq: 0, idleTimer: null, absTimer: null,
    };
    rooms.set(id, room);
    room.absTimer = setTimeout(() => destroy(id), absMs);
    if (room.absTimer.unref) room.absTimer.unref();
    resetIdle(room);
    return publicRoom(room);
  };

  const listRooms = () =>
    [...rooms.values()].map(publicRoom).sort((a, b) => b.createdAt - a.createdAt);

  const getRoom = (id) => {
    const r = rooms.get(id);
    return r ? publicRoom(r) : null;
  };

  const postMessage = (id, { from = "?", text = "", mid = "" } = {}) => {
    const room = rooms.get(id);
    if (!room) return null;
    const clean = String(text || "").slice(0, 2000);
    if (!clean) return null;
    const msg = {
      seq: ++room.seq,
      mid: String(mid || "").slice(0, 40),
      from: String(from || "?").slice(0, 80),
      text: clean,
      ts: Date.now(),
    };
    room.messages.push(msg);
    if (room.messages.length > MAX_MSGS) room.messages.shift();
    if (room.members.has(msg.from) || room.members.size < MAX_MEMBERS) room.members.add(msg.from);
    resetIdle(room);
    return msg;
  };

  const listMessages = (id, since = 0) => {
    const room = rooms.get(id);
    if (!room) return null;
    const s = parseInt(since, 10) || 0;
    return room.messages.filter((m) => m.seq > s);
  };

  // --- señalización WebRTC (mailbox RAM) ---
  const postSignal = (id, { from = "", to = "", payload = null } = {}) => {
    const room = rooms.get(id);
    if (!room) return null;
    if (payload != null) { // rechaza payloads gigantes (SDP/ICE son pequeños)
      try { if (JSON.stringify(payload).length > MAX_SIGNAL_BYTES) return null; }
      catch (e) { return null; }
    }
    const sig = {
      seq: ++room.sigSeq,
      from: String(from || "").slice(0, 80),
      to: String(to || "").slice(0, 80),
      payload,
      ts: Date.now(),
    };
    room.signals.push(sig);
    if (room.signals.length > MAX_SIGNALS) room.signals.shift();
    if (sig.from && (room.members.has(sig.from) || room.members.size < MAX_MEMBERS)) room.members.add(sig.from);
    resetIdle(room);
    return sig;
  };

  const getSignals = (id, { forId = "", since = 0 } = {}) => {
    const room = rooms.get(id);
    if (!room) return null;
    const s = parseInt(since, 10) || 0;
    return {
      signals: room.signals.filter(
        (x) => x.seq > s && x.from !== forId && (!x.to || x.to === forId)
      ),
      members: [...room.members].filter((m) => m !== forId),
    };
  };

  return {
    createRoom,
    adoptRoom,
    addRemoteFeed,
    getRemoteFeeds,
    anyRemoteRooms,
    listRooms,
    getRoom,
    postMessage,
    listMessages,
    postSignal,
    getSignals,
    _destroy: destroy,
  };
};
