// gw2-table.jsx — modern table with pill chips + battle popup (navy theme).
const { useState: useS2t, useEffect: useEff2t, useRef: useRef2t } = React;
const MAX_WEEKS_2 = 3; // show at most the 3 most recent weeks (older ones are pruned in admin)

// Compact attack pill — sized to its nickname so many fit in the week columns.
function Pill2({ atk, onOpen }) {
  const win = atk.result === "win";
  const c = win ? "var(--win)" : "var(--loss)";
  const points = Math.max(0, Math.min(20, Number.isFinite(Number(atk.points)) ? Math.round(Number(atk.points)) : (win ? 20 : 0)));
  return (
    <button onClick={() => onOpen(atk)} className="g2-pill" title={`${atk.attacker} · ${win ? hwT("victory").toLowerCase() : hwT("defeat").toLowerCase()}`} style={{
      display: "inline-flex", alignItems: "center", gap: 7, width: "100%", maxWidth: "100%",
      minHeight: 32, padding: "0 9px 0 0", borderRadius: 999, cursor: "pointer",
      background: `color-mix(in oklab, ${c} 17%, transparent)`,
      border: `1px solid color-mix(in oklab, ${c} 50%, transparent)`,
      fontFamily: "var(--font-mono)", fontSize: "var(--fs-btn)", fontWeight: 400, textAlign: "left",
      transition: "background .12s, transform .08s",
    }}>
      <span style={{
        width: 32, alignSelf: "stretch", minHeight: 30, borderRadius: 999, flexShrink: 0, display: "grid", placeItems: "center",
        background: `color-mix(in oklab, ${c} 26%, transparent)`,
        border: `1px solid color-mix(in oklab, ${c} 72%, transparent)`,
        color: "var(--text)", fontSize: 10.5, fontWeight: 800, lineHeight: 1,
      }}>{points}</span>
      <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: "var(--text)" }}>{atk.attacker}</span>
    </button>
  );
}

function Empty2() {
  return <div style={{ color: "var(--text-faint)", opacity: .4, fontFamily: "var(--font-mono)" }}>—</div>;
}

function weekLabel2(week) {
  return hwT("week", { week });
}

// Distinct weeks present in the data, newest first.
function weeksForData2(data) {
  const set = new Set();
  data.forEach((opp) => { const w = Number(opp.week); if (Number.isFinite(w)) set.add(w); });
  return Array.from(set).sort((a, b) => b - a);
}

function BotButton2({ kind, onOpen }) {
  const c = kind === "heroes" ? "var(--hero)" : "var(--titan)";
  return (
    <button onClick={onOpen} className="g2-botbtn" style={{
      display: "inline-flex", alignItems: "center", justifyContent: "center", width: "100%", height: "100%", minHeight: 32,
      padding: "0 10px", borderRadius: 16, cursor: "pointer",
      background: `color-mix(in oklab, ${c} 16%, transparent)`,
      border: `1px solid color-mix(in oklab, ${c} 52%, transparent)`,
      color: c, fontFamily: "var(--font-mono)", fontSize: 12, fontWeight: 800, letterSpacing: ".04em",
    }}>{hwT("bot")}</button>
  );
}

// One kind row inside an opponent card: coloured Герои/Титаны tag + attack pills,
// with the БОТ ("Помощь") button pinned to the right as its own column.
function KindRow2({ kind, label, attacks, onOpen, onOpenBot }) {
  const tone = kind === "heroes" ? "var(--hero)" : "var(--titan)";
  return (
    <div className="g2-kind-row">
      <span className="g2-kind-tag" style={{ color: tone, background: `color-mix(in oklab, ${tone} 16%, transparent)` }}>{label}</span>
      <div className="g2-kind-pills">
        {attacks.length ? attacks.map((a) => <Pill2 key={a.id} atk={a} onOpen={onOpen} />) : <Empty2 />}
      </div>
      <div className="g2-kind-bot"><BotButton2 kind={kind} onOpen={onOpenBot} /></div>
    </div>
  );
}

