/* eslint-disable */
const { useState, useEffect, useMemo, useRef } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": "classic",
  "density": "cozy",
  "playerCount": 2
}/*EDITMODE-END*/;

function App() {
  const [state, setRawState] = useState(() => window.HordeState.loadState());
  const [tweaks, setTweaks] = useState(() => {
    try { const r = localStorage.getItem('horde-mode-tweaks'); return r ? {...TWEAK_DEFAULTS, ...JSON.parse(r)} : TWEAK_DEFAULTS; }
    catch { return TWEAK_DEFAULTS; }
  });
  const [tab, setTab] = useState('round');
  const [openedCard, setOpenedCard] = useState(null); // {card, kind}
  const [openedSheet, setOpenedSheet] = useState(null); // {rawLabel, unit}
  const [room, setRoom] = useState({status:'idle', code:null, peers:[], claimed:{}, error:null});
  const [showRoom, setShowRoom] = useState(false);
  const stateRef = useRef(state);
  useEffect(() => { stateRef.current = state; }, [state]);

  // myPid is local-only (which player slot we own). Persisted separately.
  const myPid = state.myPid ?? null;
  const isOnline = room.status === 'connected';

  // Wire HordeNet listeners once. Also auto-reconnect to last room.
  useEffect(() => {
    const Net = window.HordeNet; if (!Net) return;
    Net.on('status', (s) => setRoom(r => ({
      ...r,
      status: s,
      code: Net.getCode(),
      peers: Net.getPeers(),
      claimed: Net.getClaimed(),
      error: s === 'error' ? "Couldn't connect. Check the code and try again." : (s === 'connected' ? null : r.error),
    })));
    Net.on('peers', (peers) => setRoom(r => ({...r, peers, claimed: Net.getClaimed()})));
    Net.on('shared', (data) => {
      const cur = stateRef.current;
      const merged = {...cur, ...data, myPid: Net.getMyPid() ?? cur.myPid};
      if (data.players) {
        // Rebuild players from the authoritative server roster. For OUR slot,
        // the dealt secrets + chosen index come from the backend (HordeNet),
        // which is the source of truth — never broadcast in others' payloads.
        const sec = (Net.getSecrets && Net.getSecrets()) || {};
        merged.players = data.players.map(incoming => {
          if (incoming.id === merged.myPid) {
            const local = cur.players.find(p => p.id === incoming.id);
            return {
              ...incoming,
              secrets: (sec.secrets && sec.secrets.length ? sec.secrets : (local?.secrets || [])),
              secretChosen: sec.secretChosen ?? local?.secretChosen ?? null,
            };
          }
          return incoming;
        });
      }
      setRawState(merged);
      window.HordeState.saveState(merged);
    });
    // The backend deals our 2 secret objectives and tells us privately. Fold the
    // dealt secrets (and any persisted chosen index) into our own player slot.
    Net.on('secrets', (sec) => {
      const cur = stateRef.current;
      const pid = Net.getMyPid();
      if (pid == null) return;
      const players = cur.players.map(p => p.id === pid
        ? {...p, secrets: sec.secrets || [], secretChosen: sec.secretChosen ?? p.secretChosen ?? null}
        : p);
      const merged = {...cur, players};
      setRawState(merged);
      window.HordeState.saveState(merged);
    });
    // Auto-reconnect on app load if a previous room is in localStorage
    Net.tryReconnect && Net.tryReconnect();
  }, []);

  // Strip private fields before broadcasting.
  function redactState(s) {
    return {
      round: s.round, rounds: s.rounds, points: s.points, playerCount: s.playerCount,
      faction: s.faction, deployment: s.deployment, hardMode: s.hardMode, setup: s.setup,
      activeMisery: s.activeMisery, revealedSecondary: s.revealedSecondary,
      spawnRollLog: s.spawnRollLog, savedSpawns: s.savedSpawns, phasesDone: s.phasesDone,
      players: s.players.map(p => ({
        id: p.id, name: p.name, cp: p.cp, sp: p.sp,
        purchases: p.purchases, eliminated: p.eliminated,
        secretRevealed: p.secretRevealed,
        // Only include the chosen secret card # if revealed.
        secretChosen: p.secretRevealed ? p.secretChosen : null,
        secrets: p.secretRevealed ? p.secrets : [],
      })),
    };
  }

  function setState(s) {
    setRawState(s);
    window.HordeState.saveState(s);
    // Broadcast on every change while connected. Best-effort, last-write-wins.
    if (window.HordeNet?.isConnected()) {
      try { window.HordeNet.sendShared(redactState(s)); } catch {}
    }
  }

  async function hostRoom(hostName) {
    const cur = stateRef.current;
    // Reset to a 1-player game with the host's chosen name. The backend creates
    // the room from these settings; joiners are added server-side as they connect.
    const hostPlayer = {...window.HordeState.newPlayer(1), name: hostName || 'Host'};
    const next = {...cur, myPid: 1, playerCount: 1, players: [hostPlayer]};
    setState(next);
    try {
      const settings = {
        points: cur.points, rounds: cur.rounds, faction: cur.faction,
        deployment: cur.deployment, hardMode: cur.hardMode,
      };
      const { playerId } = await window.HordeNet.hostRoom(hostName || 'Host', settings);
      const withPid = {...stateRef.current, myPid: playerId};
      setState(withPid); // pushes our setup snapshot to the room
    } catch (e) { setRoom(r => ({...r, error: 'Connection failed'})); }
  }
  async function joinRoom(code, joinerName) {
    // The backend assigns our slot and streams full state via 'shared'.
    try {
      const { playerId } = await window.HordeNet.joinRoom(code, joinerName || 'Player');
      const cur = stateRef.current;
      setRawState({...cur, myPid: playerId});
      window.HordeState.saveState({...cur, myPid: playerId});
    } catch (e) { setRoom(r => ({...r, error: 'Connection failed'})); }
  }
  function leaveRoom() {
    window.HordeNet.disconnect();
    setRoom({status:'idle', code:null, peers:[], claimed:{}, error:null});
    setState({...state, myPid: null});
  }
  function setTweak(keyOrObj, val) {
    const patch = typeof keyOrObj === 'string' ? {[keyOrObj]: val} : keyOrObj;
    const next = {...tweaks, ...patch};
    setTweaks(next);
    try { localStorage.setItem('horde-mode-tweaks', JSON.stringify(next)); } catch {}
    try { window.parent.postMessage({type:'__edit_mode_set_keys', edits: patch}, '*'); } catch {}
  }

  // Apply tweaks to root attributes
  useEffect(() => {
    document.documentElement.dataset.palette = tweaks.palette;
    document.documentElement.dataset.density = tweaks.density;
  }, [tweaks.palette, tweaks.density]);

  // Apply demo player count when not in a game
  useEffect(() => {
    if (state.setup) return;
    if (tweaks.playerCount && tweaks.playerCount !== state.playerCount) {
      const players = [];
      for (let i = 1; i <= tweaks.playerCount; i++) players.push(window.HordeState.newPlayer(i));
      setState({...state, playerCount: tweaks.playerCount, players});
    }
  }, [tweaks.playerCount]);

  function openCard(card, kind) { setOpenedCard({card, kind}); }
  function closeCard() { setOpenedCard(null); }
  // Open the datasheet for a spawned unit (Orks only for now). Resolves the
  // spawn-table label (e.g. "20 Boyz + Leader") down to its canonical datasheet.
  function openDatasheet(rawLabel) {
    if (!rawLabel) return;
    const unit = (state.faction === 'Orks' && window.resolveOrkDatasheet)
      ? window.resolveOrkDatasheet(rawLabel) : null;
    setOpenedSheet({ rawLabel, unit });
  }
  function closeDatasheet() { setOpenedSheet(null); }

  // Player switcher
  const playerOptions = [
    { id: 0, label: 'GM', short: 'GM' },
    ...state.players.map(p => ({ id: p.id, label: p.name, short: `P${p.id}`, eliminated: p.eliminated })),
  ];

  // SETUP screen
  if (!state.setup) {
    return <SetupScreen state={state} setState={setState} onJoinRoom={joinRoom} room={room}/>;
  }

  // END-OF-GAME screen
  if (state.gameOver) {
    return <EndGameScreen state={state} setState={setState} openCard={openCard}/>;
  }

  const navItems = [
    { id: 'round', label: 'Round', Icon: Icon.Round, badge: state.round },
    { id: 'dash', label: state.activePlayer === 0 ? 'Shared' : `P${state.activePlayer}`, Icon: Icon.Player },
    { id: 'spawn', label: 'Spawn', Icon: Icon.Spawn },
    { id: 'shop', label: 'Shop', Icon: Icon.Shop },
    { id: 'decks', label: 'Decks', Icon: Icon.Cards },
    { id: 'rules', label: 'Rules', Icon: Icon.Book },
  ];

  return (
    <div className="app-shell">
      <header className="app-header">
        <div className="brand">
          <div className="logo-mark">40K</div>
          <div className="title">
            <div className="word1">HORDE</div>
            <div className="word2">MODE · COMPANION</div>
          </div>
        </div>
        <div className="header-right" style={{display:'flex', gap:6, alignItems:'center'}}>
          <button className={`room-btn ${isOnline ? 'on' : ''}`} onClick={() => setShowRoom(true)} title="Room">
            <span className="net-dot"/>
            {isOnline ? room.code : 'SOLO'}
          </button>
          <div className="round-pill">R{state.round}{state.rounds === 5 ? `/5` : '∞'}</div>
        </div>
      </header>

      <div className="player-tabs">
        {playerOptions.map(p => (
          <button key={p.id}
            className={`player-tab ${state.activePlayer === p.id ? 'active' : ''} ${p.eliminated ? 'ko' : ''}`}
            onClick={() => setState({...state, activePlayer: p.id})}>
            <span className="dot"/>
            <span className="label">{p.label}</span>
            {p.id !== 0 && state.players.find(pp => pp.id === p.id) && (
              <span className="meta">{state.players.find(pp => pp.id === p.id).sp}sp</span>
            )}
          </button>
        ))}
      </div>

      <main className="app-main">
        {tab === 'round' && <RoundScreen state={state} setState={setState} openCard={openCard} openDatasheet={openDatasheet}/>}
        {tab === 'dash' && <DashboardScreen state={state} setState={setState} openCard={openCard}/>}
        {tab === 'spawn' && <SpawnScreen state={state} setState={setState}/>}
        {tab === 'shop' && <ShopScreen state={state} setState={setState} openCard={openCard}/>}
        {tab === 'decks' && <DecksScreen state={state} setState={setState} openCard={openCard}/>}
        {tab === 'rules' && <RulesScreen state={state} setState={setState}/>}
      </main>

      <nav className="bottom-nav">
        {navItems.map(item => (
          <button key={item.id} className={`nav-item ${tab === item.id ? 'active' : ''}`} onClick={() => setTab(item.id)}>
            <item.Icon width="22" height="22"/>
            <span className="lbl">{item.label}</span>
          </button>
        ))}
      </nav>

      {openedCard && <CardDetailDrawer entry={openedCard} onClose={closeCard} state={state} setState={setState}/>}
      {openedSheet && <DatasheetDrawer unit={openedSheet.unit} rawLabel={openedSheet.rawLabel} onClose={closeDatasheet}/>}
      {showRoom && <RoomDrawer state={state} setState={setState} room={room} onClose={() => setShowRoom(false)} hostRoom={hostRoom} joinRoom={joinRoom} leaveRoom={leaveRoom}/>}

      <TweaksDrawer tweaks={tweaks} setTweak={setTweak} state={state} setState={setState}/>
    </div>
  );
}

