// gw2-app.jsx — v2 root: navy dashboard layout, KPI grid, donut, table, tweaks.
const { useState: useS2a, useMemo: useMemo2, useEffect: useEff2a } = React;

const TWEAK_DEFAULTS_2 = /*EDITMODE-BEGIN*/{
  "accent": "#4d96f0",
  "outcome": ["#46d18a", "#f0697a"],
  "bg": "#0b1626",
  "density": "regular",
  "fsTitle": 22,
  "fsBase": 16,
  "fsBtn": 14
}/*EDITMODE-END*/;

const DENSITY2 = {
  compact: { pillPad: "5px 11px", cellGap: "6px", rowGap: "6px" },
  regular: { pillPad: "8px 13px", cellGap: "8px", rowGap: "8px" },
  comfy:   { pillPad: "11px 15px", cellGap: "11px", rowGap: "11px" },
};

const PUBLIC_ADMIN_KEY = "gw_admin_state_v1";
const SEL_GUILD_KEY = "gw_sel_guild_v1"; // remembers the chosen guild across reloads
const PUBLIC_STATE_API = "/api/state";
const WEEK_START_2 = 21;

function normalizeWeek2(week, fallback) {
  const n = Number(week);
  return Number.isFinite(n) && n >= WEEK_START_2 ? Math.round(n) : fallback;
}

function normalizePoints2(points, result) {
  const n = Number(points);
  const fallback = result === "loss" ? 0 : 20;
  return Math.max(0, Math.min(20, Number.isFinite(n) ? Math.round(n) : fallback));
}

function normalizeAttack2(a, prefix, idx) {
  const result = a.result === "loss" ? "loss" : "win";
  return {
    id: a.id || `${prefix}-a-${idx + 1}`,
    attacker: a.attacker || "Player",
    result,
    points: normalizePoints2(a.points, result),
    week: normalizeWeek2(a.week, WEEK_START_2 + idx),
    power: a.power || "",
    stars: Number.isFinite(a.stars) ? a.stars : (a.result === "loss" ? 0 : 3),
    stats: Array.isArray(a.stats) ? a.stats : [],
  };
}

function normalizeCard2(card, idx) {
  const id = card.id || `opp-${idx + 1}`;
  const bot = card.bot && typeof card.bot === "object" ? card.bot : {};
  const normalizeBotShots = (list, kind) => (Array.isArray(list) ? list : []).map((shot, i) => (
    typeof shot === "string" ? { id: shot } : { id: shot.id || `${id}-bot-${kind}-${i + 1}`, title: shot.title || "" }
  ));
  return {
    id,
    rank: idx + 1,
    name: card.name || "Opponent",
    power: card.power || "—",
    week: normalizeWeek2(card.week, WEEK_START_2),
    heroes: (card.heroes || []).map((a, i) => normalizeAttack2(a, `${id}-h`, i)),
    titans: (card.titans || []).map((a, i) => normalizeAttack2(a, `${id}-t`, i)),
    bot: {
      heroes: normalizeBotShots(bot.heroes, "heroes"),
      titans: normalizeBotShots(bot.titans, "titans"),
    },
  };
}

// Weeks are independent: the card week is authoritative. Older states may
// contain stale attack.week values left from the legacy multi-week card model;
// keep the card intact and align its attacks to the card's week.
function splitCard2(card) {
  const week = normalizeWeek2(card.week, WEEK_START_2);
  const align = (list) => (Array.isArray(list) ? list : []).map((attack) => Object.assign({}, attack, { week }));
  return [Object.assign({}, card, {
    week,
    heroes: align(card.heroes),
    titans: align(card.titans),
  })];
}

function fallbackPublicState2() {
  const guilds = GW_GUILDS.filter((g) => g.active !== false);
  const battles = {};
  guilds.forEach((g, gi) => {
    const src = gi === 0 ? GW_DATA : GW_DATA.slice(0, 8 + gi);
    battles[g.id] = src.reduce((out, card) => out.concat(splitCard2(card)), []).map((card, i) => normalizeCard2(card, i));
  });
  return { guilds, battles };
}

function publicStateFromAdmin2(state) {
  if (!state || !Array.isArray(state.guilds) || !state.battles) return null;
  const guilds = state.guilds.filter((g) => g.active);
  const battles = {};
  guilds.forEach((g) => {
    const cards = Array.isArray(state.battles[g.id]) ? state.battles[g.id] : [];
    battles[g.id] = cards.filter((c) => c.active)
      .reduce((out, card) => out.concat(splitCard2(card)), [])
      .map((card, i) => normalizeCard2(card, i));
  });
  return { guilds, battles };
}