// A single opponent within a week.
function OppCard2({ opp, rank, weekText, onOpen, onOpenBot }) {
  return (
    <div className="g2-opp-card">
      <div className="g2-opp-head">
        <span className="g2-opp-rank">{String(rank).padStart(2, "0")}</span>
        <div className="g2-opp-name-wrap">
          <div className="g2-opp-name">{opp.name}</div>
        </div>
        <span className="g2-opp-week">{weekText}</span>
      </div>
      <KindRow2 kind="heroes" label={hwT("heroes")} attacks={opp.heroes} onOpen={onOpen} onOpenBot={() => onOpenBot(opp, "heroes")} />
      <KindRow2 kind="titans" label={hwT("titans")} attacks={opp.titans} onOpen={onOpen} onOpenBot={() => onOpenBot(opp, "titans")} />
    </div>
  );
}

// One week = one independent lane (its own opponents/players).
function WeekLane2({ week, opps, onOpen, onOpenBot }) {
  return (
    <div className="g2-lane">
      <div className="g2-lane-head">
        <span className="g2-lane-week">{weekLabel2(week)}</span>
        <span className="g2-lane-count">{hwT("opponentsCount", { count: opps.length })}</span>
      </div>
      <div className="g2-lane-body">
        {opps.length ? opps.map((opp, i) => (
          <OppCard2 key={opp.id} opp={opp} rank={i + 1} weekText={weekLabel2(week)} onOpen={onOpen} onOpenBot={onOpenBot} />
        )) : (
          <div className="g2-lane-empty">{weekLabel2(week)} · {hwT("nothingFound")}</div>
        )}
      </div>
    </div>
  );
}

function BattleTable2({ data, onOpen, onOpenBot, header }) {
  // newest 3 weeks; each lane is fully independent (no merging across weeks)
  const weeks = weeksForData2(data).slice(0, MAX_WEEKS_2);
  const weeksKey = weeks.join(",");
  const lanesRef = useRef2t(null);
  // which week's lane is currently in view (mobile swipes one lane at a time) —
  // shown in the table header instead of on every opponent card.
  const [activeWeek, setActiveWeek] = useS2t(weeks[0] != null ? weeks[0] : null);
  const canSwipeToOlderWeek = weeks.indexOf(activeWeek) < weeks.length - 1;
  useEff2t(() => {
    if (weeks.length && weeks.indexOf(activeWeek) === -1) setActiveWeek(weeks[0]);
  }, [weeksKey]);
  useEff2t(() => {
    const el = lanesRef.current;
    if (!el) return;
    const sync = () => {
      const center = el.scrollLeft + el.clientWidth / 2;
      const kids = el.children;
      for (let i = 0; i < kids.length; i++) {
        const c = kids[i];
        if (center >= c.offsetLeft && center < c.offsetLeft + c.offsetWidth) {
          const w = weeks[i];
          if (w != null) setActiveWeek(w);
          break;
        }
      }
    };
    sync();
    el.addEventListener("scroll", sync, { passive: true });
    return () => el.removeEventListener("scroll", sync);
  }, [weeksKey]);
  return (
    <Card pad={0} style={{ overflow: "visible" }}>
      <div className="g2-table-head" style={{ padding: "16px 20px", borderBottom: "1px solid var(--card-border)" }}>
        {header}
        {weeks.length > 1 && (
          <div className="g2-mobile-swipe-hint">{hwT("swipeForPreviousWeek")}</div>
        )}
        {activeWeek != null && (
          <div className="g2-mobile-week-nav">
            <div className="g2-head-week">{weekLabel2(activeWeek)}</div>
            {weeks.length > 1 && (
              <div className={"g2-swipe-cue" + (canSwipeToOlderWeek ? "" : " is-reverse")}
                aria-hidden="true">
                <span className="g2-swipe-track"></span>
                <span className="g2-swipe-finger"></span>
              </div>
            )}
          </div>
        )}
      </div>
      {weeks.length ? (
        <div className="g2-lanes" ref={lanesRef}>
          {weeks.map((week) => (
            <WeekLane2 key={week} week={week} opps={data.filter((o) => Number(o.week) === week)}
              onOpen={onOpen} onOpenBot={onOpenBot} />
          ))}
        </div>
      ) : (
        <div style={{ padding: "48px 0", textAlign: "center", color: "var(--text-faint)", fontFamily: "var(--font-mono)" }}>{hwT("nothingFound")}</div>
      )}
    </Card>
  );
}