// ============ CARD DETAIL DRAWER ============
function CardDetailDrawer({ entry, onClose, state, setState }) {
  const { card, kind } = entry;
  const accent = { misery:'misery', secondary:'secondary-c', secret:'secret', resupply:'resupply' }[kind] || '';
  const isResupply = kind === 'resupply';
  const player = state.players.find(p => p.id === state.activePlayer);
  const cost = isResupply ? (card.cost === 'X' ? 0 : +card.cost) : 0;
  const canBuy = isResupply && player && !player.eliminated && player.sp >= cost;

  function buy() {
    if (!canBuy) return;
    setState({...state, players: state.players.map(p =>
      p.id === player.id ? {...p, sp: p.sp - cost, purchases: [...p.purchases, card.name]} : p
    )});
    onClose();
  }
  function activate() {
    if (kind === 'misery') {
      if (!state.activeMisery.includes(card.n)) {
        setState({...state, activeMisery: [...state.activeMisery, card.n]});
      }
      onClose();
    } else if (kind === 'secondary') {
      setState({...state, revealedSecondary: card.n});
      onClose();
    }
  }

  return (
    <Drawer open={true} onClose={onClose} accent={accent} title={card.name}>
      {card.image && (
        <div style={{textAlign:'center', marginBottom:'var(--pad-3)'}}>
          <img src={card.image} alt={card.name} style={{maxWidth:'100%', maxHeight:'62dvh', display:'inline-block'}}/>
        </div>
      )}
      {isResupply && (
        <div className="tile">
          <div className="kv"><span className="k">Cost</span><span className="v">{card.cost} SP</span></div>
          {card.tags && <div className="kv"><span className="k">Tags</span><span className="v">{card.tags.join(' · ')}</span></div>}
        </div>
      )}
      <div className="row gap-3" style={{marginTop:'var(--pad-3)'}}>
        {kind === 'misery' && (
          <button className="btn misery grow" onClick={activate}>Add to Active Misery</button>
        )}
        {kind === 'secondary' && (
          <button className="btn secondary-c grow" onClick={activate}>Set as This Round's Secondary</button>
        )}
        {kind === 'resupply' && (
          <button className={`btn resupply grow`} onClick={buy} disabled={!canBuy}>
            {!player || player.id === 0 ? 'Switch to a player' : canBuy ? `Buy for ${cost} SP` : `Need ${cost} SP`}
          </button>
        )}
        <button className="btn ghost grow" onClick={onClose}>Close</button>
      </div>
    </Drawer>
  );
}