function readPublicState2() {
  try {
    const raw = localStorage.getItem(PUBLIC_ADMIN_KEY);
    return publicStateFromAdmin2(raw ? JSON.parse(raw) : null) || fallbackPublicState2();
  } catch (e) {
    return fallbackPublicState2();
  }
}

async function fetchPublicState2() {
  const r = await fetch(PUBLIC_STATE_API, { cache: "no-store" });
  if (!r.ok) return null;
  const payload = await r.json();
  return publicStateFromAdmin2(payload.state);
}

function applyVars2(t) {
  const r = document.documentElement.style;
  r.setProperty("--accent", t.accent);
  r.setProperty("--win", t.outcome[0]);
  r.setProperty("--loss", t.outcome[1]);
  r.setProperty("--bg", t.bg);
  // Rich navy surface ramp (matches the admin) — kept independent of the bg
  // tweak so the panels stay deep blue rather than washing out toward grey.
  r.setProperty("--surface", "#15243c");
  r.setProperty("--surface-2", "#1d3050");
  r.setProperty("--card-border", "#29415f");
  r.setProperty("--fs-title", t.fsTitle + "px");
  r.setProperty("--fs-base", t.fsBase + "px");
  r.setProperty("--fs-btn", t.fsBtn + "px");
  const d = DENSITY2[t.density] || DENSITY2.regular;
  r.setProperty("--pill-pad", d.pillPad);
  r.setProperty("--cell-gap", d.cellGap);
  r.setProperty("--row-gap", d.rowGap);
}

function Filters2({ filter, setFilter, query, setQuery, counts }) {
  const opts = [
    { id: "all", label: hwT("all"), n: counts.all },
    { id: "win", label: hwT("wins"), n: counts.win },
    { id: "loss", label: hwT("losses"), n: counts.loss },
  ];
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
      <div style={{ display: "flex", gap: 4, background: "var(--surface-2)", border: "1px solid var(--card-border)", borderRadius: 999, padding: 4 }}>
        {opts.map((o) => {
          const on = filter === o.id;
          return (
            <button key={o.id} onClick={() => setFilter(o.id)} style={{
              padding: "6px 14px", borderRadius: 999, cursor: "pointer", border: "none",
              background: on ? "var(--accent)" : "transparent",
              color: on ? "#06101f" : "var(--text-dim)",
              fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: on ? 700 : 500,
              display: "flex", alignItems: "center", gap: 7,
            }}>
              {o.label}
              <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, opacity: .8 }}>{o.n}</span>
            </button>
          );
        })}
      </div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder={hwT("searchOpponent")} style={{
        minWidth: 170, maxWidth: 240, padding: "8px 14px", borderRadius: 999,
        background: "var(--surface-2)", border: "1px solid var(--card-border)", color: "var(--text)",
        fontFamily: "var(--font-sans)", fontSize: 13, outline: "none",
      }} />
    </div>
  );
}

function filterData2(data, filter, query) {
  const q = query.trim().toLowerCase();
  const matching = data.filter((o) => !q || o.name.toLowerCase().includes(q));
  if (filter === "all") return matching;
  return matching
    .map((o) => ({ ...o, heroes: o.heroes.filter((a) => a.result === filter), titans: o.titans.filter((a) => a.result === filter) }))
    .filter((o) => o.heroes.length + o.titans.length > 0);
}

