// Совместная работа в реальном времени (М11): живой обмен аннотациями/закладками
// по WebSocket поверх backend-relay `/cloud/collab/{room}`.
//
// Транспорт — чистый relay (тип != "edit", без CRDT): полезная нагрузка сообщения
// "aris" — это текущее значение ключа-массива (annotations_*/bookmarks_*), которое
// на приёме объединяется теми же правилами, что и облако-синхронизация (union by id).
// Это дополняет pull-on-open синхронизацию: пока документ открыт у нескольких
// участников с одним sync-кодом, правки появляются мгновенно.
//
// Комната = sync-код + хэш документа: только устройства с тем же кодом, открывшие
// тот же документ, оказываются в одной комнате (модель доверия = общий секрет кода).

import { getSettings } from "../common/storage.js";

let sock = null;
let roomId = null;
let onMessage = null;
let onPresence = null;
let userId = null;
let reconnectTimer = null;
let closedByUs = false;
let attempts = 0;

/** http(s)://host → ws(s)://host */
function toWs(url) {
  if (/^https/i.test(url)) return "wss" + url.slice(5);
  if (/^http/i.test(url)) return "ws" + url.slice(4);
  return url; // уже ws/wss
}

/** Стабильный безопасный идентификатор комнаты из кода и хэша документа. */
function makeRoom(code, docHash) {
  return `${code}_${docHash}`.replace(/[^A-Za-z0-9_-]/g, "").slice(0, 200);
}

/** Случайный идентификатор участника на время сессии. */
function ensureUser() {
  if (userId) return userId;
  const a = new Uint8Array(6);
  crypto.getRandomValues(a);
  userId = "u" + [...a].map((b) => b.toString(16).padStart(2, "0")).join("");
  return userId;
}

/** Активна ли совместная работа сейчас (открыт сокет). */
export function isLive() {
  return !!sock && sock.readyState === WebSocket.OPEN;
}

export function whoAmI() {
  return userId;
}

/**
 * Подключиться к комнате документа.
 * @param {string} docHash — хэш открытого документа.
 * @param {object} handlers — { onAnnotations(key, arr), onBookmarks(key, arr), onPeers(list) }.
 */
export async function connect(docHash, handlers = {}) {
  const s = await getSettings();
  const enabled = !!s.collabEnabled && !!s.syncEnabled;
  const code = (s.syncCode || "").trim();
  const url = (s.syncUrl || s.backendUrl || "").replace(/\/+$/, "");
  if (!enabled || code.length < 8 || !url || !docHash) return false;

  onMessage = handlers;
  onPresence = handlers.onPeers || null;
  roomId = makeRoom(code, docHash);
  ensureUser();
  closedByUs = false;
  open(toWs(url));
  return true;
}

function open(wsBase) {
  try {
    sock = new WebSocket(`${wsBase}/cloud/collab/${encodeURIComponent(roomId)}?user=${userId}`);
  } catch (e) {
    console.warn("[Aris collab] open:", e.message);
    return;
  }
  sock.onopen = () => {
    attempts = 0;
    console.info("[Aris collab] комната:", roomId);
  };
  sock.onmessage = (ev) => {
    let msg;
    try { msg = JSON.parse(ev.data); } catch { return; }
    if (!msg || typeof msg !== "object") return;
    if (msg.user && msg.user === userId) return; // собственное эхо игнорируем
    handle(msg);
  };
  sock.onclose = () => {
    sock = null;
    if (closedByUs) return;
    // экспоненциальный реконнект (до ~15с), пока документ открыт
    const delay = Math.min(15000, 1000 * 2 ** Math.min(attempts++, 4));
    clearTimeout(reconnectTimer);
    reconnectTimer = setTimeout(() => roomId && open(wsBase), delay);
  };
  sock.onerror = () => { try { sock.close(); } catch {} };
}

function handle(msg) {
  if (msg.type === "presence" && Array.isArray(msg.participants)) {
    onPresence && onPresence(msg.participants);
    return;
  }
  if (msg.type === "join" || msg.type === "leave") {
    onPresence && onPresence(null, msg.type, msg.user);
    return;
  }
  if (msg.type !== "aris" || !msg.key) return;
  const arr = Array.isArray(msg.value) ? msg.value : [];
  if (msg.key.startsWith("annotations_")) onMessage.onAnnotations && onMessage.onAnnotations(msg.key, arr);
  else if (msg.key.startsWith("bookmarks_")) onMessage.onBookmarks && onMessage.onBookmarks(msg.key, arr);
}

/** Разослать текущее значение ключа-массива участникам комнаты. */
export function broadcast(key, value) {
  if (!isLive()) return;
  try {
    sock.send(JSON.stringify({ type: "aris", key, value, user: userId }));
  } catch (e) {
    console.warn("[Aris collab] send:", e.message);
  }
}

/** Отключиться (при закрытии/смене документа). */
export function disconnect() {
  closedByUs = true;
  clearTimeout(reconnectTimer);
  roomId = null;
  onMessage = null;
  onPresence = null;
  if (sock) { try { sock.close(); } catch {} sock = null; }
}