// ============ TWEAKS DRAWER ============
function TweaksDrawer({ tweaks, setTweak, state, setState }) {
  return (
    <TweaksPanel title="Tweaks" noDeckControls={true}>
      <TweakSection title="Color palette">
        <TweakRadio
          value={tweaks.palette}
          onChange={(v) => setTweak('palette', v)}
          options={[
            {value:'classic', label:'Classic'},
            {value:'toxic', label:'Toxic'},
            {value:'nightfall', label:'Nightfall'},
            {value:'bone', label:'Bone'},
          ]}
        />
        <PaletteSwatches palette={tweaks.palette}/>
      </TweakSection>

      <TweakSection title="Density">
        <TweakRadio
          value={tweaks.density}
          onChange={(v) => setTweak('density', v)}
          options={[
            {value:'compact', label:'Compact'},
            {value:'cozy', label:'Cozy'},
          ]}
        />
      </TweakSection>

      <TweakSection title="Demo players">
        <TweakRadio
          value={tweaks.playerCount}
          onChange={(v) => setTweak('playerCount', v)}
          options={[
            {value:1, label:'1'},
            {value:2, label:'2'},
            {value:3, label:'3'},
            {value:4, label:'4'},
          ]}
        />
        <div className="muted" style={{fontSize:11, marginTop:6}}>Affects setup default. To change mid-game, reset.</div>
      </TweakSection>

      <TweakSection title="Quick actions">
        <button className="btn sm subtle" style={{width:'100%', marginBottom:6}} onClick={() => {
          setState({...state, players: state.players.map(p => ({...p, sp: p.sp + 5}))});
        }}>+5 SP all players</button>
        <button className="btn sm subtle" style={{width:'100%', marginBottom:6}} onClick={() => {
          setState({...state, round: Math.min(state.round + 1, state.rounds === 5 ? 5 : 99), activeMisery: [], revealedSecondary: null});
        }}>Skip to next round</button>
        <button className="btn sm danger" style={{width:'100%'}} onClick={() => {
          if (confirm('Reset entire game?')) setState(window.HordeState.defaultGameState());
        }}>Reset game</button>
      </TweakSection>
    </TweaksPanel>
  );
}