// Read-only screenshot viewer for the popup. Screenshots are uploaded in the
// admin and stored via the Cloudflare API (refs to R2). We fetch ONLY the slots we need
// (?id=/?ids=) — the full sidecar can be several MB, and polling it in full
// repeatedly was pushing the tab into Out-of-Memory.
function loadSlotsByIds(ids) {
  if (!ids || !ids.length) return Promise.resolve({});
  const q = ids.length === 1
    ? "id=" + encodeURIComponent(ids[0])
    : "ids=" + encodeURIComponent(ids.join(","));
  return fetch("/api/image-slots?" + q + "&t=" + Date.now(), { cache: "no-store" })
    .then((r) => (r.ok ? r.json() : {}))
    .catch(() => ({}));
}

function ScreenshotViewer({ slotId, expanded, canExpand, onToggleExpanded }) {
  const [url, setUrl] = useS2t(null);
  const [done, setDone] = useS2t(false);
  const [natural, setNatural] = useS2t(null);
  useEff2t(() => {
    let alive = true;
    const refresh = () => {
      if (document.hidden) return;
      loadSlotsByIds([slotId]).then((j) => {
        if (!alive) return;
        const v = j && j[slotId];
        setUrl((window.HW_slotImageUrl && window.HW_slotImageUrl(v)) || null);
        setDone(true);
      });
    };
    refresh();
    const timer = setInterval(refresh, 5000);
    const onVis = () => { if (!document.hidden) refresh(); };
    document.addEventListener("visibilitychange", onVis);
    return () => { alive = false; clearInterval(timer); document.removeEventListener("visibilitychange", onVis); };
  }, [slotId]);
  const shellStyle = expanded ? {
    width: "100%", flex: 1, minHeight: 0, borderRadius: 12, border: "1px solid var(--card-border)",
    background: "var(--surface-2)", overflow: "hidden", display: "grid", placeItems: "center",
  } : {
    width: "100%", borderRadius: 12, border: "1px solid var(--card-border)",
    background: "var(--surface-2)", overflow: "hidden",
  };
  const cropAspect = natural ? `${natural.w * 0.6} / ${natural.h}` : "3 / 4";
  const frameStyle = expanded ? {
    height: "100%", maxWidth: "100%", aspectRatio: cropAspect, overflow: "hidden",
  } : {
    width: "100%", overflow: "hidden",
  };
  const clickable = !!url && !!canExpand;
  return (
    <div className={"g2-shot-viewer" + (expanded ? " g2-shot-expanded" : "")} onClick={clickable ? onToggleExpanded : undefined} style={Object.assign({ cursor: clickable ? (expanded ? "zoom-out" : "zoom-in") : "default" }, shellStyle)}>
      {url ? (
        <div style={frameStyle}>
          <img
            src={url}
            alt={hwT("battleScreenshot")}
            onLoad={(e) => setNatural({ w: e.currentTarget.naturalWidth || 1, h: e.currentTarget.naturalHeight || 1 })}
            style={{
              display: "block", width: "166.6667%", maxWidth: "none",
              height: expanded ? "100%" : "auto", marginLeft: "-33.3333%",
              objectFit: "fill", userSelect: "none", WebkitUserDrag: "none",
            }}
          />
        </div>
      ) : (
        <div style={{ minHeight: "min(420px, 56vh)", display: "grid", placeItems: "center", textAlign: "center", color: "var(--text-faint)", fontFamily: "var(--font-mono)", fontSize: 12, padding: 24, lineHeight: 1.6 }}>
          {done ? hwT("screenshotMissing") : hwT("loading")}
        </div>
      )}
    </div>
  );
}

