// app.jsx — main controller (multi-device, server-driven)
const { useState, useEffect, useRef, useCallback } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#ff5e3a",
  "accent2": "#ffd23f",
  "metaphor": "num",
  "sfx": true,
  "scanlines": true
}/*EDITMODE-END*/;

const ACCENT_PALETTES = [
  ["#ff5e3a", "#ffd23f"],
  ["#ff3aa3", "#7dffd0"],
  ["#00d4ff", "#ffd23f"],
  ["#a855f7", "#fde047"],
  ["#36d399", "#ffd23f"],
];

function App() {
  const [t, setTweak] = window.useTweaks(TWEAK_DEFAULTS);

  // Session state
  const [myTeam, setMyTeam] = useState(null);
  const [isHost, setIsHost] = useState(false);

  // Server-driven game state
  const [displayState, setDisplayState] = useState(null); // what's rendered
  const [timerState, setTimerState] = useState(null);      // live tick data
  const [uiPhase, setUiPhase] = useState('in');            // fade-in | fade-out
  const [errorMsg, setErrorMsg] = useState(null);
  const [showExit, setShowExit] = useState(false);
  const [showLeaveConfirm, setShowLeaveConfirm] = useState(false);
  const [localMode, setLocalMode] = useState(false);

  // Refs to avoid stale closure issues inside socket handlers
  const prevPhaseRef = useRef(null);
  const transitionRef = useRef(false);

  // Apply tweaks to CSS vars
  useEffect(() => {
    document.documentElement.style.setProperty('--accent', t.accent);
    document.documentElement.style.setProperty('--accent-2', t.accent2);
    document.documentElement.style.setProperty('--accent2', t.accent2);
  }, [t.accent, t.accent2]);
  useEffect(() => { window.setSfxMuted && window.setSfxMuted(!t.sfx); }, [t.sfx]);
  useEffect(() => { document.body.classList.toggle('no-scanlines', !t.scanlines); }, [t.scanlines]);

  // Wire up all socket listeners once
  useEffect(() => {
    const sc = window.socketClient;

    sc.on('room_state', (newState) => {
      const prevPhase = prevPhaseRef.current;
      const newPhase = newState && newState.phase;

      if (prevPhase === null || prevPhase === newPhase) {
        // Same phase or first load — update directly, no animation
        prevPhaseRef.current = newPhase;
        setDisplayState(newState);
      } else if (!transitionRef.current) {
        // Phase changed — do fade-out → swap → fade-in
        transitionRef.current = true;
        setUiPhase('out');
        window.sfx && window.sfx.woosh();
        setTimeout(() => {
          prevPhaseRef.current = newPhase;
          setDisplayState(newState);
          setTimerState(null);
          setTimeout(() => {
            setUiPhase('in');
            transitionRef.current = false;
          }, 20);
        }, 240);
      } else {
        // Already transitioning — apply after current transition ends
        setTimeout(() => {
          prevPhaseRef.current = newPhase;
          setDisplayState(newState);
        }, 280);
      }
    });

    sc.on('session_info', ({ myTeam: name, isHost: host }) => {
      setMyTeam(name);
      setIsHost(host);
    });

    sc.on('timer_tick', (tick) => {
      setTimerState(tick);
    });

    sc.on('error', ({ message }) => {
      setErrorMsg(message);
      setTimeout(() => setErrorMsg(null), 3500);
    });

    sc.on('host_quit', () => {
      setMyTeam(null);
      setIsHost(false);
      setDisplayState(null);
      setTimerState(null);
      prevPhaseRef.current = null;
      localStorage.removeItem('gs-session');
      localStorage.removeItem('gs-team');
      setErrorMsg('The host ended the game.');
      setTimeout(() => setErrorMsg(null), 4000);
    });

    return () => {
      sc.off('room_state');
      sc.off('session_info');
      sc.off('timer_tick');
      sc.off('error');
      sc.off('host_quit');
    };
  }, []);

  const ds = displayState;
  const isActiveTeam = ds && myTeam && ds.teams[ds.activeTeamIndex] && ds.teams[ds.activeTeamIndex].name === myTeam;
  const inGame = ds && ds.phase !== 'lobby' && ds.phase !== 'end';
  const phaseClass = uiPhase === 'out' ? 'fade-out' : 'fade-in';

  // Merge timer tick data with base state for display
  const effectiveTimer = timerState || (ds && ds.cardStates ? {
    timeLeft: ds.cardStates[ds.activeCardIndex] ? ds.cardStates[ds.activeCardIndex].timeLeft : 0,
    cardStates: ds.cardStates,
    activeCardIndex: ds.activeCardIndex,
    bufferTimeLeft: ds.bufferTimeLeft || 0,
    bufferWord: ds.bufferWord || null,
  } : null);

  const isLastTurn = (state) => {
    if (!state) return false;
    const ni = (state.activeTeamIndex + 1) % state.teams.length;
    const roundOver = ni === 0;
    const nextRound = roundOver ? state.round + 1 : state.round;
    return nextRound > state.rounds;
  };

  const leaveRoom = () => {
    window.socketClient.emit('leave_room');
    setMyTeam(null);
    setIsHost(false);
    setDisplayState(null);
    setTimerState(null);
    prevPhaseRef.current = null;
    localStorage.removeItem('gs-session');
    localStorage.removeItem('gs-team');
    setShowExit(false);
    setShowLeaveConfirm(false);
  };

  // ── Screen selection ─────────────────────────────────────────────────────
  let screen = null;

  if (localMode) {
    return (
      <LocalGame
        metaphor={t.metaphor}
        onExit={() => setLocalMode(false)}
      />
    );
  }

  if (!ds || ds.phase === 'lobby') {
    screen = (
      <LobbyScreen
        roomState={ds}
        myTeam={myTeam}
        isHost={isHost}
        onLocalMode={() => setLocalMode(true)}
      />
    );
  } else if (ds.phase === 'end') {
    screen = (
      <EndScreen
        teams={ds.teams}
        onRestart={() => window.socketClient.emit('restart_game')}
        onAddRound={ds.teams.length >= 2 ? () => window.socketClient.emit('add_round') : null}
      />
    );
  } else if (isActiveTeam) {
    // ── Active team screens ────────────────────────────────────────────────
    if (ds.phase === 'pick') {
      screen = (
        <PickScreen
          slots={ds.currentSlots}
          teamName={myTeam}
          metaphor={t.metaphor}
          onDone={(cards) => {
            window.sfx && window.sfx.lock();
            window.socketClient.emit('lock_cards', { picks: cards });
          }}
        />
      );
    } else if (ds.phase === 'order') {
      screen = (
        <OrderScreen
          cards={ds.chosenCards}
          teamName={myTeam}
          metaphor={t.metaphor}
          onReady={(sequenced) => {
            window.socketClient.emit('order_cards', { order: sequenced });
          }}
        />
      );
    } else if (ds.phase === 'play') {
      screen = (
        <PlayScreen
          cards={ds.orderedCards}
          teamName={myTeam}
          metaphor={t.metaphor}
          timeLeft={effectiveTimer ? effectiveTimer.timeLeft : 0}
          cardStates={effectiveTimer ? effectiveTimer.cardStates : ds.cardStates}
          activeCardIndex={effectiveTimer ? effectiveTimer.activeCardIndex : ds.activeCardIndex}
          bufferTimeLeft={effectiveTimer ? effectiveTimer.bufferTimeLeft : 0}
          onGuessed={() => window.socketClient.emit('card_guessed')}
          onEndTurn={() => window.socketClient.emit('end_turn')}
        />
      );
    } else if (ds.phase === 'result') {
      screen = (
        <ResultScreen
          cards={ds.orderedCards}
          guessed={ds.cardStates.map((s) => s.status === 'guessed')}
          teams={ds.teams}
          teamIndex={ds.activeTeamIndex}
          metaphor={t.metaphor}
          onNext={() => { window.sfx && window.sfx.woosh(); window.socketClient.emit('next_turn'); }}
          isLast={isLastTurn(ds)}
        />
      );
    }
  } else {
    // ── Waiting / spectator screens ────────────────────────────────────────
    if (ds.phase === 'result') {
      screen = (
        <ResultScreen
          cards={ds.orderedCards}
          guessed={ds.cardStates.map((s) => s.status === 'guessed')}
          teams={ds.teams}
          teamIndex={ds.activeTeamIndex}
          metaphor={t.metaphor}
          onNext={() => { window.sfx && window.sfx.woosh(); window.socketClient.emit('next_turn'); }}
          isLast={isLastTurn(ds)}
        />
      );
    } else {
      screen = (
        <WaitingScreen
          phase={ds.phase}
          activeTeam={ds.teams[ds.activeTeamIndex] ? ds.teams[ds.activeTeamIndex].name : ''}
          timeLeft={effectiveTimer ? effectiveTimer.timeLeft : 0}
          cardStates={effectiveTimer ? effectiveTimer.cardStates : ds.cardStates}
          activeCardIndex={effectiveTimer ? effectiveTimer.activeCardIndex : ds.activeCardIndex}
          bufferWord={effectiveTimer ? effectiveTimer.bufferWord : null}
        />
      );
    }
  }

  return (
    <>
      {inGame && ds && (
        <HUD
          teams={ds.teams}
          activeIdx={ds.activeTeamIndex}
          round={ds.round}
          totalRounds={ds.rounds}
          onExit={() => setShowExit(true)}
          onPlayerClick={null}
          lockActive={true}
        />
      )}

      {showExit && isHost && (
        <Modal
          icon="🎮"
          title="Host Options"
          body={ds && ds.endAfterRound
            ? "Game will end after this round's last turn — scores tallied then."
            : "End the game cleanly for everyone after this round, or bail now."}
          confirmLabel={ds && ds.endAfterRound ? null : "🚪 Leave Room"}
          tertiaryLabel={ds && !ds.endAfterRound ? "⏳ End After Round" : null}
          onConfirm={() => { setShowExit(false); setShowLeaveConfirm(true); }}
          onTertiary={() => {
            window.socketClient.emit('set_end_after');
            setShowExit(false);
          }}
          cancelLabel="Keep Playing"
          onCancel={() => setShowExit(false)}
          danger={true}
        />
      )}
      {showLeaveConfirm && (
        <Modal
          icon="⚠️"
          title="End game for everyone?"
          body="If you leave now, the game immediately ends for all players — no results screen."
          confirmLabel="End Game for All"
          cancelLabel="Stay"
          onConfirm={leaveRoom}
          onCancel={() => setShowLeaveConfirm(false)}
          danger={true}
        />
      )}
      {showExit && !isHost && (
        <Modal
          icon="🚪"
          title="Leave the game?"
          body="You'll go back to the lobby. Others keep playing."
          confirmLabel="Leave Room"
          cancelLabel="Keep Playing"
          onConfirm={leaveRoom}
          onCancel={() => setShowExit(false)}
        />
      )}

      {ds && ds.endAfterRound && inGame && (
        <div style={{
          position: 'fixed',
          top: 60, left: '50%', transform: 'translateX(-50%)',
          background: 'rgba(255, 210, 63, 0.95)',
          color: '#2a1207',
          fontFamily: 'var(--font-arcade)',
          fontSize: 9,
          letterSpacing: 2,
          padding: '6px 14px',
          borderRadius: 999,
          zIndex: 30,
          boxShadow: '0 4px 16px rgba(0,0,0,0.4)',
          pointerEvents: 'none',
          whiteSpace: 'nowrap',
        }}>
          ▲ ENDING AFTER THIS ROUND
        </div>
      )}

      {errorMsg && (
        <div style={{
          position: 'fixed',
          top: 68, left: '50%', transform: 'translateX(-50%)',
          background: 'rgba(255, 79, 94, 0.95)',
          color: '#fff',
          fontFamily: 'var(--font-arcade)',
          fontSize: 9,
          letterSpacing: 2,
          padding: '10px 18px',
          borderRadius: 999,
          zIndex: 100,
          boxShadow: '0 4px 16px rgba(0,0,0,0.4)',
          whiteSpace: 'nowrap',
          animation: 'fadeIn 200ms ease both',
        }}>
          {errorMsg}
        </div>
      )}

      <div className={phaseClass} style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
        {screen}
      </div>

      <TweaksPanel>
        <TweakSection label="Look" />
        <TweakColor
          label="Palette"
          value={[t.accent, t.accent2]}
          options={ACCENT_PALETTES}
          onChange={(v) => setTweak({ accent: v[0], accent2: v[1] })}
        />
        <TweakToggle
          label="CRT scanlines"
          value={t.scanlines}
          onChange={(v) => setTweak('scanlines', v)}
        />
        <TweakSection label="Gameplay" />
        <TweakRadio
          label="Points display"
          value={t.metaphor}
          options={['num', 'dots', 'bars']}
          onChange={(v) => setTweak('metaphor', v)}
        />
        <TweakToggle
          label="Sound effects"
          value={t.sfx}
          onChange={(v) => setTweak('sfx', v)}
        />
      </TweaksPanel>
    </>
  );
}

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