function PaletteSwatches({ palette }) {
  const swatches = window.HordeState.PALETTES[palette] || {};
  return (
    <div className="row" style={{gap:6, marginTop:8}}>
      {Object.entries(swatches).map(([k,v]) =>
        <div key={k} style={{flex:1, height:24, background:v, border:'1px solid rgba(255,255,255,0.1)'}} title={k}/>
      )}
    </div>
  );
}

// ===== ROOM DRAWER =====
function RoomDrawer({ state, setState, room, onClose, hostRoom, joinRoom, leaveRoom }) {
  const [mode, setMode] = useState(room.status === 'connected' ? 'live' : 'menu');
  const [code, setCode] = useState('');
  const me = state.players.find(p => p.id === state.myPid);
  const [hostName, setHostName] = useState(me?.name || 'Host');
  const [joinName, setJoinName] = useState('');
  useEffect(() => { setMode(room.status === 'connected' ? 'live' : 'menu'); }, [room.status]);

  return (
    <Drawer open={true} onClose={onClose} title="Multiplayer Room">
      {mode === 'menu' && (
        <div className="col gap-3">
          <div className="muted" style={{fontSize:12}}>Sync this game across phones via WebRTC. No accounts, no servers — just a 4-letter room code. Players type their name when they join.</div>
          <div className="tile">
            <div className="eyebrow">Host a new room</div>
            <input value={hostName} onChange={e => setHostName(e.target.value.slice(0,24))} placeholder="Your name" maxLength={24}
              style={{width:'100%', marginTop:6, padding:'12px', fontFamily:'var(--display)', fontWeight:700, fontSize:16, background:'var(--bg-3)', border:'1px solid var(--line)', color:'var(--ink)'}}/>
            <button className="btn full" style={{marginTop:8}} disabled={hostName.trim().length === 0} onClick={async () => { await hostRoom(hostName.trim()); }}>Host new room</button>
            <div className="muted" style={{fontSize:11, marginTop:6}}>Hosting wipes the demo player list — joiners populate it as they connect.</div>
          </div>
          <div className="hr"/>
          <div className="tile">
            <div className="eyebrow">Join existing room</div>
            <input value={code} onChange={e => setCode(e.target.value.toUpperCase())} placeholder="ABCD" maxLength={4}
              style={{width:'100%', marginTop:6, padding:'12px', fontFamily:'var(--display)', fontStyle:'italic', fontWeight:800, fontSize:24, letterSpacing:'0.2em', textAlign:'center', background:'var(--bg-3)', border:'1px solid var(--line)', color:'var(--ink)', textTransform:'uppercase'}}/>
            <input value={joinName} onChange={e => setJoinName(e.target.value.slice(0,24))} placeholder="Your name" maxLength={24}
              style={{width:'100%', marginTop:8, padding:'12px', fontFamily:'var(--display)', fontWeight:700, fontSize:16, background:'var(--bg-3)', border:'1px solid var(--line)', color:'var(--ink)'}}/>
            <button className="btn full" style={{marginTop:8}} disabled={code.length !== 4 || joinName.trim().length === 0} onClick={() => joinRoom(code, joinName.trim())}>Join</button>
          </div>
        </div>
      )}
      {mode === 'live' && (
        <div className="col gap-3">
          <div className="tile" style={{textAlign:'center', padding:'var(--pad-4)'}}>
            <div className="eyebrow">Room code</div>
            <div className="display h2" style={{fontSize:48, letterSpacing:'0.2em', color:'var(--c-resupply)', marginTop:6}}>{room.code}</div>
            <div className="muted" style={{fontSize:12, marginTop:6}}>Share this code with teammates.</div>
            <div className="muted" style={{fontSize:10, marginTop:8, opacity:0.6}}>Channel: {window.HordeNet?.getStrategy?.() || '?'} · {window.HordeNet?.getAppVersion?.() || ''}</div>
          </div>
          <div className="tile">
            <div className="row between">
              <div className="eyebrow">Players in room</div>
              <span className="chip resupply">{state.players.length} · LIVE</span>
            </div>
            <div className="col gap-1" style={{marginTop:8}}>
              {state.players.map(p => {
                const isMe = p.id === state.myPid;
                return (
                  <div key={p.id} className="row between" style={{padding:'6px 0', borderTop:isMe?'none':'1px solid var(--line-soft)'}}>
                    <div className="display" style={{fontSize:13, color: isMe ? 'var(--ink)' : 'var(--ink-2)'}}>
                      P{p.id} · {p.name}{isMe ? ' (you)' : ''}
                    </div>
                    <span className={isMe ? 'chip resupply' : 'muted'} style={{fontSize: isMe ? 9 : 11}}>{isMe ? 'YOU' : 'connected'}</span>
                  </div>
                );
              })}
              {room.peers.length + 1 > state.players.length && (
                <div className="muted" style={{fontSize:11, marginTop:6}}>{room.peers.length + 1 - state.players.length} peer(s) connected but not yet joined as players.</div>
              )}
            </div>
          </div>
          <div className="row gap-3">
            <button className="btn ghost grow" onClick={leaveRoom}>Leave room</button>
            <button className="btn danger grow" onClick={() => {
              if (confirm('End the game and wipe local state? Other players keep their copies until they end too.')) {
                window.HordeNet.disconnect();
                setRoom({status:'idle', code:null, peers:[], claimed:{}, error:null});
                setState(window.HordeState.defaultGameState());
                onClose();
              }
            }}>End game</button>
          </div>
        </div>
      )}
      {room.status === 'connecting' && (
        <div className="center" style={{padding:'var(--pad-4)'}}>
          <div className="display" style={{color:'var(--ink-3)'}}>CONNECTING…</div>
        </div>
      )}
      {room.error && <div className="chip misery" style={{marginTop:8}}>{room.error}</div>}
    </Drawer>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