function BotScreenshots2({ slotIds, loader = loadSlotsByIds }) {
  const [urls, setUrls] = useS2t([]);
  const [done, setDone] = useS2t(false);
  const [idx, setIdx] = useS2t(0);
  useEff2t(() => {
    let alive = true;
    const refresh = () => {
      if (document.hidden) return;
      loader(slotIds).then((j) => {
        if (!alive) return;
        const next = slotIds
          .map((id) => {
            const v = j && j[id];
            return (window.HW_slotImageUrl && window.HW_slotImageUrl(v)) || null;
          })
          .filter(Boolean);
        setUrls(next);
        setDone(true);
      });
    };
    refresh();
    const timer = setInterval(refresh, 5000);
    const onVis = () => { if (!document.hidden) refresh(); };
    document.addEventListener("visibilitychange", onVis);
    return () => { alive = false; clearInterval(timer); document.removeEventListener("visibilitychange", onVis); };
  }, [slotIds.join("|")]);
  useEff2t(() => { setIdx(0); }, [slotIds.join("|")]);
  useEff2t(() => {
    if (idx >= urls.length) setIdx(Math.max(0, urls.length - 1));
  }, [idx, urls.length]);
  const hasMany = urls.length > 1;
  const go = (dir) => setIdx((v) => {
    if (!urls.length) return 0;
    return (v + dir + urls.length) % urls.length;
  });
  return (
    <div className="g2-bot-slider">
      {urls.length ? (
        <>
          <img src={urls[idx]} alt={hwT("battleCollection")} />
          {hasMany && (
            <>
              <button className="g2-bot-arrow g2-bot-prev" onClick={() => go(-1)} title={hwT("previousScreenshot")}>‹</button>
              <button className="g2-bot-arrow g2-bot-next" onClick={() => go(1)} title={hwT("nextScreenshot")}>›</button>
              <div className="g2-bot-count">{idx + 1} / {urls.length}</div>
            </>
          )}
        </>
      ) : (
        <div className="g2-bot-empty">
          {done ? hwT("screenshotsMissing") : hwT("loading")}
        </div>
      )}
    </div>
  );
}

function BotPopup2({ ctx, onClose }) {
  useEff2t(() => {
    const h = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", h);
    return () => document.removeEventListener("keydown", h);
  }, []);
  if (!ctx) return null;
  const { opp, kind } = ctx;
  const isHero = kind === "heroes";
  const c = isHero ? "var(--hero)" : "var(--titan)";
  const shots = (opp.bot && Array.isArray(opp.bot[kind])) ? opp.bot[kind] : [];
  const slotIds = shots.map((s) => `admin-bot-${s.id}`);
  return (
    <div onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }} style={{
      position: "fixed", inset: 0, zIndex: 100, display: "grid", placeItems: "center",
      background: "rgba(5,10,20,.7)", backdropFilter: "blur(4px)", padding: "16px", overflow: "auto",
    }}>
      <div style={{
        width: "min(780px, 96vw)", background: "var(--surface)", border: "1px solid var(--card-border)",
        borderRadius: 18, overflow: "hidden", boxShadow: "0 40px 90px -24px rgba(0,0,0,.85)",
      }}>
        <div style={{ display: "flex", alignItems: "center", gap: 14, padding: "16px 18px", borderBottom: "1px solid var(--card-border)" }}>
          <div style={{
            fontFamily: "var(--font-mono)", fontSize: 11, fontWeight: 800, letterSpacing: ".08em", textTransform: "uppercase",
            padding: "6px 12px", borderRadius: 999, color: c, whiteSpace: "nowrap",
            background: `color-mix(in oklab, ${c} 18%, transparent)`, border: `1px solid color-mix(in oklab, ${c} 48%, transparent)`,
          }}>{hwT("bot")}</div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 16, fontWeight: 700, color: "var(--text)", display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
              <span style={{ fontFamily: "var(--font-mono)", color: c }}>{isHero ? hwT("heroes") : hwT("titans")}</span>
              <span style={{ color: "var(--text-faint)" }}>→</span>
              <span>{opp.name}</span>
            </div>
            <div style={{ fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-faint)", marginTop: 3 }}>{hwT("opponentNumber", { rank: String(opp.rank).padStart(2, "0") })}</div>
          </div>
          <button onClick={onClose} className="g2-x" style={{ width: 32, height: 32, borderRadius: 10, cursor: "pointer", flexShrink: 0, background: "var(--surface-2)", border: "1px solid var(--card-border)", color: "var(--text-dim)", fontSize: 15 }}>✕</button>
        </div>
        <div style={{ padding: 18 }}>
          <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: ".1em", textTransform: "uppercase", color: "var(--text-faint)", marginBottom: 10 }}>
            {hwT("battleCollection")}
          </div>
          <BotScreenshots2 slotIds={slotIds} />
        </div>
      </div>
    </div>
  );
}