function App2() {
  const [lang, setLang] = useHwLanguage();
  window.HW_LANG = lang;
  window.HW_SET_LANG = setLang;
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS_2);
  useMemo2(() => applyVars2(t), [t]);

  const [publicState, setPublicState] = useS2a(() => readPublicState2());
  // Remember the chosen guild across reloads (F5 keeps you on the same table).
  // No saved choice → null → welcome panel on first open.
  const [guild, setGuildState] = useS2a(() => { try { return localStorage.getItem(SEL_GUILD_KEY) || null; } catch (e) { return null; } });
  const setGuild = (id) => {
    setGuildState(id);
    try { if (id) localStorage.setItem(SEL_GUILD_KEY, id); else localStorage.removeItem(SEL_GUILD_KEY); } catch (e) { /* ignore */ }
  };
  const [filter, setFilter] = useS2a("all");
  const [query, setQuery] = useS2a("");
  const [popup, setPopup] = useS2a(null);
  const [botPopup, setBotPopup] = useS2a(null);
  const [botFightPopup, setBotFightPopup] = useS2a(null);
  const [botFightMode, setBotFightMode] = useS2a(false);

  useEff2a(() => {
    let alive = true;
    const refresh = () => fetchPublicState2()
      .then((remote) => { if (alive && remote) setPublicState(remote); })
      .catch(() => { if (alive) setPublicState(readPublicState2()); });
    const onStorage = (e) => { if (e.key === PUBLIC_ADMIN_KEY) refresh(); };
    window.addEventListener("storage", onStorage);
    window.addEventListener("focus", refresh);
    const timer = setInterval(() => { if (!document.hidden) refresh(); }, 5000);
    refresh();
    return () => {
      alive = false;
      window.removeEventListener("storage", onStorage);
      window.removeEventListener("focus", refresh);
      clearInterval(timer);
    };
  }, []);

  useEff2a(() => {
    if (guild == null) return; // "no selection" is a valid initial state
    if (publicState.guilds.some((g) => g.id === guild)) return;
    setGuild(null); // selected guild disappeared → back to the welcome panel
    setPopup(null);
    setBotPopup(null);
  }, [publicState, guild]);

  const away = publicState.guilds.find((g) => g.id === guild);
  const guildData = away ? (publicState.battles[away.id] || []) : [];
  const allAtk = guildData.flatMap((o) => [...o.heroes, ...o.titans]);
  const wins = allAtk.filter((a) => a.result === "win").length;
  const counts = { all: allAtk.length, win: wins, loss: allAtk.length - wins };
  const shown = useMemo2(() => filterData2(guildData, filter, query), [guildData, filter, query]);

  const tableHeader = (
    <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16, flexWrap: "wrap" }}>
      <div style={{ display: "flex", alignItems: "baseline", gap: 10 }}>
        <h2 style={{ margin: 0, fontSize: 18, fontWeight: 800, color: "var(--text)", letterSpacing: "-0.02em" }}>{hwT("battlesWithOpponent")}</h2>
        <span style={{ fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-faint)" }}>{hwT("opponentsCount", { count: shown.length })}</span>
      </div>
      <Filters2 filter={filter} setFilter={setFilter} query={query} setQuery={setQuery} counts={counts} />
    </div>
  );

  return (
    <div className="g2-app-root">
      <Toolbar guilds={publicState.guilds} value={guild} onChange={setGuild}
        botMode={botFightMode} onBotMode={(mode) => { setBotFightMode(mode); setBotFightPopup(null); }} />

      {away ? (
        <>
          <VSBanner2 home={GW_HOME} away={away} />

          {botFightMode ? (
            <BotFightBoard2 guild={away} onOpen={setBotFightPopup} />
          ) : (
            <BattleTable2 data={shown} header={tableHeader} onOpen={(atk) => {
              const opp = shown.find((o) => o.heroes.includes(atk) || o.titans.includes(atk));
              setPopup({ opp, atk });
            }} onOpenBot={(opp, kind) => {
              setBotPopup({ opp, kind });
            }} />
          )}
        </>
      ) : (
        <WelcomePanel2 home={GW_HOME} />
      )}

      <BattlePopup2 ctx={popup} onClose={() => setPopup(null)} />
      <BotPopup2 ctx={botPopup} onClose={() => setBotPopup(null)} />
      <BotFightPopup2 ctx={botFightPopup} onClose={() => setBotFightPopup(null)} />

      <TweaksPanel>
        <TweakSection label={hwT("colors")} />
        <TweakColor label={hwT("accent")} value={t.accent}
          options={["#4d96f0", "#46d18a", "#a774f0", "#f2b34e", "#ff6b9d"]}
          onChange={(v) => setTweak("accent", v)} />
        <TweakColor label={hwT("winLoss")} value={t.outcome}
          options={[["#46d18a", "#f0697a"], ["#3fd07a", "#ff6b6b"], ["#5ad1c0", "#f59e5b"], ["#7bd450", "#e0506e"]]}
          onChange={(v) => setTweak("outcome", v)} />
        <TweakColor label={hwT("background")} value={t.bg}
          options={["#0b1626", "#0c1320", "#0e1830", "#101622", "#0a1420"]}
          onChange={(v) => setTweak("bg", v)} />
        <TweakSection label={hwT("tableDensity")} />
        <TweakRadio label={hwT("density")} value={t.density} options={["compact", "regular", "comfy"]} onChange={(v) => setTweak("density", v)} />
        <TweakSection label={hwT("fontSize")} />
        <TweakSlider label={hwT("heading")} value={t.fsTitle} min={16} max={30} unit="px" onChange={(v) => setTweak("fsTitle", v)} />
        <TweakSlider label={hwT("text")} value={t.fsBase} min={12} max={18} unit="px" onChange={(v) => setTweak("fsBase", v)} />
        <TweakSlider label={hwT("buttonsNicks")} value={t.fsBtn} min={10} max={16} unit="px" onChange={(v) => setTweak("fsBtn", v)} />
      </TweaksPanel>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App2 />);
