// Player app — drives screens from socket state.

(() => {
const S = window.PlayerScreens;
const { Stage, GlobalRisoStyles } = window;

// Persistent ID for reconnection
function getPlayerId() {
  let id = localStorage.getItem('hf_player_id');
  if (!id) {
    id = 'p_' + Math.random().toString(36).slice(2, 10);
    localStorage.setItem('hf_player_id', id);
  }
  return id;
}
const PLAYER_ID = getPlayerId();
window.PLAYER_ID = PLAYER_ID;

// Read ?code=XXXX once, before first render, so QR scans skip the splash.
function readUrlCode() {
  try {
    const url = new URL(window.location.href);
    const c = (url.searchParams.get('code') || '').toUpperCase();
    return /^[A-Z0-9]{4}$/.test(c) ? c : '';
  } catch { return ''; }
}

function App() {
  const initialCode = readUrlCode();
  const initialName = localStorage.getItem('hf_player_name') || '';
  const savedRoomCode = localStorage.getItem('hf_player_room') || '';
  // Decide initial phase:
  //  - QR arrival → name-then-auto if no name, else joining
  //  - Returning player with saved code+name → joining (auto-rejoin)
  //  - Otherwise splash
  let initialPhase = 'splash';
  let initialCodeUsed = '';
  if (initialCode && initialName.length >= 2) {
    initialPhase = 'joining';
    initialCodeUsed = initialCode;
  } else if (initialCode) {
    initialPhase = 'name-then-auto';
    initialCodeUsed = initialCode;
  } else if (savedRoomCode && initialName.length >= 2) {
    initialPhase = 'joining';
    initialCodeUsed = savedRoomCode;
  }

  const [phase, setPhase] = React.useState(initialPhase);
  const [name, setName] = React.useState(initialName);
  const [code, setCode] = React.useState(initialCodeUsed);
  const [error, setError] = React.useState(null);
  const [room, setRoom] = React.useState(null);
  const [year, setYear] = React.useState(1995);
  const [locked, setLocked] = React.useState(false);
  const [reveal, setReveal] = React.useState(null); // { round, song, guesses }
  const [showStats, setShowStats] = React.useState(false);
  const [debugOpen, setDebugOpen] = React.useState(false);
  const longPressTimer = React.useRef(null);
  const socketRef = React.useRef(null);
  const triedAutoJoinRef = React.useRef(false);

  // Refs so the connect handler always sees the latest values without re-binding.
  const codeRef = React.useRef(code);
  const nameRef = React.useRef(name);
  const phaseRef = React.useRef(phase);
  React.useEffect(() => { codeRef.current = code; }, [code]);
  React.useEffect(() => { nameRef.current = name; }, [name]);
  React.useEffect(() => { phaseRef.current = phase; }, [phase]);

  // Connect socket once on mount. The 'connect' handler also re-attaches on
  // reconnect (iOS Safari drops the socket constantly), keeping server-side
  // state in sync with the client.
  React.useEffect(() => {
    const sock = io({ transports: ['websocket', 'polling'] });
    socketRef.current = sock;

    sock.on('connect', () => {
      const savedCode = codeRef.current || localStorage.getItem('hf_player_room');
      const savedName = nameRef.current || localStorage.getItem('hf_player_name');
      if (savedCode && savedName && savedName.length >= 2) {
        sock.emit('player:join',
          { code: savedCode, name: savedName, playerId: PLAYER_ID },
          (resp) => {
            if (resp?.ok) {
              if (phaseRef.current !== 'in-room') setPhase('in-room');
            } else if (resp?.error === 'no_room') {
              localStorage.removeItem('hf_player_room');
              if (phaseRef.current === 'joining' || phaseRef.current === 'in-room') {
                setPhase('splash');
                setRoom(null);
              }
            }
          });
      }
    });
    sock.on('room:state', (state) => {
      setRoom(state);
      // Reset locked when a new round starts
      if (state.phase === 'playing') {
        setLocked(false);
      }
    });
    sock.on('round:reveal', (payload) => {
      setReveal(payload);
    });
    sock.on('player:kicked', () => {
      setError('You were removed from the room.');
      setRoom(null);
      setPhase('splash');
    });
    return () => sock.disconnect();
  }, []);

  // Auto-join once socket is up and we're in "joining" phase (came in via QR with name set)
  React.useEffect(() => {
    if (phase !== 'joining' || !code || !name || triedAutoJoinRef.current) return;
    if (!socketRef.current) return;
    triedAutoJoinRef.current = true;
    doJoin(code, name);
  }, [phase, code, name]);

  // Reset the stats overlay when a new game starts.
  React.useEffect(() => {
    if (room?.phase && room.phase !== 'ended') setShowStats(false);
  }, [room?.phase]);

  const doJoin = (joinCode, joinName) => {
    setError(null);
    const finalName = joinName || name;
    socketRef.current.emit('player:join',
      { code: joinCode, name: finalName, playerId: PLAYER_ID },
      (resp) => {
        if (resp?.ok) {
          setCode(resp.code);
          localStorage.setItem('hf_player_name', finalName);
          localStorage.setItem('hf_player_room', resp.code);
          setPhase('in-room');
        } else {
          setError(resp?.error || 'unknown');
          // If auto-rejoin failed (e.g. saved room expired), drop the saved state
          // and put the user back on splash so they see options instead of a stuck loader.
          if (phase === 'joining') {
            triedAutoJoinRef.current = false;
            localStorage.removeItem('hf_player_room');
            setPhase('splash');
          }
        }
      });
  };

  const lockGuess = (y) => {
    setLocked(true);
    socketRef.current.emit('player:guess', { year: y }, (resp) => {
      if (!resp?.ok) setLocked(false);
    });
  };

  const goHome = () => {
    socketRef.current?.emit('player:leave', {});
    localStorage.removeItem('hf_player_room');
    setRoom(null);
    setReveal(null);
    setCode('');
    setLocked(false);
    setError(null);
    setPhase('splash');
    triedAutoJoinRef.current = false;
    try {
      const url = new URL(window.location.href);
      if (url.searchParams.has('code')) {
        url.searchParams.delete('code');
        window.history.replaceState({}, '', url.toString());
      }
    } catch (_) {}
  };

  // Compute the body for every phase. Single return at the end so home/state
  // inspector overlays render on every screen (including solo + onboarding).
  // Reveal data: prefer the live event payload, but fall back to room.lastReveal
  // (server-broadcast) so reconnecting mid-reveal still shows your scoring.
  const effectiveReveal = reveal || room?.lastReveal || null;
  const myGuess = effectiveReveal?.guesses?.find(g => g.id === PLAYER_ID);
  const me = room?.players.find(p => p.id === PLAYER_ID);

  let body;
  if (phase === 'solo') {
    body = <window.SoloApp onExit={() => setPhase('splash')} />;
  } else if (phase === 'splash') {
    body = <S.Splash onJoin={() => setPhase('name')} onSolo={() => setPhase('solo')} />;
  } else if (phase === 'name') {
    body = <S.Name name={name} setName={setName}
      onNext={() => setPhase('join')} onBack={() => setPhase('splash')} />;
  } else if (phase === 'name-then-auto') {
    body = <S.Name name={name} setName={setName}
      onNext={() => { setPhase('joining'); triedAutoJoinRef.current = false; }}
      onBack={() => setPhase('splash')} />;
  } else if (phase === 'joining') {
    body = <Loading />;
  } else if (phase === 'join') {
    body = <S.Join name={name} error={error}
      onJoin={(c) => doJoin(c)}
      onBack={() => { setError(null); setPhase('name'); }} />;
  } else if (!room) {
    body = <Loading />;
  } else if (room.phase === 'lobby') {
    body = <S.Lobby name={name} code={room.code} players={room.players} hostConnected={room.hostConnected} />;
  } else if (room.phase === 'playing') {
    body = <S.Guess
      round={room.round} year={year} setYear={setYear}
      onLock={lockGuess} players={room.players} locked={locked || !!me?.locked}
      roundDeadline={room.roundDeadline}
    />;
    if (locked || me?.locked) {
      body = <S.Locked year={year} players={room.players} roundDeadline={room.roundDeadline} />;
    }
  } else if (room.phase === 'revealed') {
    const song = effectiveReveal?.song ?? room.song;
    body = <S.Reveal
      year={song?.year ?? room.song?.year}
      song={song}
      myGuess={myGuess}
      guesses={effectiveReveal?.guesses ?? []}
      players={room.players}
      round={room.round}
    />;
  } else if (room.phase === 'ended') {
    if (showStats) {
      body = <window.FunStats history={room.history || []} onBack={() => setShowStats(false)} />;
    } else {
      body = <S.Final players={room.players} onSeeStats={() => setShowStats(true)} />;
    }
  } else {
    body = <Loading />;
  }

  // Persistent HITFORHIT logo — always top-left, tap = leave room,
  // long-press = state inspector.
  const showHomeLogo = phase !== 'splash';

  const onLogoDown = () => {
    longPressTimer.current = setTimeout(() => setDebugOpen(true), 700);
  };
  const onLogoUp = () => {
    if (longPressTimer.current) {
      clearTimeout(longPressTimer.current);
      longPressTimer.current = null;
    }
  };

  const resync = () => {
    const sock = socketRef.current;
    if (!sock) return;
    if (code && name) {
      sock.emit('player:join', { code, name, playerId: PLAYER_ID }, () => {});
    }
  };

  const homeLogo = showHomeLogo ? (
    <div
      onClick={goHome}
      onPointerDown={onLogoDown}
      onPointerUp={onLogoUp}
      onPointerLeave={onLogoUp}
      style={{
        position: 'fixed', top: 'calc(env(safe-area-inset-top, 0px) + 60px)', left: 10,
        height: 28, padding: '0 10px', zIndex: 900,
        background: window.R.paper, border: `2px solid ${window.R.ink}`,
        boxShadow: `2px 2px 0 ${window.R.ink}`,
        display: 'flex', alignItems: 'center',
        cursor: 'pointer', fontSize: 11, fontWeight: 900, letterSpacing: '.08em',
      }}
      title="Tap = leave · Long-press = state inspector"
    >HITFORHIT</div>
  ) : null;

  const debugOverlay = debugOpen ? (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 1100,
      background: 'rgba(26,26,26,.55)',
      display: 'flex', alignItems: 'flex-end',
    }} onClick={() => setDebugOpen(false)}>
      <div onClick={(e) => e.stopPropagation()} style={{
        width: '100%', maxWidth: 460, margin: '0 auto',
        background: window.R.paper, border: `2px solid ${window.R.ink}`,
        padding: '14px 18px calc(env(safe-area-inset-bottom, 0px) + 18px)',
        display: 'flex', flexDirection: 'column', gap: 10,
        fontFamily: 'ui-monospace, monospace', fontSize: 11, lineHeight: 1.5,
      }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <b style={{ fontFamily: 'inherit' }}>STATE</b>
          <span onClick={() => setDebugOpen(false)} style={{ cursor: 'pointer', fontSize: 16, padding: '0 8px' }}>×</span>
        </div>
        <div>phase: <b>{phase}</b></div>
        <div>socket: <b>{socketRef.current?.connected ? 'connected' : 'disconnected'}</b></div>
        <div>code: <b>{code || '—'}</b></div>
        <div>name: <b>{name || '—'}</b></div>
        <div>playerId: <b>{PLAYER_ID}</b></div>
        <div>room.phase: <b>{room?.phase || '—'}</b></div>
        <div>room.round: <b>{room?.round ?? '—'}</b></div>
        <div>players: <b>{room?.players?.length ?? 0}</b> · locked: <b>{room?.players?.filter(p => p.locked).length ?? 0}</b></div>
        <div>locked (me): <b>{String(locked)}</b></div>
        <div>reveal: <b>{effectiveReveal ? `r${effectiveReveal.round} ${effectiveReveal.song?.title || ''}` : '—'}</b></div>
        <div style={{ display: 'flex', gap: 8, marginTop: 6 }}>
          <window.Btn big={false} bg={window.R.green} onClick={() => { resync(); setDebugOpen(false); }}>↻ RE-SYNC</window.Btn>
          <window.Btn big={false} bg={window.R.pink} fg={window.R.paper} onClick={() => { goHome(); setDebugOpen(false); }}>RESET</window.Btn>
        </div>
      </div>
    </div>
  ) : null;

  return <Stage><GlobalRisoStyles />{body}{homeLogo}{debugOverlay}</Stage>;
}

function Loading() {
  return (
    <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', color: window.R.muted, fontWeight: 700 }}>
      Connecting…
    </div>
  );
}

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