function BattlePopup2({ ctx, onClose }) {
  const [expanded, setExpanded] = useS2t(false);
  const [canExpand, setCanExpand] = useS2t(() => typeof window === "undefined" ? true : window.matchMedia("(min-width: 721px)").matches);
  useEff2t(() => {
    const h = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", h);
    return () => document.removeEventListener("keydown", h);
  }, []);
  useEff2t(() => { setExpanded(false); }, [ctx && ctx.atk && ctx.atk.id]);
  useEff2t(() => {
    if (typeof window === "undefined" || !window.matchMedia) return;
    const mq = window.matchMedia("(min-width: 721px)");
    const sync = () => {
      setCanExpand(mq.matches);
      if (!mq.matches) setExpanded(false);
    };
    sync();
    if (mq.addEventListener) mq.addEventListener("change", sync);
    else mq.addListener(sync);
    return () => {
      if (mq.removeEventListener) mq.removeEventListener("change", sync);
      else mq.removeListener(sync);
    };
  }, []);
  useEff2t(() => {
    if (!expanded) return;
    const html = document.documentElement;
    const body = document.body;
    const prevHtmlOverflow = html.style.overflow;
    const prevBodyOverflow = body.style.overflow;
    html.style.overflow = "hidden";
    body.style.overflow = "hidden";
    return () => {
      html.style.overflow = prevHtmlOverflow;
      body.style.overflow = prevBodyOverflow;
    };
  }, [expanded]);
  if (!ctx) return null;
  const { opp, atk } = ctx;
  const win = atk.result === "win";
  const c = win ? "var(--win)" : "var(--loss)";
  const slotId = `admin-shot-${atk.id}`;
  return (
    <div onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }} style={{
      position: "fixed", inset: 0, zIndex: 100, display: "grid", placeItems: "center",
      background: "rgba(5,10,20,.7)", backdropFilter: "blur(4px)", padding: expanded ? 0 : "16px",
      overflow: expanded ? "hidden" : "auto",
    }}>
      <div style={{
        width: expanded ? "100vw" : "min(780px, 96vw)", height: expanded ? "100vh" : "auto",
        background: "var(--surface)", border: "1px solid var(--card-border)", borderRadius: expanded ? 0 : 18,
        overflow: "hidden", boxShadow: "0 40px 90px -24px rgba(0,0,0,.85)",
        display: expanded ? "flex" : "block", flexDirection: "column",
      }}>
        <div style={{ display: "flex", alignItems: "center", gap: 14, padding: "16px 18px", borderBottom: "1px solid var(--card-border)" }}>
          <div style={{
            fontFamily: "var(--font-mono)", fontSize: 11, fontWeight: 700, letterSpacing: ".08em", textTransform: "uppercase",
            padding: "6px 12px", borderRadius: 999, color: c, whiteSpace: "nowrap",
            background: `color-mix(in oklab, ${c} 16%, transparent)`, border: `1px solid color-mix(in oklab, ${c} 45%, transparent)`,
          }}>{win ? hwT("victory") : hwT("defeat")}</div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 16, fontWeight: 700, color: "var(--text)", display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
              <span style={{ fontFamily: "var(--font-mono)", color: "var(--accent)" }}>{atk.attacker}</span>
              <span style={{ color: "var(--text-faint)" }}>→</span>
              <span>{opp.name}</span>
            </div>
            <div style={{ fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-faint)", marginTop: 3 }}>{hwT("opponentNumber", { rank: String(opp.rank).padStart(2, "0") })}</div>
          </div>
          <button onClick={onClose} className="g2-x" style={{ width: 32, height: 32, borderRadius: 10, cursor: "pointer", flexShrink: 0, background: "var(--surface-2)", border: "1px solid var(--card-border)", color: "var(--text-dim)", fontSize: 15 }}>✕</button>
        </div>
        <div style={{
          padding: 18, flex: expanded ? 1 : "initial", minHeight: expanded ? 0 : "initial",
          display: expanded ? "flex" : "block", flexDirection: "column",
        }}>
          <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: ".1em", textTransform: "uppercase", color: "var(--text-faint)", marginBottom: 10 }}>
            {hwT("battleScreenshot")}
          </div>
          <ScreenshotViewer
            slotId={slotId}
            expanded={expanded}
            canExpand={canExpand}
            onToggleExpanded={() => setExpanded((v) => !v)}
          />
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { BattleTable2, BattlePopup2, BotPopup2 });
