import { fnv1a32, hash2u32, mulberry32 } from "./rng.js";
import { formatPlanet, generatePlanet } from "./planetGeneration.js";
import { apiFetchConfig, apiFetchScores, apiFetchGrid, apiRevealCell, apiFetchEconScores, apiTickEconScores, apiFetchElementBonus, apiTickElementBonus, apiFetchDbInfo, apiFetchVictoryPoints } from "./api.js";
import { computeTeamIncome, computeTeamElementBonus, renderResourceTable, setEconSort, getEconSort } from "./economy.js";

// ── Constants ─────────────────────────────────────────────────────────────────

const GRID_W = 100;
const GRID_H = 100;
const PLANET_CHANCE = 0.1;

// SVG playfield dimensions (must match /graphism/playground.svg viewBox)
const SVG_W = 1000;
const SVG_H = 1000;

const COLOR_RING_IDLE       = "rgba(113,199,255,0.08)";
const COLOR_BLUE_DISCOVERED = "rgba(90, 200, 255, 0.38)";
const COLOR_RED_DISCOVERED  = "rgba(220, 75, 85, 0.42)";
const COLOR_OPPONENT_GREY   = "rgba(95, 98, 110, 0.72)";

// ── Shared game state ─────────────────────────────────────────────────────────

export const GAME_CONFIG = {
  clickCooldownSeconds: 5,
  databaseWipeoutIntervalSeconds: 21600,
  debugModeForTeams: false,
  configReloadIntervalSeconds: 30,
  worldSeed: "",
  seedPeriodEndsAtUtc: "",
  elementWorth: {},
  resourceWorth: { common: {}, rare: {} },
};
window.GAME_CONFIG = GAME_CONFIG;

/** @type {Map<string, { discoveredBy: string, hasPlanet: boolean, planet: object|null }>} */
export const cells = new Map();

export let currentTeam = "blue";
export function setCurrentTeam(t) { currentTeam = t; }

export let seedStr = "";
export let seedU32 = 0;
export function applySeed(str) {
  seedStr = str;
  seedU32 = fnv1a32(str || "fallback");
}

export const teamCooldownEndMs = { blue: 0, red: 0 };
let rafId = 0;
let lastPointerEvent = null;

// ── Playfield mask ────────────────────────────────────────────────────────────

/** Set of "x,y" keys for exploitable tiles (populated by loadPlayfieldMask). */
let exploitableTiles = null;

/** Preloaded SVG image for background rendering. */
let playfieldImg = null;

/**
 * Loads /graphism/playground.svg, rasterises it to an offscreen canvas,
 * then classifies each 100×100 grid tile as exploitable if ≥80 % of its
 * sampled pixels are the black playable zone (R≤20, G≤20, B≤20).
 */
export function loadPlayfieldMask() {
  return new Promise((resolve) => {
    const img = new Image();
    img.onload = () => {
      playfieldImg = img;

      // ── Rasterise SVG at native size onto an offscreen canvas ──
      const oc   = document.createElement("canvas");
      oc.width   = SVG_W;
      oc.height  = SVG_H;
      const octx = oc.getContext("2d");

      // White fill so transparent SVG areas appear white (not black).
      octx.fillStyle = "#ffffff";
      octx.fillRect(0, 0, SVG_W, SVG_H);
      octx.drawImage(img, 0, 0, SVG_W, SVG_H);

      let imageData;
      try {
        imageData = octx.getImageData(0, 0, SVG_W, SVG_H);
      } catch (e) {
        console.warn("[playfield] canvas tainted – no exploitable tiles:", e);
        exploitableTiles = new Set();
        resolve();
        return;
      }

      const data = imageData.data; // RGBA flat array

      const SAMPLES_X = 10;
      const SAMPLES_Y = 10;
      const cellW     = SVG_W / GRID_W; // 7.5 px
      const cellH     = SVG_H / GRID_H; // 10 px
      const tiles     = new Set();

      for (let gy = 0; gy < GRID_H; gy++) {
        for (let gx = 0; gx < GRID_W; gx++) {
          let blackCount = 0;

          for (let sy = 0; sy < SAMPLES_Y; sy++) {
            for (let sx = 0; sx < SAMPLES_X; sx++) {
              const px = Math.min(
                SVG_W - 1,
                Math.floor(gx * cellW + (sx + 0.5) * cellW / SAMPLES_X)
              );
              const py = Math.min(
                SVG_H - 1,
                Math.floor(gy * cellH + (sy + 0.5) * cellH / SAMPLES_Y)
              );
              const idx = (py * SVG_W + px) * 4;
              const r = data[idx], g = data[idx + 1], b = data[idx + 2];
              // Pure black = playable zone (path2, fill:#000000)
              if (r <= 20 && g <= 20 && b <= 20) blackCount++;
            }
          }

          const coverage = blackCount / (SAMPLES_X * SAMPLES_Y);
          if (coverage >= 0.8) tiles.add(cellKey(gx, gy));
        }
      }

      exploitableTiles = tiles;
      console.log(`[playfield] ${tiles.size} exploitable tiles loaded.`);
      resolve();
    };
    img.onerror = () => {
      console.warn("[playfield] failed to load playground.svg");
      exploitableTiles = new Set();
      resolve();
    };
    img.src = "/graphism/playground.svg";
  });
}

