﻿const { useState, useEffect, useCallback, useRef } = React;
        const { WORD_LENGTH, MAX_GUESSES, INITIAL_HP, FALLBACK_WORDS, POWER_UPS, KEYBOARD_ROWS, MAX_ONLINE_PLAYERS } = window.AppModules.constants;
        const { checkWord, summarizeResult, scorePlayerGuess } = window.AppModules.gameLogic;
        const FirebaseService = window.AppModules.firebaseService;
        const { useFirebaseAuth } = window.AppModules.hooks.useFirebaseAuth;
        const { useGameFlow } = window.AppModules.hooks.useGameFlow;
        const { useInputAndGuessFlow } = window.AppModules.hooks.useInputAndGuessFlow;
        const { useAISoloFlow } = window.AppModules.hooks.useAISoloFlow;
        const { useDictionary } = window.AppModules.hooks.useDictionary;
        const { useLobbyActions } = window.AppModules.hooks.useLobbyActions;
        const { useAIEngine } = window.AppModules.hooks.useAIEngine;
        const { useMultiplayer } = window.AppModules.hooks.useMultiplayer;

        const { BattleView, LobbyView } = window.AppComponents;

        const LIVE_OVERLAY_DEFAULTS = {
            enabled: false,
            tickerText: 'LIVE WORDLE BATTLE',
            tickerSpeedSec: 18,
            textScale: 100,
            tickerPosition: 'bottom',
        };

        const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
        const escapeHtml = (value) => String(value || '')
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#39;');

        const App = () => {
            const appId = typeof __app_id !== 'undefined' ? __app_id : 'wordle-battle-arena-fix';
            const [playerName, setPlayerName] = useState("BROSKI_" + Math.floor(Math.random()*9000 + 1000));
            const [playerAvatar, setPlayerAvatar] = useState('AN');
            const [playerAvatarImageUrl, setPlayerAvatarImageUrl] = useState('');
            const [playerAvatarStoragePath, setPlayerAvatarStoragePath] = useState('');
            const [opponentAvatarImageUrls, setOpponentAvatarImageUrls] = useState({});
            const [avatarUploadFile, setAvatarUploadFile] = useState(null);
            const [pendingAvatarPreviewUrl, setPendingAvatarPreviewUrl] = useState('');
            const [opponentName, setOpponentName] = useState("OPPONENT");
            const [profileStats, setProfileStats] = useState({ wins: 0, losses: 0, matches: 0, localWins: 0, localLosses: 0 });
            const [profileLoading, setProfileLoading] = useState(true);
            const [profileSaveState, setProfileSaveState] = useState({ saving: false, message: '' });
            const [authForm, setAuthForm] = useState({ phoneNumber: '', otpCode: '' });
            const [authActionLoading, setAuthActionLoading] = useState(false);
            const { dictionary, validGuessSet, isDictLoading, dictLoadStatus } = useDictionary({
                wordLength: WORD_LENGTH,
                fallbackWords: FALLBACK_WORDS,
                sourceUrl: 'https://snippet.host/negcdg/raw',
            });
            const [dictionaryNotice, setDictionaryNotice] = useState(null);
            const hasShownDictionaryNoticeRef = useRef(false);

            const [view, setView] = useState('lobby'); 
            const [gameMode, setGameMode] = useState('battle');
            const [battleRounds, setBattleRounds] = useState(3);
            const [hardCapEnabled, setHardCapEnabled] = useState(false);
            const [checkWordEnabled, setCheckWordEnabled] = useState(false);
            const [lobbyMaxPlayers, setLobbyMaxPlayers] = useState(2);
            const [isPaused, setIsPaused] = useState(false);
            const [showDebug, setShowDebug] = useState(false);
            const [cpuParams, setCpuParams] = useState({ speed: 4500, accuracy: 0.15 });
            const [botOpponentEnabled, setBotOpponentEnabled] = useState(true);
            const [liveOverlaySettings, setLiveOverlaySettings] = useState(LIVE_OVERLAY_DEFAULTS);
            const [liveOverlayStatus, setLiveOverlayStatus] = useState({ isOpen: false, error: '' });

            const [gameState, setGameState] = useState('playing'); 
            const [playerHp, setPlayerHp] = useState(INITIAL_HP);
            const [enemyHp, setEnemyHp] = useState(INITIAL_HP);
            const [playerPoints, setPlayerPoints] = useState(0);
            const [enemyPoints, setEnemyPoints] = useState(0);
            const [playerSolvedCount, setPlayerSolvedCount] = useState(0); 
            const [enemySolvedCount, setEnemySolvedCount] = useState(0);
            const [solvedHistory, setSolvedHistory] = useState([]); 

            const [isOnline, setIsOnline] = useState(false);
            const [lobbyCode, setLobbyCode] = useState("");
            const [playerRole, setPlayerRole] = useState(null); 
            const [lobbyStatus, setLobbyStatus] = useState('idle');
            const [lobbyError, setLobbyError] = useState("");

            const [battleWords, setBattleWords] = useState([]);
            const [activeDebuffs, setActiveDebuffs] = useState({ glitch: 0, ink: 0, short: 0 });
            const [countdown, setCountdown] = useState(null); 
            const [celebration, setCelebration] = useState(null); 
            const [damageEffect, setDamageEffect] = useState(null);

            const [playerBoard, setPlayerBoard] = useState(Array(MAX_GUESSES).fill(""));
            const [playerResults, setPlayerResults] = useState([]); 
            const [playerCurrentGuess, setPlayerCurrentGuess] = useState("");
            const [playerTarget, setPlayerTarget] = useState("");

            const [enemyBoard, setEnemyBoard] = useState(Array(MAX_GUESSES).fill(""));
            const [enemyResults, setEnemyResults] = useState([]);
            const [enemyTarget, setEnemyTarget] = useState("");
            const [opponents, setOpponents] = useState([]);

            const [activeShield, setActiveShield] = useState(0);
            const [chatMessages, setChatMessages] = useState([]);
            const [keyStatuses, setKeyStatuses] = useState({});
            const [rematchState, setRematchState] = useState({ acceptedBy: {}, requestedBy: null });
            const [currentMatchNumber, setCurrentMatchNumber] = useState(1);
            const [endMatchStep, setEndMatchStep] = useState('reveal');
            const [revealedWords, setRevealedWords] = useState({});
            const [isRematchSubmitting, setIsRematchSubmitting] = useState(false);
            const [leaderboardRows, setLeaderboardRows] = useState([]);
            const [leaderboardLoading, setLeaderboardLoading] = useState(true);
            const [leaderboardError, setLeaderboardError] = useState('');
            const [pendingDebuffId, setPendingDebuffId] = useState(null);
            const [invalidGuessPulse, setInvalidGuessPulse] = useState(0);
            const rematchHostStartRef = useRef(null);
            const matchRecordRef = useRef(null);
            const profileFallbackRef = useRef({
                name: playerName,
                avatar: playerAvatar,
            });

            const db = useRef(null);
            const auth = useRef(null);
            const {
                user,
                authLoading,
                authError,
                otpPending,
                startPhoneSignIn,
                startPhoneUpgrade,
                verifyPhoneCode,
                signInAsGuest,
                signOutUser,
            } = useFirebaseAuth({ Firebase: window.Firebase, authRef: auth, dbRef: db });

            const getTileColor = (status) => {
                switch (status) {
                    case 'correct': return 'bg-green-500 border-green-400 shadow-[0_0_10px_rgba(34,197,94,0.4)]';
                    case 'present': return 'bg-yellow-500 border-yellow-400 shadow-[0_0_10px_rgba(234,179,8,0.4)]';
                    case 'absent': return 'bg-gray-700 border-gray-600';
                    default: return 'bg-transparent border-gray-600';
                }
            };

            const { startRoundCountdown, initLocalGame, exitToLobby } = useGameFlow({
                dictionary,
                fallbackWords: FALLBACK_WORDS,
                initialHp: INITIAL_HP,
                maxGuesses: MAX_GUESSES,
                setCountdown,
                setIsOnline,
                setPlayerHp,
                setEnemyHp,
                setPlayerPoints,
                setEnemyPoints,
                setPlayerSolvedCount,
                setEnemySolvedCount,
                setSolvedHistory,
                setPlayerBoard,
                setPlayerResults,
                setEnemyBoard,
                setEnemyResults,
                setIsPaused,
                setBattleWords,
                setPlayerTarget,
                setEnemyTarget,
                setGameState,
                setView,
                setLobbyStatus,
                setLobbyCode,
                setActiveDebuffs,
                setChatMessages,
            });

            const { syncAction, createLobby, joinLobby, reconnectToLobby, usePowerUp, acceptRematch, declineRematch, startRematchAsHost, sendChatMessage } = useLobbyActions({
                Firebase,
                FirebaseService,
                db,
                user,
                appId,
                playerRole,
                lobbyCode,
                dictionary,
                fallbackWords: FALLBACK_WORDS,
                gameMode,
                battleRounds,
                hardCapEnabled,
                lobbyMaxPlayers,
                playerName,
                playerAvatar,
                initialHp: INITIAL_HP,
                maxGuesses: MAX_GUESSES,
                powerUps: POWER_UPS,
                playerPoints,
                countdown,
                isPaused,
                isOnline,
                opponentRoles: opponents.map((opponent) => opponent.roleKey),
                playerHp,
                setPlayerHp,
                setPlayerPoints,
                setActiveShield,
                setLobbyError,
                setLobbyStatus,
                setLobbyCode,
                setPlayerRole,
                setIsOnline,
            });

            const reconnectTimerRef = useRef(null);
            const reconnectAttemptRef = useRef(0);
            const reconnectInFlightRef = useRef(false);
            const liveOverlayWindowRef = useRef(null);

            const clearReconnectTimer = useCallback(() => {
                if (reconnectTimerRef.current) {
                    clearTimeout(reconnectTimerRef.current);
                    reconnectTimerRef.current = null;
                }
            }, []);

            const resetReconnectState = useCallback(() => {
                clearReconnectTimer();
                reconnectAttemptRef.current = 0;
                reconnectInFlightRef.current = false;
            }, [clearReconnectTimer]);

            const scheduleReconnect = useCallback((reason = 'connection issue') => {
                if (!isOnline || !lobbyCode || !user || reconnectInFlightRef.current) return;

                clearReconnectTimer();

                const runAttempt = async () => {
                    if (!isOnline || !lobbyCode || !user) {
                        resetReconnectState();
                        return;
                    }

                    reconnectInFlightRef.current = true;
                    reconnectAttemptRef.current += 1;
                    const attempt = reconnectAttemptRef.current;
                    setLobbyError(`Connection interrupted (${reason}). Reconnecting... (${attempt}/5)`);

                    const recovered = await reconnectToLobby();
                    reconnectInFlightRef.current = false;

                    if (recovered) {
                        setLobbyError('');
                        resetReconnectState();
                        return;
                    }

                    if (attempt >= 5) {
                        setLobbyError('Connection lost. Could not reconnect automatically. Re-enter the lobby code to retry.');
                        resetReconnectState();
                        return;
                    }

                    const delayMs = Math.min(8000, 1000 * Math.pow(2, attempt - 1));
                    reconnectTimerRef.current = setTimeout(() => {
                        reconnectTimerRef.current = null;
                        runAttempt();
                    }, delayMs);
                };

                runAttempt();
            }, [isOnline, lobbyCode, user, clearReconnectTimer, reconnectToLobby, setLobbyError, resetReconnectState]);

            const triggerInvalidGuessFeedback = useCallback(() => {
                setInvalidGuessPulse((prev) => prev + 1);
            }, []);

            const normalizeProfileName = useCallback((value) => {
                const clean = String(value || '').trim().toUpperCase().slice(0, 24);
                return clean || `PILOT_${Math.floor(Math.random() * 9000 + 1000)}`;
            }, []);

            const normalizeAvatar = useCallback((value) => {
                const clean = String(value || '').replace(/[^A-Za-z0-9]/g, '').toUpperCase().slice(0, 2);
                return clean || 'AN';
            }, []);

            const updateLiveOverlaySettings = useCallback((patch) => {
                setLiveOverlaySettings((prev) => {
                    const next = { ...prev, ...(patch || {}) };
                    next.enabled = Boolean(next.enabled);
                    next.tickerText = String(next.tickerText || '').slice(0, 120);
                    next.tickerSpeedSec = clamp(parseInt(next.tickerSpeedSec, 10) || 18, 8, 40);
                    next.textScale = clamp(parseInt(next.textScale, 10) || 100, 80, 170);
                    next.tickerPosition = next.tickerPosition === 'top' ? 'top' : 'bottom';
                    return next;
                });
                setLiveOverlayStatus((prev) => ({ ...prev, error: '' }));
            }, []);

            const getLiveOverlayShellHtml = useCallback(() => `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Wordle Live Overlay</title>
<style>
:root {
    --live-scale: 1;
    --ticker-seconds: 18s;
}
* { box-sizing: border-box; }
html, body {
    margin: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    background: #00ff00;
    font-family: 'Inter', 'Segoe UI', Tahoma, sans-serif;
    color: #ffffff;
}
.stage {
    width: 100%;
    height: 100%;
    background: #00ff00;
    position: relative;
}
.hud {
    position: absolute;
    inset: 2vw;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    pointer-events: none;
}
.headline {
    align-self: flex-start;
    background: rgba(2, 6, 23, 0.85);
    border: 2px solid rgba(34, 211, 238, 0.8);
    border-radius: 14px;
    padding: calc(0.55rem * var(--live-scale)) calc(0.9rem * var(--live-scale));
    letter-spacing: 0.12em;
    font-weight: 900;
    font-size: calc(0.68rem * var(--live-scale));
    text-transform: uppercase;
    box-shadow: 0 0 16px rgba(34, 211, 238, 0.4);
    max-width: 88%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.scoregrid {
    align-self: flex-end;
    width: min(34vw, 470px);
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: calc(0.55rem * var(--live-scale));
}
.stat {
    background: rgba(15, 23, 42, 0.88);
    border: 2px solid rgba(244, 114, 182, 0.65);
    border-radius: 12px;
    padding: calc(0.6rem * var(--live-scale));
}
.stat-label {
    font-size: calc(0.5rem * var(--live-scale));
    letter-spacing: 0.13em;
    text-transform: uppercase;
    color: #cbd5e1;
    font-weight: 700;
}
.stat-value {
    margin-top: calc(0.25rem * var(--live-scale));
    font-size: calc(1rem * var(--live-scale));
    line-height: 1;
    font-weight: 900;
    color: #f8fafc;
}
.ticker {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: calc(2.5rem * var(--live-scale));
    background: rgba(15, 23, 42, 0.95);
    border-top: 2px solid rgba(251, 191, 36, 0.8);
    display: flex;
    align-items: center;
    overflow: hidden;
    box-shadow: 0 0 18px rgba(15, 23, 42, 0.6);
}
.ticker.top {
    top: 0;
    bottom: auto;
    border-top: none;
    border-bottom: 2px solid rgba(251, 191, 36, 0.8);
}
.ticker-track {
    white-space: nowrap;
    font-weight: 900;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    font-size: calc(0.72rem * var(--live-scale));
    color: #fde68a;
    padding-left: 100%;
    animation: marquee var(--ticker-seconds) linear infinite;
}
@keyframes marquee {
    0% { transform: translateX(0); }
    100% { transform: translateX(-100%); }
}
.board-widget {
    position: absolute;
    left: 2.2vw;
    top: 23vh;
    width: 320px;
    min-width: 180px;
    min-height: 160px;
    background: rgba(2, 6, 23, 0.9);
    border: 2px solid rgba(251, 146, 60, 0.85);
    border-radius: 14px;
    box-shadow: 0 0 20px rgba(2, 6, 23, 0.65);
    padding: 8px;
    resize: both;
    overflow: auto;
    pointer-events: auto;
}
.name-widget {
    position: absolute;
    right: 3vw;
    top: 10vh;
    width: min(34vw, 520px);
    min-width: 220px;
    min-height: 110px;
    background: rgba(2, 6, 23, 0.9);
    border: 2px solid rgba(34, 211, 238, 0.9);
    border-radius: 18px;
    box-shadow: 0 0 28px rgba(34, 211, 238, 0.3);
    padding: 10px 12px 14px;
    resize: both;
    overflow: auto;
    pointer-events: auto;
}
.board-drag-handle {
    cursor: move;
    user-select: none;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    font-weight: 900;
    color: #fdba74;
    font-size: calc(0.5rem * var(--live-scale));
    margin-bottom: 8px;
}
.name-drag-handle {
    cursor: move;
    user-select: none;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-weight: 900;
    color: #67e8f9;
    font-size: calc(0.48rem * var(--live-scale));
    margin-bottom: 10px;
}
.player-name-display {
    font-size: clamp(calc(1.8rem * var(--live-scale)), 4vw, calc(4rem * var(--live-scale)));
    line-height: 0.95;
    font-weight: 900;
    text-transform: uppercase;
    color: #f8fafc;
    letter-spacing: 0.08em;
    text-shadow: 0 0 22px rgba(34, 211, 238, 0.35);
    word-break: break-word;
}
.player-board {
    display: grid;
    grid-template-rows: repeat(6, 1fr);
    gap: 4px;
}
.board-row {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 4px;
}
.cell {
    aspect-ratio: 1 / 1;
    border: 1px solid #64748b;
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: calc(0.78rem * var(--live-scale));
    font-weight: 900;
    text-transform: uppercase;
    color: #e2e8f0;
    background: rgba(15, 23, 42, 0.72);
}
.cell.correct { background: rgba(34, 197, 94, 0.95); border-color: rgba(134, 239, 172, 1); color: #062010; }
.cell.present { background: rgba(234, 179, 8, 0.95); border-color: rgba(254, 240, 138, 1); color: #3b2f00; }
.cell.absent { background: rgba(51, 65, 85, 0.95); border-color: rgba(100, 116, 139, 1); color: #e2e8f0; }
</style>
</head>
<body>
<div class="stage">
    <div class="hud">
        <div id="headline" class="headline">LIVE WORDLE BATTLE</div>
        <div class="scoregrid">
            <div class="stat"><div class="stat-label">Pilot</div><div id="stat-pilot" class="stat-value">-</div></div>
            <div class="stat"><div class="stat-label">Points</div><div id="stat-points" class="stat-value">0</div></div>
            <div class="stat"><div class="stat-label">Hull</div><div id="stat-hull" class="stat-value">100%</div></div>
            <div class="stat"><div class="stat-label">Solved</div><div id="stat-solved" class="stat-value">0/3</div></div>
            <div class="stat"><div class="stat-label">Ranked W/L</div><div id="stat-ranked" class="stat-value">0 / 0</div></div>
            <div class="stat"><div class="stat-label">Solo W/L</div><div id="stat-solo" class="stat-value">0 / 0</div></div>
        </div>
    </div>

    <div id="board-widget" class="board-widget">
        <div id="board-drag-handle" class="board-drag-handle">Player Grid</div>
        <div id="player-board" class="player-board"></div>
    </div>

    <div id="name-widget" class="name-widget">
        <div id="name-drag-handle" class="name-drag-handle">Player Name</div>
        <div id="player-name-display" class="player-name-display">PILOT</div>
    </div>

    <div id="ticker" class="ticker">
        <div id="ticker-track" class="ticker-track">LIVE WORDLE BATTLE</div>
    </div>
</div>

<script>
(function () {
    const esc = (v) => String(v == null ? '' : v);
    const boardWidget = document.getElementById('board-widget');
    const boardHandle = document.getElementById('board-drag-handle');
    const nameWidget = document.getElementById('name-widget');
    const nameHandle = document.getElementById('name-drag-handle');
    const boardRoot = document.getElementById('player-board');
    const makeDraggable = (widget, handle) => {
        let dragState = null;

        handle.addEventListener('pointerdown', (event) => {
            event.preventDefault();
            const rect = widget.getBoundingClientRect();
            dragState = {
                dx: event.clientX - rect.left,
                dy: event.clientY - rect.top,
            };
            handle.setPointerCapture(event.pointerId);
        });

        handle.addEventListener('pointermove', (event) => {
            if (!dragState) return;
            const left = Math.max(0, Math.min(window.innerWidth - widget.offsetWidth, event.clientX - dragState.dx));
            const top = Math.max(0, Math.min(window.innerHeight - widget.offsetHeight, event.clientY - dragState.dy));
            widget.style.left = left + 'px';
            widget.style.top = top + 'px';
            widget.style.right = 'auto';
        });

        const endDrag = (event) => {
            if (!dragState) return;
            dragState = null;
            try {
                handle.releasePointerCapture(event.pointerId);
            } catch (_err) {}
        };

        handle.addEventListener('pointerup', endDrag);
        handle.addEventListener('pointercancel', endDrag);
    };

    makeDraggable(boardWidget, boardHandle);
    makeDraggable(nameWidget, nameHandle);

    const ensureBoard = (rows, cols) => {
        boardRoot.innerHTML = '';
        for (let r = 0; r < rows; r += 1) {
            const row = document.createElement('div');
            row.className = 'board-row';
            for (let c = 0; c < cols; c += 1) {
                const cell = document.createElement('div');
                cell.className = 'cell';
                row.appendChild(cell);
            }
            boardRoot.appendChild(row);
        }
    };

    const getCell = (rowIdx, colIdx) => {
        const row = boardRoot.children[rowIdx];
        if (!row) return null;
        return row.children[colIdx] || null;
    };

    const apply = (payload) => {
        const data = payload || {};
        document.documentElement.style.setProperty('--live-scale', String(data.textScale || 1));
        document.documentElement.style.setProperty('--ticker-seconds', String(data.tickerSeconds || 18) + 's');

        document.getElementById('headline').textContent = esc(data.headline);
        document.getElementById('stat-pilot').textContent = esc(data.playerName);
        document.getElementById('stat-points').textContent = esc(data.playerPoints);
        document.getElementById('stat-hull').textContent = esc(data.playerHp) + '%';
        document.getElementById('stat-solved').textContent = esc(data.playerSolvedCount) + '/' + esc(data.battleRounds);
        document.getElementById('stat-ranked').textContent = esc(data.rankedWins) + ' / ' + esc(data.rankedLosses);
        document.getElementById('stat-solo').textContent = esc(data.localWins) + ' / ' + esc(data.localLosses);
        document.getElementById('player-name-display').textContent = esc(data.playerName);

        const ticker = document.getElementById('ticker');
        ticker.classList.toggle('top', data.tickerPosition === 'top');
        document.getElementById('ticker-track').textContent = esc(data.tickerLine);

        const rows = Math.max(1, parseInt(data.rows, 10) || 6);
        const cols = Math.max(1, parseInt(data.cols, 10) || 5);
        ensureBoard(rows, cols);

        const board = Array.isArray(data.board) ? data.board : [];
        const statuses = Array.isArray(data.statuses) ? data.statuses : [];
        for (let r = 0; r < rows; r += 1) {
            for (let c = 0; c < cols; c += 1) {
                const cell = getCell(r, c);
                if (!cell) continue;
                const letter = (board[r] && board[r][c]) ? board[r][c] : '';
                const status = (statuses[r] && statuses[r][c]) ? statuses[r][c] : '';
                cell.textContent = letter;
                cell.className = 'cell';
                if (status === 'correct' || status === 'present' || status === 'absent') {
                    cell.classList.add(status);
                }
            }
        }
    };

    window.addEventListener('message', (event) => {
        const msg = event.data || {};
        if (msg.type !== 'WORDLE_OVERLAY_UPDATE') return;
        apply(msg.payload || {});
    });
})();
</script>
</body>
</html>`, []);

            const renderLiveOverlayWindow = useCallback(() => {
                const overlayWindow = liveOverlayWindowRef.current;
                if (!overlayWindow || overlayWindow.closed) {
                    liveOverlayWindowRef.current = null;
                    setLiveOverlayStatus((prev) => ({ ...prev, isOpen: false }));
                    return;
                }

                const stats = profileStats || {};
                const textScale = (liveOverlaySettings.textScale || 100) / 100;
                const tickerText = (liveOverlaySettings.tickerText || '').trim() || 'LIVE WORDLE BATTLE';
                const tickerDuration = liveOverlaySettings.tickerSpeedSec || 18;
                const modeLabel = String(gameMode || 'battle').replace('-', ' ').toUpperCase();
                const liveStatus = view === 'playing' ? 'IN MATCH' : 'IN LOBBY';
                const playerBoardRows = Array.isArray(playerBoard)
                    ? playerBoard.map((row) => String(row || ''))
                    : Array(MAX_GUESSES).fill('');
                const playerStatuses = [];

                for (let rowIdx = 0; rowIdx < playerBoardRows.length; rowIdx += 1) {
                    const boardRow = String(playerBoardRows[rowIdx] || '').padEnd(WORD_LENGTH, ' ');
                    const isCurrentRow = rowIdx === playerResults.length;
                    const displayRow = isCurrentRow
                        ? String(playerCurrentGuess || '').padEnd(WORD_LENGTH, ' ')
                        : boardRow;
                    playerBoardRows[rowIdx] = displayRow.toUpperCase().slice(0, WORD_LENGTH).split('');

                    const rowStatuses = Array.isArray(playerResults[rowIdx])
                        ? playerResults[rowIdx].slice(0, WORD_LENGTH)
                        : Array(WORD_LENGTH).fill('');
                    playerStatuses.push(rowStatuses);
                }

                const headlineParts = [
                    `${liveStatus}`,
                    `MODE ${modeLabel}`,
                    isOnline && lobbyCode ? `LOBBY ${lobbyCode}` : 'SOLO STREAM',
                    `STATE ${String(gameState || 'playing').toUpperCase()}`,
                ];
                const headline = headlineParts.join(' | ');

                if (!overlayWindow.__wordleOverlayBootstrapped) {
                    overlayWindow.document.open();
                    overlayWindow.document.write(getLiveOverlayShellHtml());
                    overlayWindow.document.close();
                    overlayWindow.__wordleOverlayBootstrapped = true;
                }

                overlayWindow.postMessage({
                    type: 'WORDLE_OVERLAY_UPDATE',
                    payload: {
                        textScale,
                        tickerSeconds: tickerDuration,
                        tickerPosition: liveOverlaySettings.tickerPosition,
                        headline,
                        playerName,
                        playerPoints,
                        playerHp,
                        playerSolvedCount,
                        battleRounds,
                        rankedWins: stats.wins || 0,
                        rankedLosses: stats.losses || 0,
                        localWins: stats.localWins || 0,
                        localLosses: stats.localLosses || 0,
                        rows: playerBoardRows.length || MAX_GUESSES,
                        cols: WORD_LENGTH,
                        board: playerBoardRows,
                        statuses: playerStatuses,
                        tickerLine: `${tickerText}   |   OPPONENT ${opponentName} HP ${enemyHp}%   |   ENEMY SOLVED ${enemySolvedCount}   |   ${headline}   |   `,
                    },
                }, '*');
            }, [
                profileStats,
                liveOverlaySettings,
                gameMode,
                view,
                isOnline,
                lobbyCode,
                gameState,
                playerName,
                playerPoints,
                playerHp,
                playerSolvedCount,
                battleRounds,
                opponentName,
                enemyHp,
                enemySolvedCount,
                playerBoard,
                playerResults,
                playerCurrentGuess,
                getLiveOverlayShellHtml,
            ]);

            const openLiveOverlayWindow = useCallback(() => {
                if (!liveOverlaySettings.enabled) {
                    setLiveOverlayStatus({ isOpen: false, error: 'Enable Live Overlay first.' });
                    return;
                }

                const overlayWindow = window.open('', 'wordle-live-overlay', 'popup=yes,width=1280,height=720');
                if (!overlayWindow) {
                    setLiveOverlayStatus({ isOpen: false, error: 'Popup blocked. Allow popups for this site.' });
                    return;
                }

                liveOverlayWindowRef.current = overlayWindow;
                overlayWindow.__wordleOverlayBootstrapped = false;
                setLiveOverlayStatus({ isOpen: true, error: '' });
            }, [liveOverlaySettings.enabled]);

            const closeLiveOverlayWindow = useCallback(() => {
                if (liveOverlayWindowRef.current && !liveOverlayWindowRef.current.closed) {
                    liveOverlayWindowRef.current.close();
                }
                liveOverlayWindowRef.current = null;
                setLiveOverlayStatus((prev) => ({ ...prev, isOpen: false }));
            }, []);

            useEffect(() => {
                if (!liveOverlaySettings.enabled) {
                    closeLiveOverlayWindow();
                }
            }, [liveOverlaySettings.enabled, closeLiveOverlayWindow]);

            useEffect(() => {
                if (!liveOverlaySettings.enabled || !liveOverlayStatus.isOpen) return;
                renderLiveOverlayWindow();
            }, [liveOverlaySettings.enabled, liveOverlayStatus.isOpen, renderLiveOverlayWindow]);

            useEffect(() => {
                if (!liveOverlayStatus.isOpen) return undefined;

                const intervalId = setInterval(() => {
                    const overlayWindow = liveOverlayWindowRef.current;
                    if (overlayWindow && !overlayWindow.closed) return;
                    clearInterval(intervalId);
                    liveOverlayWindowRef.current = null;
                    setLiveOverlayStatus((prev) => ({ ...prev, isOpen: false }));
                }, 600);

                return () => clearInterval(intervalId);
            }, [liveOverlayStatus.isOpen]);

            useEffect(() => {
                return () => {
                    if (liveOverlayWindowRef.current && !liveOverlayWindowRef.current.closed) {
                        liveOverlayWindowRef.current.close();
                    }
                };
            }, []);

            useEffect(() => {
                if (!avatarUploadFile) {
                    setPendingAvatarPreviewUrl('');
                    return;
                }

                const next = URL.createObjectURL(avatarUploadFile);
                setPendingAvatarPreviewUrl(next);

                return () => URL.revokeObjectURL(next);
            }, [avatarUploadFile]);

            const handleAvatarImageFileSelect = useCallback((file) => {
                setAvatarUploadFile(file || null);
            }, []);

            const runAuthAction = useCallback(async (action) => {
                setAuthActionLoading(true);
                setProfileSaveState({ saving: false, message: '' });
                try {
                    await action();
                } catch (err) {
                    console.error('[Auth] Action failed', err);
                } finally {
                    setAuthActionLoading(false);
                }
            }, []);

            const handleAuthSignIn = useCallback(() => runAuthAction(() => startPhoneSignIn(authForm)), [runAuthAction, startPhoneSignIn, authForm]);
            const handleAuthRegister = useCallback(() => runAuthAction(() => startPhoneUpgrade(authForm)), [runAuthAction, startPhoneUpgrade, authForm]);
            const handleAuthVerify = useCallback(() => runAuthAction(() => verifyPhoneCode({ code: authForm.otpCode })), [runAuthAction, verifyPhoneCode, authForm]);
            const handleAuthGuest = useCallback(() => runAuthAction(() => signInAsGuest()), [runAuthAction, signInAsGuest]);
            const handleAuthSignOut = useCallback(() => runAuthAction(() => signOutUser()), [runAuthAction, signOutUser]);

            const saveProfile = useCallback(async () => {
                if (!user || !db.current) return;
                const name = normalizeProfileName(playerName);
                const avatar = normalizeAvatar(playerAvatar);
                let avatarImageUrl = playerAvatarImageUrl;
                let avatarStoragePath = playerAvatarStoragePath;

                setProfileSaveState({ saving: true, message: '' });
                try {
                    if (avatarUploadFile) {
                        const upload = await FirebaseService.uploadUserAvatarImage({
                            Firebase,
                            storage: Firebase.storage,
                            appId,
                            uid: user.uid,
                            file: avatarUploadFile,
                        });
                        avatarStoragePath = upload.storagePath;
                        avatarImageUrl = await FirebaseService.resolveAvatarImageUrl({
                            Firebase,
                            storage: Firebase.storage,
                            storagePath: avatarStoragePath,
                        });
                    }

                    await FirebaseService.saveUserProfile({
                        Firebase,
                        db,
                        appId,
                        uid: user.uid,
                        name,
                        avatar,
                        avatarImageUrl,
                        avatarStoragePath,
                    });
                    setPlayerName(name);
                    setPlayerAvatar(avatar);
                    profileFallbackRef.current = { name, avatar };
                    setPlayerAvatarImageUrl(avatarImageUrl || '');
                    setPlayerAvatarStoragePath(avatarStoragePath || '');
                    setAvatarUploadFile(null);
                    setProfileSaveState({ saving: false, message: 'Profile saved.' });
                } catch (err) {
                    console.error('[Profile] Save failed', err);
                    setProfileSaveState({ saving: false, message: 'Failed to save profile.' });
                }
            }, [user, db, normalizeProfileName, playerName, normalizeAvatar, playerAvatar, playerAvatarImageUrl, playerAvatarStoragePath, avatarUploadFile, FirebaseService, Firebase, appId]);

            const { handleKeyInput } = useInputAndGuessFlow({
                wordLength: WORD_LENGTH,
                maxGuesses: MAX_GUESSES,
                gameState,
                countdown,
                isPaused,
                showDebug,
                gameMode,
                isOnline,
                playerTarget,
                playerResults,
                playerBoard,
                playerSolvedCount,
                battleRounds,
                battleWords,
                dictionary,
                validGuessSet,
                checkWordEnabled,
                hardCapEnabled,
                enemyHp,
                opponents,
                opponentRoles: opponents.map((opponent) => opponent.roleKey),
                playerPoints,
                activeDebuffs,
                playerRole,
                Firebase,
                FirebaseService,
                db,
                appId,
                lobbyCode,
                user,
                checkWord,
                summarizeResult,
                scorePlayerGuess,
                syncAction,
                setPlayerResults,
                setPlayerBoard,
                setPlayerCurrentGuess,
                setKeyStatuses,
                setCelebration,
                setPlayerSolvedCount,
                setGameState,
                setPlayerTarget,
                setEnemyTarget,
                setPlayerPoints,
                setEnemyHp,
                setOpponents,
                setDamageEffect,
                setSolvedHistory,
                onInvalidGuess: triggerInvalidGuessFeedback,
                playerCurrentGuess,
            });

            const handleMultiplayerStart = useCallback((data) => {
                const myData = FirebaseService.buildPlayerSnapshot({ lobbyData: data, roleKey: playerRole, initialHp: INITIAL_HP, maxGuesses: MAX_GUESSES });
                const opponentSnapshots = FirebaseService
                    .getOpponentRoles({ lobbyData: data, playerRole })
                    .map((roleKey) => FirebaseService.buildPlayerSnapshot({ lobbyData: data, roleKey, initialHp: INITIAL_HP, maxGuesses: MAX_GUESSES }));
                const primaryOpponent = opponentSnapshots[0];

                setView('playing');
                setGameState('playing');
                setGameMode(data.gameMode);
                setBattleRounds(data.battleRounds || 3);
                setHardCapEnabled(Boolean(data.hardCapEnabled));
                setCurrentMatchNumber(data.matchNumber || 1);
                setEndMatchStep('reveal');
                setRevealedWords({});
                setRematchState(data.rematchState || { acceptedBy: {}, requestedBy: null });
                setPlayerCurrentGuess('');
                setCelebration(null);
                setDamageEffect(null);
                setPlayerHp(myData.hp ?? INITIAL_HP);
                setEnemyHp(primaryOpponent?.hp ?? INITIAL_HP);
                setPlayerPoints(myData.points ?? 0);
                setEnemyPoints(primaryOpponent?.points ?? 0);
                setPlayerSolvedCount(myData.solved ?? 0);
                setEnemySolvedCount(primaryOpponent?.solved ?? 0);
                setPlayerBoard(myData.board ?? Array(MAX_GUESSES).fill(''));
                setEnemyBoard(primaryOpponent?.board ?? Array(MAX_GUESSES).fill(''));
                setPlayerResults(myData.results ?? []);
                setEnemyResults(primaryOpponent?.results ?? []);
                setSolvedHistory(Array.isArray(data.solvedHistory) ? data.solvedHistory : []);
                setActiveDebuffs(myData.debuffs || { glitch: 0, ink: 0, short: 0 });
                setOpponents(opponentSnapshots);
                setOpponentName(primaryOpponent?.name || 'OPPONENT');
                setChatMessages(Array.isArray(data.chatMessages) ? data.chatMessages : []);
                if (data.battleWords) {
                    setBattleWords(data.battleWords);
                    const currentRoundIdx = Math.max(0, myData.solved || 0);
                    const fallbackTarget = data.battleWords[currentRoundIdx] || data.battleWords[0] || '';
                    setPlayerTarget(myData.target || fallbackTarget);
                    setEnemyTarget(primaryOpponent?.target || fallbackTarget);
                }
                startRoundCountdown();

                // Fetch opponent profile images (fire-and-forget; non-critical).
                opponentSnapshots.forEach((opp) => {
                    if (!opp?.uid) return;
                    FirebaseService.getOrCreateUserProfile({ Firebase, db, appId, user: { uid: opp.uid }, fallbackName: opp.name, fallbackAvatar: opp.avatar })
                        .then(async (profile) => {
                            let url = profile?.avatarImageUrl || '';
                            if (!url && profile?.avatarStoragePath) {
                                url = await FirebaseService.resolveAvatarImageUrl({ Firebase, storage: Firebase.storage, storagePath: profile.avatarStoragePath });
                            }
                            if (url) setOpponentAvatarImageUrls((prev) => ({ ...prev, [opp.uid]: url }));
                        })
                        .catch(() => {});
                });
            }, [FirebaseService, playerRole, startRoundCountdown, db, appId, Firebase]);

            const handleFinished = useCallback(async (outcome, data) => {
                setGameState(outcome);
                setEndMatchStep('reveal');
                setRevealedWords({});
                if (!user || !db.current) return;

                if (!isOnline) {
                    try {
                        await FirebaseService.incrementLocalProfileResult({ Firebase, db, appId, uid: user.uid, outcome });
                        setProfileStats((prev) => ({
                            ...prev,
                            localWins: (prev.localWins || 0) + (outcome === 'won' ? 1 : 0),
                            localLosses: (prev.localLosses || 0) + (outcome === 'lost' ? 1 : 0),
                        }));
                    } catch (err) {
                        console.error('[Profile] Failed to record local result', err);
                    }
                    return;
                }

                if (!data || !lobbyCode) return;
                const matchNumber = data.matchNumber || 1;
                if (matchRecordRef.current === matchNumber) return;
                matchRecordRef.current = matchNumber;
                try {
                    await FirebaseService.recordMatchResultIfNeeded({ Firebase, db, appId, lobbyCode });
                    const profile = await FirebaseService.getOrCreateUserProfile({
                        Firebase,
                        db,
                        appId,
                        user,
                        fallbackName: playerName,
                        fallbackAvatar: playerAvatar,
                    });
                    setProfileStats(profile?.stats || { wins: 0, losses: 0, matches: 0, localWins: 0, localLosses: 0 });
                } catch (err) {
                    console.error('[Leaderboard] Failed to record match result', err);
                }
            }, [isOnline, db, lobbyCode, FirebaseService, Firebase, appId, user, playerName, playerAvatar]);

            const handleAcceptRematch = useCallback(async () => {
                setIsRematchSubmitting(true);
                try {
                    await acceptRematch();
                } finally {
                    setIsRematchSubmitting(false);
                }
            }, [acceptRematch]);

            const handleDeclineRematch = useCallback(async () => {
                await declineRematch();
                exitToLobby();
            }, [declineRematch, exitToLobby]);

            const debuffIds = ['glitch', 'ink', 'short'];
            const handleUsePowerUp = useCallback(async (id) => {
                const multiTargetSelectionNeeded = isOnline && debuffIds.includes(id) && opponents.length > 1;
                if (multiTargetSelectionNeeded) {
                    setPendingDebuffId(id);
                    return;
                }
                await usePowerUp(id);
                setPendingDebuffId(null);
            }, [isOnline, opponents, usePowerUp]);

            const handleDebuffTargetSelect = useCallback(async (roleKey) => {
                if (!pendingDebuffId) return;
                await usePowerUp(pendingDebuffId, roleKey);
                setPendingDebuffId(null);
            }, [pendingDebuffId, usePowerUp]);

            const cancelDebuffTargeting = useCallback(() => {
                setPendingDebuffId(null);
            }, []);

            const toggleWordReveal = useCallback((idx) => {
                setRevealedWords((prev) => ({ ...prev, [idx]: !prev[idx] }));
            }, []);

            const handleOpponentUpdate = useCallback((opponentData) => {
                setOpponentName(opponentData.name);
                setEnemyHp(opponentData.hp);
                setEnemyPoints(opponentData.points);
                setEnemySolvedCount(opponentData.solved);
                setEnemyBoard(opponentData.board);
                setEnemyResults(opponentData.results);
                setEnemyTarget(opponentData.target);
            }, []);

            const handleOpponentsUpdate = useCallback((opponentList) => {
                setOpponents(opponentList);
                if (!opponentList?.length) {
                    setOpponentName('OPPONENT');
                    setEnemyHp(INITIAL_HP);
                    setEnemyPoints(0);
                    setEnemySolvedCount(0);
                    setEnemyBoard(Array(MAX_GUESSES).fill(''));
                    setEnemyResults([]);
                    setEnemyTarget('');
                    return;
                }

                const primaryOpponent = opponentList[0];
                setOpponentName(primaryOpponent.name || 'OPPONENT');
                setEnemyHp(primaryOpponent.hp ?? INITIAL_HP);
                setEnemyPoints(primaryOpponent.points ?? 0);
                setEnemySolvedCount(primaryOpponent.solved ?? 0);
                setEnemyBoard(primaryOpponent.board ?? Array(MAX_GUESSES).fill(''));
                setEnemyResults(primaryOpponent.results ?? []);
                setEnemyTarget(primaryOpponent.target || '');

                // Refresh opponent profile images whenever the opponent list changes.
                opponentList.forEach((opp) => {
                    if (!opp?.uid || opponentAvatarImageUrls[opp.uid]) return;
                    FirebaseService.getOrCreateUserProfile({ Firebase, db, appId, user: { uid: opp.uid }, fallbackName: opp.name, fallbackAvatar: opp.avatar })
                        .then(async (profile) => {
                            let url = profile?.avatarImageUrl || '';
                            if (!url && profile?.avatarStoragePath) {
                                url = await FirebaseService.resolveAvatarImageUrl({ Firebase, storage: Firebase.storage, storagePath: profile.avatarStoragePath });
                            }
                            if (url) setOpponentAvatarImageUrls((prev) => ({ ...prev, [opp.uid]: url }));
                        })
                        .catch(() => {});
                });
            }, [Firebase, FirebaseService, db, appId, opponentAvatarImageUrls]);

            useMultiplayer({
                enabled: isOnline,
                lobbyCode,
                user,
                db,
                Firebase,
                appId,
                playerRole,
                view,
                initialHp: INITIAL_HP,
                maxGuesses: MAX_GUESSES,
                onStartPlaying: handleMultiplayerStart,
                onSolvedHistory: setSolvedHistory,
                onDebuffs: setActiveDebuffs,
                onOpponentUpdate: handleOpponentUpdate,
                onOpponentsUpdate: handleOpponentsUpdate,
                onFinished: handleFinished,
                onRematchState: setRematchState,
                onChatMessages: setChatMessages,
                onConnectionError: () => scheduleReconnect('lobby sync error'),
                onConnectionRestored: () => {
                    if (reconnectAttemptRef.current > 0) {
                        setLobbyError('');
                        resetReconnectState();
                    }
                },
            });

            useEffect(() => {
                if (!isOnline || !lobbyCode) return undefined;

                const onOnline = () => scheduleReconnect('network restored');
                const onOffline = () => {
                    setLobbyError('Network offline. Waiting to reconnect...');
                };

                window.addEventListener('online', onOnline);
                window.addEventListener('offline', onOffline);

                return () => {
                    window.removeEventListener('online', onOnline);
                    window.removeEventListener('offline', onOffline);
                };
            }, [isOnline, lobbyCode, scheduleReconnect]);

            useEffect(() => {
                if (!isOnline) {
                    setOpponents([]);
                    setRematchState({ acceptedBy: {}, requestedBy: null });
                    setPendingDebuffId(null);
                    setChatMessages([]);
                    resetReconnectState();
                }
            }, [isOnline, resetReconnectState]);

            useEffect(() => {
                return () => {
                    clearReconnectTimer();
                };
            }, [clearReconnectTimer]);

            const {
                onUpdateEnemyResult,
                onEnemySolved,
                onSetPlayerHp,
                onDamageEffect,
            } = useAISoloFlow({
                maxGuesses: MAX_GUESSES,
                battleRounds,
                battleWords,
                dictionary,
                showDebug,
                setEnemyBoard,
                setEnemyResults,
                setCelebration,
                setEnemySolvedCount,
                setGameState,
                setEnemyTarget,
                setPlayerHp,
                setDamageEffect,
                setSolvedHistory,
            });

            useEffect(() => {
                if (authLoading) return;
                if (!user || !db.current) {
                    setProfileLoading(false);
                    return;
                }

                let cancelled = false;
                setProfileLoading(true);

                (async () => {
                    try {
                        const profile = await FirebaseService.getOrCreateUserProfile({
                            Firebase,
                            db,
                            appId,
                            user,
                            fallbackName: profileFallbackRef.current.name,
                            fallbackAvatar: profileFallbackRef.current.avatar,
                        });
                        if (cancelled) return;

                        let avatarImageUrl = profile?.avatarImageUrl || '';
                        if (!avatarImageUrl && profile?.avatarStoragePath) {
                            avatarImageUrl = await FirebaseService.resolveAvatarImageUrl({
                                Firebase,
                                storage: Firebase.storage,
                                storagePath: profile.avatarStoragePath,
                            });
                        }

                        if (cancelled) return;
                        setPlayerName(normalizeProfileName(profile?.name));
                        setPlayerAvatar(normalizeAvatar(profile?.avatar));
                        setPlayerAvatarImageUrl(avatarImageUrl || '');
                        setPlayerAvatarStoragePath(profile?.avatarStoragePath || '');
                        setProfileStats(profile?.stats || { wins: 0, losses: 0, matches: 0, localWins: 0, localLosses: 0 });
                        setProfileLoading(false);
                    } catch (err) {
                        if (cancelled) return;
                        console.error('[Profile] Load failed', err);
                        setProfileLoading(false);
                    }
                })();

                return () => {
                    cancelled = true;
                };
            }, [authLoading, user, db, FirebaseService, Firebase, appId, normalizeProfileName, normalizeAvatar]);

            useEffect(() => {
                if (!user || !db.current) return;
                setLeaderboardLoading(true);
                setLeaderboardError('');

                const unsubscribe = FirebaseService.subscribeTopLeaderboard({
                    Firebase,
                    db,
                    appId,
                    limitCount: 10,
                    onData: (rows) => {
                        setLeaderboardRows(rows);
                        setLeaderboardLoading(false);
                    },
                    onError: (err) => {
                        console.error('[Leaderboard] Subscription failed', err);
                        setLeaderboardError('Unable to load leaderboard right now.');
                        setLeaderboardLoading(false);
                    },
                });

                return () => unsubscribe();
            }, [user, db, FirebaseService, Firebase, appId]);

            useEffect(() => {
                const readyCount = Object.keys(rematchState?.acceptedBy || {}).length;
                const bothAccepted = readyCount >= Math.max(2, opponents.length + 1);
                const matchFinished = gameState === 'won' || gameState === 'lost';
                if (!isOnline || !matchFinished || !bothAccepted || playerRole !== 'p1' || !lobbyCode) return;
                if (rematchHostStartRef.current === currentMatchNumber) return;
                rematchHostStartRef.current = currentMatchNumber;
                startRematchAsHost().catch((err) => {
                    console.error('[Rematch] Host reset failed', err);
                    rematchHostStartRef.current = null;
                });
            }, [isOnline, gameState, rematchState, playerRole, lobbyCode, currentMatchNumber, startRematchAsHost]);

            useAIEngine({
                enabled: view === 'playing' && !isOnline && botOpponentEnabled && gameState === 'playing' && !isPaused && !countdown && !celebration,
                cpuParams,
                enemyResults,
                enemyTarget,
                activeShield,
                dictionary,
                fallbackWords: FALLBACK_WORDS,
                enemySolvedCount,
                battleRounds,
                battleWords,
                showDebug,
                onSetOpponentName: setOpponentName,
                onUpdateEnemyResult,
                onEnemySolved,
                onSetPlayerHp,
                onDamageEffect,
            });

            useEffect(() => {
                if (view !== 'playing' || isOnline || botOpponentEnabled) return;
                setOpponentName('NO BOT');
            }, [view, isOnline, botOpponentEnabled]);

            useEffect(() => {
                if (isDictLoading || hasShownDictionaryNoticeRef.current) return;

                const didLoad = dictLoadStatus === 'loaded';
                setDictionaryNotice({
                    text: didLoad ? 'dictionary loaded!' : 'dictionary failed',
                    tone: didLoad ? 'success' : 'error',
                });
                hasShownDictionaryNoticeRef.current = true;

                const timeoutId = setTimeout(() => {
                    setDictionaryNotice(null);
                }, 3000);

                return () => clearTimeout(timeoutId);
            }, [isDictLoading, dictLoadStatus]);

            // Keyboard Hook
            useEffect(() => {
                const physicalKey = (e) => {
                    if (view !== 'playing') return;

                    const target = e.target;
                    const tag = (target?.tagName || '').toLowerCase();
                    const isEditable = Boolean(
                        target?.isContentEditable
                        || tag === 'input'
                        || tag === 'textarea'
                        || tag === 'select'
                        || target?.closest?.('[contenteditable="true"]')
                    );
                    if (isEditable) return;

                    if (e.key === 'Escape' && view === 'playing') setIsPaused(p => !p);
                    if (e.key === 'Enter') handleKeyInput('ENTER');
                    else if (e.key === 'Backspace') handleKeyInput('BACKSPACE');
                    else if (/^[a-zA-Z]$/.test(e.key)) handleKeyInput(e.key.toUpperCase());
                };
                window.addEventListener('keydown', physicalKey);
                return () => window.removeEventListener('keydown', physicalKey);
            }, [view, handleKeyInput]);

            const dictionaryNoticeBanner = dictionaryNotice ? (
                <div className="fixed top-4 left-1/2 -translate-x-1/2 z-[220] pointer-events-none">
                    <div className={`px-4 py-2 rounded-xl border text-xs md:text-sm font-black uppercase tracking-widest shadow-xl ${dictionaryNotice.tone === 'success' ? 'bg-emerald-950/90 border-emerald-400/80 text-emerald-200' : 'bg-rose-950/90 border-rose-400/80 text-rose-200'}`}>
                        {dictionaryNotice.text}
                    </div>
                </div>
            ) : null;

            // Keyboard Component
            if (isDictLoading) return <div className="min-h-screen bg-slate-950 flex flex-col items-center justify-center gap-6 font-mono text-cyan-400 animate-pulse uppercase tracking-widest">Loading Game..</div>;

            if (view === 'lobby') return (
                <>
                    {dictionaryNoticeBanner}
                    <LobbyView
                        user={user}
                        authLoading={authLoading || authActionLoading}
                        authError={authError}
                        authForm={authForm}
                        setAuthForm={setAuthForm}
                        onAuthSignIn={handleAuthSignIn}
                        onAuthRegister={handleAuthRegister}
                        onAuthVerify={handleAuthVerify}
                        onAuthGuest={handleAuthGuest}
                        onAuthSignOut={handleAuthSignOut}
                        otpPending={otpPending}
                        profileLoading={profileLoading}
                        profileStats={profileStats}
                        profileSaveState={profileSaveState}
                        onSaveProfile={saveProfile}
                        playerName={playerName}
                        setPlayerName={setPlayerName}
                        playerAvatar={playerAvatar}
                        setPlayerAvatar={setPlayerAvatar}
                        playerAvatarImageUrl={pendingAvatarPreviewUrl || playerAvatarImageUrl}
                        onAvatarImageFileSelect={handleAvatarImageFileSelect}
                        gameMode={gameMode}
                        setGameMode={setGameMode}
                        botOpponentEnabled={botOpponentEnabled}
                        setBotOpponentEnabled={setBotOpponentEnabled}
                        lobbyMaxPlayers={lobbyMaxPlayers}
                        setLobbyMaxPlayers={setLobbyMaxPlayers}
                        maxOnlinePlayers={MAX_ONLINE_PLAYERS}
                        battleRounds={battleRounds}
                        setBattleRounds={setBattleRounds}
                        hardCapEnabled={hardCapEnabled}
                        setHardCapEnabled={setHardCapEnabled}
                        checkWordEnabled={checkWordEnabled}
                        setCheckWordEnabled={setCheckWordEnabled}
                        initLocalGame={initLocalGame}
                        createLobby={createLobby}
                        lobbyStatus={lobbyStatus}
                        joinLobby={joinLobby}
                        lobbyCode={lobbyCode}
                        lobbyError={lobbyError}
                        waitingPlayers={isOnline ? [{ roleKey: playerRole || 'self', name: playerName, avatar: playerAvatar }, ...opponents] : []}
                        waitingPlayerLimit={lobbyMaxPlayers}
                        leaderboardRows={leaderboardRows}
                        leaderboardLoading={leaderboardLoading}
                        leaderboardError={leaderboardError}
                        liveOverlaySettings={liveOverlaySettings}
                        onLiveOverlaySettingsChange={updateLiveOverlaySettings}
                        onOpenLiveOverlay={openLiveOverlayWindow}
                        onCloseLiveOverlay={closeLiveOverlayWindow}
                        liveOverlayStatus={liveOverlayStatus}
                    />
                </>
            );

            return (
                <>
                    {dictionaryNoticeBanner}
                    <BattleView
                        playerName={playerName}
                        playerUid={user?.uid || ''}
                        playerPoints={playerPoints}
                        isOnline={isOnline}
                        lobbyCode={lobbyCode}
                        opponentName={opponentName}
                        opponents={opponents}
                        enemyHp={enemyHp}
                        battleRounds={battleRounds}
                        enemySolvedCount={enemySolvedCount}
                        enemyBoard={enemyBoard}
                        enemyResults={enemyResults}
                        wordLength={WORD_LENGTH}
                        getTileColor={getTileColor}
                        powerUps={POWER_UPS}
                        usePowerUp={handleUsePowerUp}
                        countdown={countdown}
                        damageEffect={damageEffect}
                        playerSolvedCount={playerSolvedCount}
                        playerHp={playerHp}
                        celebration={celebration}
                        playerBoard={playerBoard}
                        playerResults={playerResults}
                        playerCurrentGuess={playerCurrentGuess}
                        invalidGuessPulse={invalidGuessPulse}
                        keyboardRows={KEYBOARD_ROWS}
                        handleKeyInput={handleKeyInput}
                        isPaused={isPaused}
                        setIsPaused={setIsPaused}
                        initLocalGame={initLocalGame}
                        exitToLobby={exitToLobby}
                        showDebug={showDebug}
                        setShowDebug={setShowDebug}
                        cpuParams={cpuParams}
                        setCpuParams={setCpuParams}
                        gameState={gameState}
                        battleWords={battleWords}
                        solvedHistory={solvedHistory}
                        playerRole={playerRole}
                        rematchState={rematchState}
                        endMatchStep={endMatchStep}
                        setEndMatchStep={setEndMatchStep}
                        revealedWords={revealedWords}
                        onToggleWordReveal={toggleWordReveal}
                        onAcceptRematch={handleAcceptRematch}
                        onDeclineRematch={handleDeclineRematch}
                        isRematchSubmitting={isRematchSubmitting}
                        pendingDebuffId={pendingDebuffId}
                        onSelectDebuffTarget={handleDebuffTargetSelect}
                        onCancelDebuffTarget={cancelDebuffTargeting}
                        chatMessages={chatMessages}
                        onSendChat={sendChatMessage}
                        playerAvatarImageUrl={playerAvatarImageUrl}
                        opponentAvatarImageUrls={opponentAvatarImageUrls}
                    />
                </>
            );
        };

        const root = ReactDOM.createRoot(document.getElementById('root'));
        root.render(<App />);


