const layer = document.querySelector("#hudLayer");
let currentPayload = null;
let dragging = null;
let pendingPayload = null;
let mouseInteractive = false;
let renderedSignature = "";
let stableRoster = null;
let pendingRosterChange = null;

function escapeHtml(value) {
  return String(value ?? "").replace(/[&<>'"]/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", "'": "&#39;", '"': "&quot;" }[char]));
}

function tableIdFromTitle(title) {
  const value = String(title || "");
  return value.match(/\((\d+)\)\s*$/)?.[1]
    || value.match(/\bTournament\s*#?\s*(\d{5,})\b/i)?.[1]
    || value.match(/\b(?:SCHEDULEDID-G|SITGOID-G|T)(\d{6,})\b/i)?.[1]
    || value.match(/\b(\d{8,})\b/)?.[1]
    || "";
}

function normalizeTableName(value) {
  return String(value || "")
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/\b\d{8,}\b/g, " ")
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, " ")
    .trim();
}

function compareTableRecency(left, right) {
  const a = String(left?.handId || "").replace(/\D/g, "");
  const b = String(right?.handId || "").replace(/\D/g, "");
  if (a.length !== b.length) return a.length - b.length;
  return a.localeCompare(b);
}

function tableFromPayload(payload) {
  if (Array.isArray(payload?.activeTable?.players) && payload.activeTable.players.length) {
    return payload.activeTable;
  }
  const tables = payload?.tables || {};
  const tableId = tableIdFromTitle(payload?.tableTitle);
  if (tableId && tables[tableId]) return tables[tableId];
  const normalizedTitle = normalizeTableName(payload?.tableTitle);
  if (!normalizedTitle) return {};
  const candidates = [...new Set(Object.values(tables))].filter((table) => {
    const name = normalizeTableName(table?.tableName);
    return name.length >= 6 && (normalizedTitle.includes(name) || name.includes(normalizedTitle));
  });
  return candidates.sort((left, right) => compareTableRecency(right, left))[0] || {};
}

function defaultLayout(index, count) {
  if (count % 2 === 1 && index === Math.floor(count / 2)) return { rx: 0.37, ry: 0.82, scale: 1 };
  const angle = -Math.PI / 2 + (Math.PI * 2 * index) / Math.max(1, count);
  return { rx: 0.5 + Math.cos(angle) * 0.36 - 0.13, ry: 0.5 + Math.sin(angle) * 0.32 - 0.06, scale: 1 };
}

function panelRects() {
  return [...document.querySelectorAll(".player-hud")].map((panel) => {
    const rect = panel.getBoundingClientRect();
    return { x: rect.left, y: rect.top, width: rect.width, height: rect.height };
  });
}

function clampPanelToViewport(panel) {
  if (!panel) return;
  const rect = panel.getBoundingClientRect();
  const left = Math.max(0, Math.min(Math.max(0, window.innerWidth - rect.width), rect.left));
  const top = Math.max(0, Math.min(Math.max(0, window.innerHeight - rect.height), rect.top));
  panel.style.left = `${left}px`;
  panel.style.top = `${top}px`;
}

function clampAllPanelsToViewport() {
  document.querySelectorAll(".player-hud").forEach(clampPanelToViewport);
  updateShape();
}

function updateShape() {
  window.requestAnimationFrame(() => {
    window.desktopStore?.setHudShape?.(panelRects());
  });
}

function setMouseInteractive(interactive) {
  const next = Boolean(interactive);
  if (mouseInteractive === next) return;
  mouseInteractive = next;
  window.desktopStore?.setHudMouseEvents?.(next);
}

function nextOpacity(current) {
  const levels = [1, 0.85, 0.7, 0.55, 0.4];
  const index = levels.findIndex((level) => Math.abs(level - current) < 0.03);
  return levels[(index + 1 + levels.length) % levels.length];
}

function previewOpacity(value) {
  document.querySelectorAll(".player-hud").forEach((panel) => panel.style.setProperty("--hud-opacity", value));
  document.querySelectorAll("[data-opacity]").forEach((button) => {
    button.textContent = `◐ ${Math.round(value * 100)}%`;
  });
}

function normalizedPlayerName(value) {
  return String(value || "").normalize("NFKC").replace(/\s+/g, " ").trim().toLowerCase();
}

function rowsForPlayer(payload, playerName) {
  const rowsByPlayer = payload?.rowsByPlayer || {};
  if (Array.isArray(rowsByPlayer[playerName])) return rowsByPlayer[playerName];
  const wanted = normalizedPlayerName(playerName);
  const matchedKey = Object.keys(rowsByPlayer).find((name) => normalizedPlayerName(name) === wanted);
  return matchedKey && Array.isArray(rowsByPlayer[matchedKey]) ? rowsByPlayer[matchedKey] : [];
}

function rosterState(players, table, siteKey, maxSeats) {
  const entries = players.map((player, index) => ({
    name: normalizedPlayerName(player?.name),
    seat: Math.max(1, Number(player?.seat) || index + 1),
  })).filter((entry) => entry.name);
  const names = new Set(entries.map((entry) => entry.name));
  const seatsByName = new Map(entries.map((entry) => [entry.name, entry.seat]));
  return {
    key: `${siteKey}|${maxSeats}|${normalizedPlayerName(table?.hero)}|${entries.map((entry) => `${entry.seat}:${entry.name}`).join("|")}`,
    names,
    seatsByName,
    hero: normalizedPlayerName(table?.hero),
    count: entries.length,
    maxSeats,
  };
}

function rosterOverlapRatio(left, right) {
  if (!left?.names?.size || !right?.names?.size) return 0;
  let overlap = 0;
  left.names.forEach((name) => {
    if (right.names.has(name)) overlap += 1;
  });
  return overlap / Math.max(1, Math.min(left.names.size, right.names.size));
}

function shouldHoldSuspiciousRoster(nextRoster) {
  if (!stableRoster || !nextRoster || nextRoster.key === stableRoster.key) {
    pendingRosterChange = null;
    return false;
  }
  const overlap = rosterOverlapRatio(stableRoster, nextRoster);
  let movedSeats = 0;
  let comparableSeats = 0;
  nextRoster.names.forEach((name) => {
    if (!stableRoster.names.has(name)) return;
    comparableSeats += 1;
    if (stableRoster.seatsByName?.get(name) !== nextRoster.seatsByName?.get(name)) movedSeats += 1;
  });
  const seatMoveRatio = comparableSeats ? movedSeats / comparableSeats : 0;
  const seatJump = Math.abs((stableRoster.maxSeats || 0) - (nextRoster.maxSeats || 0));
  const playerJump = Math.abs((stableRoster.count || 0) - (nextRoster.count || 0));
  const heroJump = stableRoster.hero && nextRoster.hero && stableRoster.hero !== nextRoster.hero;
  const suspicious = overlap < 0.6 || seatMoveRatio >= 0.35 || seatJump >= 2 || playerJump >= 2 || heroJump;
  if (!suspicious) {
    pendingRosterChange = null;
    return false;
  }
  if (pendingRosterChange?.key === nextRoster.key) {
    pendingRosterChange.count += 1;
  } else {
    pendingRosterChange = { key: nextRoster.key, count: 1, at: Date.now() };
  }
  const confirmed = pendingRosterChange.count >= 2 || Date.now() - pendingRosterChange.at > 1800;
  if (confirmed) {
    pendingRosterChange = null;
    return false;
  }
  return true;
}

function render(payload) {
  if (!payload) return;
  if (dragging) {
    pendingPayload = payload;
    return;
  }
  const table = tableFromPayload(payload);
  const rawPlayers = table.players || [];
  const seenPlayers = new Set();
  const players = rawPlayers
    .map((player, index) => typeof player === "string" ? { name: player, seat: index + 1 } : player)
    .filter((player) => {
      const name = String(player?.name || "").trim().toLowerCase();
      if (!name || seenPlayers.has(name)) return false;
      seenPlayers.add(name);
      return true;
    })
    .sort((a, b) => Number(a.seat) - Number(b.seat))
    .slice(0, 10);
  const declaredSeats = Math.max(0, Number(table.maxSeats) || 0);
  const maxSeats = Math.max(2, Math.min(10, declaredSeats || players.length));
  const hero = players.find((player) => player.name === table.hero) || players[0];
  const heroSeat = Math.max(1, Number(hero?.seat) || 1);
  const heroIndex = Math.max(0, players.indexOf(hero));
  const heroVisualIndex = Math.floor(maxSeats / 2);
  const siteKey = String(payload.siteKey || "default").replace(/[^a-z0-9_-]+/gi, "_").toLowerCase();
  const globalScale = Math.max(0.6, Math.min(1.8, Number(payload.globalScale) || 1));
  const opacity = Math.max(0.35, Math.min(1, Number(payload.opacity) || 1));
  const panelOpacity = opacity;
  const nextRoster = rosterState(players, table, siteKey, maxSeats);
  if (shouldHoldSuspiciousRoster(nextRoster)) {
    window.desktopStore?.setHudShape?.(panelRects());
    return;
  }
  currentPayload = payload;
  const signature = JSON.stringify({
    siteKey,
    globalScale,
    opacity,
    players: players.map((player) => ({
      name: player.name,
      seat: player.seat,
      labels: rowsForPlayer(payload, player.name).map((row) => row.label),
    })),
    layouts: payload.layouts || {},
  });
  if (signature === renderedSignature) {
    const panels = [...document.querySelectorAll(".player-hud")];
    players.forEach((player, playerIndex) => {
      const values = panels[playerIndex]?.querySelectorAll(".hud-stat i") || [];
      rowsForPlayer(payload, player.name).forEach((row, rowIndex) => {
        const nextValue = String(row.value ?? "");
        if (values[rowIndex] && values[rowIndex].textContent !== nextValue) values[rowIndex].textContent = nextValue;
      });
    });
    return;
  }
  renderedSignature = signature;
  stableRoster = nextRoster;
  const physicalSeats = players.map((player, index) => Math.max(1, Number(player.seat) || index + 1));
  const usePhysicalSeats = new Set(physicalSeats).size === players.length && Math.max(0, ...physicalSeats) <= maxSeats;
  const usedVisualIndexes = new Set();
  layer.innerHTML = players.map((player, index) => {
    const seat = Math.max(1, Number(player.seat) || index + 1);
    let visualIndex = usePhysicalSeats
      ? (seat - heroSeat + heroVisualIndex + maxSeats) % maxSeats
      : (index - heroIndex + heroVisualIndex + maxSeats) % maxSeats;
    for (let attempt = 0; attempt < maxSeats && usedVisualIndexes.has(visualIndex); attempt += 1) {
      visualIndex = (visualIndex + 1) % maxSeats;
    }
    if (usedVisualIndexes.has(visualIndex)) return "";
    usedVisualIndexes.add(visualIndex);
    const slotKey = `${siteKey}-${maxSeats}-${visualIndex}`;
    const layout = payload.layouts?.[slotKey] || defaultLayout(visualIndex, maxSeats);
    const rows = rowsForPlayer(payload, player.name);
    const estimatedWidth = 230 * globalScale;
    const estimatedHeight = (rows.length > 7 ? 108 : 66) * globalScale;
    const maxRx = Math.max(0, (window.innerWidth - estimatedWidth) / Math.max(1, window.innerWidth));
    const maxRy = Math.max(0, (window.innerHeight - estimatedHeight) / Math.max(1, window.innerHeight));
    const rx = Math.max(0, Math.min(maxRx, Number(layout.rx) || 0));
    const ry = Math.max(0, Math.min(maxRy, Number(layout.ry) || 0));
    return `<section class="player-hud" data-slot="${slotKey}" data-player="${escapeHtml(player.name)}" style="left:${rx * 100}%;top:${ry * 100}%;--scale:${globalScale};--hud-opacity:${panelOpacity}">
      <header class="hud-bar"><span class="hud-name">${escapeHtml(player.name)}</span><span class="hud-actions">
        <button data-opacity type="button" title="Transparencia da HUD">◐ ${Math.round(opacity * 100)}%</button>
        <button data-scale="down" type="button">-</button><button data-scale="reset" type="button">${Math.round(globalScale * 100)}%</button><button data-scale="up" type="button">+</button>
      </span></header>
      <div class="hud-stats">${rows.map((row) => `<span class="hud-stat"><b>${escapeHtml(row.label)}</b><i>${escapeHtml(row.value)}</i></span>`).join("")}</div>
    </section>`;
  }).join("");
  updateShape();
}

document.addEventListener("pointermove", (event) => {
  setMouseInteractive(Boolean(dragging || event.target.closest(".player-hud")));
  if (dragging) {
    const visualRect = dragging.element.getBoundingClientRect();
    const left = Math.max(0, Math.min(window.innerWidth - visualRect.width, event.clientX - dragging.offsetX));
    const top = Math.max(0, Math.min(window.innerHeight - visualRect.height, event.clientY - dragging.offsetY));
    dragging.element.style.left = `${left}px`;
    dragging.element.style.top = `${top}px`;
  }
});

document.addEventListener("pointerdown", (event) => {
  const opacityButton = event.target.closest("[data-opacity]");
  if (opacityButton) {
    event.preventDefault();
    event.stopPropagation();
    const current = Math.max(0.35, Math.min(1, Number(currentPayload?.opacity) || 1));
    const next = nextOpacity(current);
    if (currentPayload) currentPayload.opacity = next;
    previewOpacity(next);
    window.desktopStore?.setHudOpacity?.();
    return;
  }
  const scaleButton = event.target.closest("[data-scale]");
  if (scaleButton) {
    event.preventDefault();
    event.stopPropagation();
    const panel = scaleButton.closest(".player-hud");
    window.desktopStore?.setHudSlotScale?.({ slotKey: panel?.dataset.slot, action: scaleButton.dataset.scale });
    return;
  }
  if (event.target.closest("button")) return;
  const panel = event.target.closest(".player-hud");
  if (!panel) return;
  event.preventDefault();
  event.stopPropagation();
  setMouseInteractive(true);
  window.desktopStore?.setHudDragging?.(true);
  panel.setPointerCapture?.(event.pointerId);
  const rect = panel.getBoundingClientRect();
  dragging = { element: panel, pointerId: event.pointerId, offsetX: event.clientX - rect.left, offsetY: event.clientY - rect.top, saving: false };
});

async function finishDrag() {
  if (!dragging || dragging.saving) return;
  const activeDrag = dragging;
  activeDrag.saving = true;
  const panel = activeDrag.element;
  const rect = panel.getBoundingClientRect();
  const slotKey = panel.dataset.slot;
  const maxLeft = Math.max(0, window.innerWidth - rect.width);
  const maxTop = Math.max(0, window.innerHeight - rect.height);
  await window.desktopStore?.setHudSlotPosition?.({
    slotKey,
    rx: Math.max(0, Math.min(maxLeft, rect.left)) / Math.max(1, window.innerWidth),
    ry: Math.max(0, Math.min(maxTop, rect.top)) / Math.max(1, window.innerHeight),
  });
  panel.releasePointerCapture?.(activeDrag.pointerId);
  dragging = null;
  const nextPayload = pendingPayload;
  pendingPayload = null;
  if (nextPayload) render(nextPayload);
  await window.desktopStore?.setHudShape?.(panelRects());
  await window.desktopStore?.setHudDragging?.(false);
  setMouseInteractive(false);
}

document.addEventListener("pointerup", finishDrag);
document.addEventListener("pointercancel", finishDrag);
window.addEventListener("blur", () => {
  if (dragging) finishDrag();
});
window.addEventListener("resize", () => {
  window.requestAnimationFrame(clampAllPanelsToViewport);
});
document.addEventListener("pointerout", (event) => {
  if (!dragging && !event.relatedTarget) setMouseInteractive(false);
});

window.desktopStore?.onHudPayload?.(render);
window.desktopStore?.getHudPayload?.().then(render);