// ── DOM refs ──────────────────────────────────────────────────────────────────

const canvas        = document.getElementById("canvas");
const ctx           = canvas.getContext("2d", { alpha: false });
const details       = document.getElementById("details");
const hint          = document.getElementById("hint");
const countdownEl   = document.getElementById("countdown");
const countdownWrap = document.getElementById("countdownWrap");
const cooldownCfgEl = document.getElementById("cooldownConfig");
const seedDisplayEl = document.getElementById("worldSeedDisplay");
const nextPeriodEl  = document.getElementById("nextPeriodUtc");
const resetCountEl  = document.getElementById("refreshCountdown");
const scoreBlueEl      = document.getElementById("scoreBlue");
const scoreRedEl       = document.getElementById("scoreRed");
const vpBlueEl           = document.getElementById("vpBlue");
const vpRedEl            = document.getElementById("vpRed");
const dbCreatedAtEl      = document.getElementById("dbCreatedAt");
const elemBonusBlueEl    = document.getElementById("elemBonusBlue");
const elemBonusRedEl     = document.getElementById("elemBonusRed");
const effectiveCooldownEl = document.getElementById("effectiveCooldown");
const incomeBlueEl     = document.getElementById("incomeBlue");
const incomeRedEl      = document.getElementById("incomeRed");
const resourceTableEl  = document.getElementById("resourceTableBody");
const econScoreBlueEl  = document.getElementById("econScoreBlue");
const econScoreRedEl   = document.getElementById("econScoreRed");
const econDeltaBlueEl  = document.getElementById("econDeltaBlue");
const econDeltaRedEl   = document.getElementById("econDeltaRed");
const teamCorner    = document.getElementById("teamCorner");
const teamTrack     = document.getElementById("teamSegmentedTrack");
const teamBlueBtn   = document.getElementById("teamBlue");
const teamRedBtn    = document.getElementById("teamRed");

// ── Cell helpers ──────────────────────────────────────────────────────────────

export function cellKey(x, y) { return `${x},${y}`; }

export function isExploitable(x, y) {
  if (!exploitableTiles) return false;
  return exploitableTiles.has(cellKey(x, y));
}

function hasPlanetAt(x, y) {
  const h = hash2u32(x, y, seedU32);
  return h / 4294967296 < PLANET_CHANCE;
}

export function cellMeta(key)        { return cells.get(key) ?? null; }
export function isOpponentTile(key)  { const m = cellMeta(key); return m !== null && m.discoveredBy !== currentTeam; }
export function isOwnTile(key)       { const m = cellMeta(key); return m !== null && m.discoveredBy === currentTeam; }

// ── Config display ────────────────────────────────────────────────────────────

export function applyConfigPayload(data) {
  GAME_CONFIG.clickCooldownSeconds          = Number(data.clickCooldownSeconds) || 0;
  GAME_CONFIG.databaseWipeoutIntervalSeconds = Number(data.databaseWipeoutIntervalSeconds) || 21600;
  GAME_CONFIG.debugModeForTeams             = Boolean(data.debugModeForTeams);
  GAME_CONFIG.configReloadIntervalSeconds   = Math.max(5, Number(data.configReloadIntervalSeconds) || 30);
  GAME_CONFIG.worldSeed                     = String(data.worldSeed ?? "");
  GAME_CONFIG.seedPeriodEndsAtUtc           = String(data.seedPeriodEndsAtUtc ?? "");
  if (data.elementWorth && typeof data.elementWorth === "object") {
    GAME_CONFIG.elementWorth = data.elementWorth;
  }
  if (data.resourceWorth && typeof data.resourceWorth === "object") {
    GAME_CONFIG.resourceWorth = data.resourceWorth;
  }

  cooldownCfgEl.textContent = String(GAME_CONFIG.clickCooldownSeconds);
  seedDisplayEl.textContent = GAME_CONFIG.worldSeed || "—";
  nextPeriodEl.textContent  = GAME_CONFIG.seedPeriodEndsAtUtc
    ? new Date(GAME_CONFIG.seedPeriodEndsAtUtc).toISOString().replace("T", " ").slice(0, 19) + "Z"
    : "—";

  updateResetCountdown();

  // Team switcher visibility is managed exclusively via unlockTeamSwitcher() /
  // lockTeamSwitcher() — NOT by debugModeForTeams config.
}

export function updateResetCountdown() {
  if (!GAME_CONFIG.seedPeriodEndsAtUtc) { resetCountEl.textContent = "--:--:--"; return; }
  const diff = new Date(GAME_CONFIG.seedPeriodEndsAtUtc).getTime() - Date.now();
  if (diff <= 0) { resetCountEl.textContent = "00:00:00"; return; }
  const s = Math.floor(diff / 1000);
  resetCountEl.textContent =
    String(Math.floor(s / 3600)).padStart(2, "0") + ":" +
    String(Math.floor((s % 3600) / 60)).padStart(2, "0") + ":" +
    String(s % 60).padStart(2, "0");
}

// ── Team switcher (admin-only) ────────────────────────────────────────────────

/** Show the team switcher widget. Called only after successful admin unlock. */
export function unlockTeamSwitcher() {
  teamCorner.classList.remove("teamCorner--hidden");
}

/** Hide the team switcher widget. */
export function lockTeamSwitcher() {
  teamCorner.classList.add("teamCorner--hidden");
}

// ── Scores ────────────────────────────────────────────────────────────────────

export async function fetchAndApplyScores() {
  try {
    const { blue, red } = await apiFetchScores();
    if (scoreBlueEl) scoreBlueEl.textContent = String(blue ?? 0);
    if (scoreRedEl)  scoreRedEl.textContent  = String(red ?? 0);
  } catch { /* ignore */ }
}

export async function loadVictoryPoints() {
  try {
    const { blue, red } = await apiFetchVictoryPoints();
    if (vpBlueEl) vpBlueEl.textContent = String(blue ?? 0);
    if (vpRedEl)  vpRedEl.textContent  = String(red ?? 0);
  } catch { /* ignore */ }
}

// ── Element bonus ─────────────────────────────────────────────────────────────

let elemBonusBlue = 0;
let elemBonusRed  = 0;

function updateEffectiveCooldownDisplay() {
  const base = GAME_CONFIG.clickCooldownSeconds;
  const bonus = currentTeam === "blue" ? elemBonusBlue : elemBonusRed;
  const effective = base / (1 + bonus / 100);
  if (elemBonusBlueEl) elemBonusBlueEl.textContent = elemBonusBlue.toFixed(2);
  if (elemBonusRedEl)  elemBonusRedEl.textContent  = elemBonusRed.toFixed(2);
  if (effectiveCooldownEl) {
    effectiveCooldownEl.textContent = effective < 1
      ? `${(effective * 1000).toFixed(0)}ms`
      : `${effective.toFixed(2)}s`;
  }
}

export async function loadElementBonus() {
  try {
    const { blue, red } = await apiFetchElementBonus();
    elemBonusBlue = blue ?? 0;
    elemBonusRed  = red  ?? 0;
    updateEffectiveCooldownDisplay();
  } catch { /* ignore */ }
}

export async function tickElementBonus() {
  const worth = GAME_CONFIG.elementWorth;
  const blueBonus = computeTeamElementBonus("blue", cells, worth);
  const redBonus  = computeTeamElementBonus("red",  cells, worth);
  try {
    const result = await apiTickElementBonus(seedStr, blueBonus, redBonus);
    elemBonusBlue = result.blue ?? blueBonus;
    elemBonusRed  = result.red  ?? redBonus;
  } catch {
    elemBonusBlue = blueBonus;
    elemBonusRed  = redBonus;
  }
  updateEffectiveCooldownDisplay();
}

export function getEffectiveCooldown() {
  const base  = GAME_CONFIG.clickCooldownSeconds;
  const bonus = currentTeam === "blue" ? elemBonusBlue : elemBonusRed;
  return base / (1 + bonus / 100);
}

export async function loadDbInfo() {
  try {
    const { createdAt } = await apiFetchDbInfo();
    if (dbCreatedAtEl && createdAt) {
      const d = new Date(createdAt);
      dbCreatedAtEl.textContent =
        d.toLocaleDateString("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric" }) +
        " " +
        d.toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" });
    }
  } catch { /* ignore */ }
}

// ── Economy display ───────────────────────────────────────────────────────────

/** Recalculates and renders team income and resource table. */
export function updateEconomyDisplay() {
  const worth = GAME_CONFIG.resourceWorth;

  const blueIncome = computeTeamIncome("blue", cells, worth);
  const redIncome  = computeTeamIncome("red",  cells, worth);

  if (incomeBlueEl) incomeBlueEl.textContent = `+${blueIncome.total.toFixed(3)}/s`;
  if (incomeRedEl)  incomeRedEl.textContent  = `+${redIncome.total.toFixed(3)}/s`;

  // Resource table shows own-team breakdown
  const teamIncome = currentTeam === "blue" ? blueIncome : redIncome;
  if (resourceTableEl) {
    resourceTableEl.innerHTML = renderResourceTable(worth, teamIncome.byResource);
  }
}

// ── Economic score ────────────────────────────────────────────────────────────

let econScoreBlue = 0;
let econScoreRed  = 0;

/** Trigger the delta fade animation on an element. */
function showEconDelta(el, delta) {
  if (!el || delta <= 0) return;
  el.textContent = `+${delta.toFixed(3)}`;
  el.classList.remove("econDelta--active");
  // Force reflow so removing/re-adding the class restarts the animation
  void el.offsetWidth;
  el.classList.add("econDelta--active");
}

/**
 * Called every ECON_TICK_MS milliseconds.
 * Adds income × (interval in seconds) to each team's cumulative score,
 * updates the score display, and triggers the delta animation.
 *
 * @param {number} intervalSeconds - how many seconds elapsed since last tick
 */
export async function tickEconScore(intervalSeconds) {
  const worth = GAME_CONFIG.resourceWorth;

  const blueIncome = computeTeamIncome("blue", cells, worth);
  const redIncome  = computeTeamIncome("red",  cells, worth);

  const blueDelta = blueIncome.total * intervalSeconds;
  const redDelta  = redIncome.total  * intervalSeconds;

  try {
    const scores = await apiTickEconScores(seedStr, blueDelta, redDelta);
    econScoreBlue = scores.blue;
    econScoreRed  = scores.red;
  } catch {
    // fallback: update locally if server unreachable
    econScoreBlue += blueDelta;
    econScoreRed  += redDelta;
  }

  if (econScoreBlueEl) econScoreBlueEl.textContent = econScoreBlue.toFixed(3);
  if (econScoreRedEl)  econScoreRedEl.textContent  = econScoreRed.toFixed(3);

  showEconDelta(econDeltaBlueEl, blueDelta);
  showEconDelta(econDeltaRedEl,  redDelta);
}

/** Load economic scores from server (called on boot / after seed change). */
export async function loadEconScores() {
  try {
    const scores = await apiFetchEconScores();
    econScoreBlue = scores.blue ?? 0;
    econScoreRed  = scores.red  ?? 0;
    if (econScoreBlueEl) econScoreBlueEl.textContent = econScoreBlue.toFixed(3);
    if (econScoreRedEl)  econScoreRedEl.textContent  = econScoreRed.toFixed(3);
  } catch { /* ignore */ }
}

/** Reset economic scores (called when world period changes). */
export function resetEconScores() {
  econScoreBlue = 0;
  econScoreRed  = 0;
  if (econScoreBlueEl) econScoreBlueEl.textContent = "0.000";
  if (econScoreRedEl)  econScoreRedEl.textContent  = "0.000";
}

// ── Config fetch + apply ──────────────────────────────────────────────────────

/** Fetches /api/config, updates GAME_CONFIG, returns true if the world seed changed. */
export async function fetchConfig() {
  const data = await apiFetchConfig(currentTeam);
  const prevSeed = GAME_CONFIG.worldSeed;
  applyConfigPayload(data);
  applySeed(GAME_CONFIG.worldSeed);
  return prevSeed !== "" && prevSeed !== GAME_CONFIG.worldSeed;
}

// ── Grid fetch ────────────────────────────────────────────────────────────────

export async function fetchGridForSeed(seed, depth = 0) {
  if (depth > 4) throw new Error("grid_stale");
  const res = await apiFetchGrid(seed);
  if (res.status === 410) {
    await fetchConfig();
    return fetchGridForSeed(seedStr, depth + 1);
  }
  if (!res.ok) throw new Error("grid");
  const data = await res.json();
  cells.clear();
  for (const row of data.cells || []) {
    cells.set(cellKey(row.x, row.y), {
      discoveredBy: row.discovered_by ?? row.discoveredBy,
      hasPlanet:    Boolean(row.has_planet),
      planet:       row.planet_json ?? null,
    });
  }
}

// ── Cooldown ──────────────────────────────────────────────────────────────────

export function cooldownActive() {
  if (GAME_CONFIG.clickCooldownSeconds <= 0) return false;
  return Date.now() < teamCooldownEndMs[currentTeam];
}

function remainingSecs() {
  return Math.max(0, (teamCooldownEndMs[currentTeam] - Date.now()) / 1000);
}

function tickCooldown() {
  if (GAME_CONFIG.clickCooldownSeconds <= 0) { countdownWrap.classList.add("hidden"); return; }
  const left = remainingSecs();
  if (left <= 0) {
    countdownWrap.classList.add("hidden");
    countdownEl.textContent = "0";
    teamCooldownEndMs[currentTeam] = 0;
    refreshCursorFromLast();
    // Auto-refresh from server when cooldown expires
    refreshFromServer();
    return;
  }
  countdownWrap.classList.remove("hidden");
  countdownEl.textContent = String(Math.ceil(left));
  refreshCursorFromLast();
  rafId = requestAnimationFrame(tickCooldown);
}

export function startCooldown() {
  const secs = getEffectiveCooldown();
  if (secs <= 0) {
    teamCooldownEndMs[currentTeam] = 0;
    countdownWrap.classList.add("hidden");
    refreshCursorFromLast();
    return;
  }
  teamCooldownEndMs[currentTeam] = Date.now() + secs * 1000;
  countdownWrap.classList.remove("hidden");
  countdownEl.textContent = String(secs);
  cancelAnimationFrame(rafId);
  rafId = requestAnimationFrame(tickCooldown);
  refreshCursorFromLast();
}

export function clearCooldown() {
  teamCooldownEndMs.blue = 0;
  teamCooldownEndMs.red  = 0;
  cancelAnimationFrame(rafId);
  countdownWrap.classList.add("hidden");
}

// ── Draw ──────────────────────────────────────────────────────────────────────

export function draw() {
  const w = canvas.width;
  const h = canvas.height;

  // 1. Dark base fill (shows in areas outside the SVG paths)
  ctx.fillStyle = "#050810";
  ctx.fillRect(0, 0, w, h);

  // 2. Draw the SVG playfield as the background layer
  if (playfieldImg) {
    ctx.drawImage(playfieldImg, 0, 0, w, h);
  }

  const cw = w / GRID_W;
  const ch = h / GRID_H;

  // 3. Draw game tile overlays (only for exploitable tiles)
  for (let y = 0; y < GRID_H; y++) {
    for (let x = 0; x < GRID_W; x++) {
      if (!isExploitable(x, y)) continue;
      const k = cellKey(x, y);
      const meta = cellMeta(k);
      if (!meta)                                   ctx.fillStyle = COLOR_RING_IDLE;
      else if (meta.discoveredBy !== currentTeam)  ctx.fillStyle = COLOR_OPPONENT_GREY;
      else ctx.fillStyle = currentTeam === "blue" ? COLOR_BLUE_DISCOVERED : COLOR_RED_DISCOVERED;
      ctx.fillRect(x * cw, y * ch, cw, ch);
    }
  }

  // 4. Draw planet dots on own discovered tiles
  for (const [key, meta] of cells) {
    if (meta.discoveredBy !== currentTeam) continue;
    const [xs, ys] = key.split(",").map(Number);
    if (!isExploitable(xs, ys) || !meta.hasPlanet) continue;
    const h32 = hash2u32(xs, ys, seedU32 ^ 0x1337);
    const rng  = mulberry32(h32);
    ctx.fillStyle = `hsl(${Math.floor(rng() * 360)} 85% 65%)`;
    const r = Math.max(1.6, Math.min(cw, ch) * 0.28);
    ctx.beginPath();
    ctx.arc(xs * cw + cw / 2, ys * ch + ch / 2, r, 0, Math.PI * 2);
    ctx.fill();
  }

  // 5. Grid lines between adjacent exploitable tiles
  ctx.strokeStyle = "rgba(255,255,255,0.10)";
  ctx.lineWidth = 1;
  ctx.beginPath();
  for (let x = 1; x < GRID_W; x++) for (let y = 0; y < GRID_H; y++) {
    if (!isExploitable(x - 1, y) || !isExploitable(x, y)) continue;
    ctx.moveTo(x * cw, y * ch); ctx.lineTo(x * cw, (y + 1) * ch);
  }
  for (let y = 1; y < GRID_H; y++) for (let x = 0; x < GRID_W; x++) {
    if (!isExploitable(x, y - 1) || !isExploitable(x, y)) continue;
    ctx.moveTo(x * cw, y * ch); ctx.lineTo((x + 1) * cw, y * ch);
  }
  ctx.stroke();
}

// ── Cursor ────────────────────────────────────────────────────────────────────

function pickCell(ev) {
  const rect = canvas.getBoundingClientRect();
  const x = Math.floor(((ev.clientX - rect.left) / rect.width)  * GRID_W);
  const y = Math.floor(((ev.clientY - rect.top)  / rect.height) * GRID_H);
  if (x < 0 || x >= GRID_W || y < 0 || y >= GRID_H) return null;
  return { x, y };
}

function refreshCursor(ev) {
  const cell = pickCell(ev);
  if (!cell || !isExploitable(cell.x, cell.y) || isOpponentTile(cellKey(cell.x, cell.y))) {
    canvas.style.cursor = "default";
  } else {
    canvas.style.cursor = "pointer";
  }
}

function refreshCursorFromLast() {
  if (lastPointerEvent) refreshCursor(lastPointerEvent);
}

// ── Selection display ─────────────────────────────────────────────────────────

function applyRevealPayload(cell) {
  cells.set(cellKey(cell.x, cell.y), {
    discoveredBy: cell.discoveredBy ?? currentTeam,
    hasPlanet:    Boolean(cell.hasPlanet),
    planet:       cell.planet ?? null,
  });
  details.classList.remove("details--hidden");
  if (!cell.exploitable) {
    hint.textContent    = `(${cell.x},${cell.y}) not exploitable.`;
    details.textContent = `Cell (${cell.x},${cell.y})\n\nStatus: Not exploitable.`;
    return;
  }
  if (!cell.hasPlanet) {
    hint.textContent    = `(${cell.x},${cell.y}) exploitable — empty.`;
    details.textContent = `Cell (${cell.x},${cell.y})\n\nStatus: Exploitable\nContains: Nothing`;
    return;
  }
  hint.textContent    = `(${cell.x},${cell.y}) exploitable — planet revealed.`;
  details.textContent = `Cell (${cell.x},${cell.y})\n\nStatus: Exploitable\nContains: Planet\n\n${formatPlanet(cell.planet)}`;
}

function showLocalSelection(x, y) {
  const k = cellKey(x, y);
  details.classList.remove("details--hidden");
  if (!isOwnTile(k)) return;
  if (!isExploitable(x, y)) {
    hint.textContent    = `(${x},${y}) not exploitable.`;
    details.textContent = `Cell (${x},${y})\n\nStatus: Not exploitable.`;
    return;
  }
  const meta = cellMeta(k);
  if (!meta?.hasPlanet) {
    hint.textContent    = `(${x},${y}) exploitable — empty.`;
    details.textContent = `Cell (${x},${y})\n\nStatus: Exploitable\nContains: Nothing`;
    return;
  }
  let planet = meta.planet;
  if (!planet) {
    const h = hash2u32(x, y, seedU32 ^ 0xa5a5a5a5);
    planet = generatePlanet(mulberry32(h));
  }
  hint.textContent    = `(${x},${y}) exploitable — planet.`;
  details.textContent = `Cell (${x},${y})\n\nStatus: Exploitable\nContains: Planet\n\n${formatPlanet(planet)}`;
}

// ── Canvas click handler ──────────────────────────────────────────────────────

async function onCanvasClick(ev) {
  const cell = pickCell(ev);
  if (!cell || !isExploitable(cell.x, cell.y)) return;
  const key = cellKey(cell.x, cell.y);

  if (isOpponentTile(key)) return;

  if (isOwnTile(key)) { showLocalSelection(cell.x, cell.y); return; }

  if (cooldownActive()) {
    hint.textContent = "Cooldown active — reveal a tile your team already discovered to view stats.";
    return;
  }

  try {
    const res = await apiRevealCell(seedStr, cell.x, cell.y, currentTeam);
    if (res.status === 409) {
      hint.textContent = "This tile was already discovered by the other team.";
      await fetchGridForSeed(seedStr);
      draw();
      return;
    }
    if (res.status === 410) {
      hint.textContent = "World period changed — syncing.";
      await refreshFromServer();
      return;
    }
    if (!res.ok) throw new Error("reveal");
    applyRevealPayload(await res.json());
    startCooldown();
    updateEconomyDisplay();
    draw();
    fetchAndApplyScores();
  } catch (e) {
    if (e?.code === "SEED_EXPIRED") { await refreshFromServer(); return; }
    hint.textContent = "Could not save reveal — check server / database.";
  }
}

// ── Team segmented control ────────────────────────────────────────────────────

export function updateTeamSegmented() {
  teamTrack.dataset.active = currentTeam;
  teamBlueBtn.setAttribute("aria-pressed", currentTeam === "blue" ? "true" : "false");
  teamRedBtn.setAttribute("aria-pressed",  currentTeam === "red"  ? "true" : "false");
}

// ── Lightweight grid refresh (called every second) ────────────────────────────

/** Fetches the current grid and redraws without touching config or scores. */
export async function refreshGridDisplay() {
  try {
    await fetchGridForSeed(seedStr);
    updateEconomyDisplay();
    draw();
    refreshCursorFromLast();
  } catch { /* ignore */ }
}

// ── Full server refresh ───────────────────────────────────────────────────────
// Exported so auth.js / main.js can call it after login or on timer.

export async function refreshFromServer() {
  try {
    const seedChanged = await fetchConfig();
    if (seedChanged) {
      clearCooldown();
      resetEconScores();
      loadEconScores();
      details.textContent = "Stats are hidden until you click a tile.";
      details.classList.add("details--hidden");
      hint.textContent = "World period changed — grid reset. Click a cell in the playfield.";
    }
    await fetchGridForSeed(seedStr);
    await fetchAndApplyScores();
    updateEconomyDisplay();
    draw();
    refreshCursorFromLast();
  } catch {
    hint.textContent = "Could not sync with server. Is the API running?";
  }
}

// ── Event listeners ───────────────────────────────────────────────────────────

// ── Resource table sort (delegated, set up once) ──────────────────────────────

resourceTableEl?.addEventListener("click", (ev) => {
  const th = ev.target.closest("th[data-sort-col]");
  if (!th) return;
  const col = Number(th.dataset.sortCol);
  const { col: curCol, dir: curDir } = getEconSort();
  // Toggle direction if same column; otherwise default to desc for numeric cols, asc for text
  const newDir = col === curCol
    ? (curDir === "asc" ? "desc" : "asc")
    : (col === 2 || col === 3 ? "desc" : "asc");
  setEconSort(col, newDir);
  updateEconomyDisplay();
});

canvas.addEventListener("mousemove",  (ev) => { lastPointerEvent = ev; refreshCursor(ev); });
canvas.addEventListener("mouseleave", ()   => { lastPointerEvent = null; canvas.style.cursor = "default"; });
canvas.addEventListener("click",      onCanvasClick);

// Team switcher buttons (kept in codebase, only functional when admin-unlocked)
teamBlueBtn.addEventListener("click", () => { setCurrentTeam("blue"); updateTeamSegmented(); updateEconomyDisplay(); draw(); refreshCursorFromLast(); });
teamRedBtn.addEventListener( "click", () => { setCurrentTeam("red");  updateTeamSegmented(); updateEconomyDisplay(); draw(); refreshCursorFromLast(); });
