// Golf Card Game - Client Application

class GolfGame {
    constructor() {
        this.ws = null;
        this.playerId = null;
        this.roomCode = null;
        this.isHost = false;
        this.gameState = null;
        this.drawnCard = null;
        this.drawnFromDiscard = false;
        this.selectedCards = [];
        this.waitingForFlip = false;
        this.currentPlayers = [];
        this.allProfiles = [];
        this.soundEnabled = true;
        this.audioCtx = null;

        // Swap animation state
        this.swapAnimationInProgress = false;
        this.swapAnimationCardEl = null;
        this.swapAnimationFront = null;
        this.pendingGameState = null;

        // Track cards we've locally flipped (for immediate feedback during selection)
        this.locallyFlippedCards = new Set();

        // Animation lock - prevent overlapping animations on same elements
        this.animatingPositions = new Set();

        // Track opponent swap animation in progress (to apply swap-out class after render)
        this.opponentSwapAnimation = null; // { playerId, position }

        // Track round winners for visual highlight
        this.roundWinnerNames = new Set();

        this.initElements();
        this.initAudio();
        this.bindEvents();
        this.checkUrlParams();
    }

    checkUrlParams() {
        // Handle ?room=XXXX share links
        const params = new URLSearchParams(window.location.search);
        const roomCode = params.get('room');
        if (roomCode) {
            this.roomCodeInput.value = roomCode.toUpperCase();
            // Focus name input so user can quickly enter name and join
            this.playerNameInput.focus();
            // Clean up URL without reloading
            window.history.replaceState({}, '', window.location.pathname);
        }
    }

    initAudio() {
        // Initialize audio context on first user interaction
        const initCtx = () => {
            if (!this.audioCtx) {
                this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
            }
            document.removeEventListener('click', initCtx);
        };
        document.addEventListener('click', initCtx);
    }

    playSound(type = 'click') {
        if (!this.soundEnabled || !this.audioCtx) return;

        const ctx = this.audioCtx;
        const oscillator = ctx.createOscillator();
        const gainNode = ctx.createGain();

        oscillator.connect(gainNode);
        gainNode.connect(ctx.destination);

        if (type === 'click') {
            oscillator.frequency.setValueAtTime(600, ctx.currentTime);
            oscillator.frequency.exponentialRampToValueAtTime(200, ctx.currentTime + 0.05);
            gainNode.gain.setValueAtTime(0.1, ctx.currentTime);
            gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.05);
            oscillator.start(ctx.currentTime);
            oscillator.stop(ctx.currentTime + 0.05);
        } else if (type === 'card') {
            oscillator.frequency.setValueAtTime(800, ctx.currentTime);
            oscillator.frequency.exponentialRampToValueAtTime(400, ctx.currentTime + 0.08);
            gainNode.gain.setValueAtTime(0.08, ctx.currentTime);
            gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.08);
            oscillator.start(ctx.currentTime);
            oscillator.stop(ctx.currentTime + 0.08);
        } else if (type === 'success') {
            oscillator.frequency.setValueAtTime(400, ctx.currentTime);
            oscillator.frequency.setValueAtTime(600, ctx.currentTime + 0.1);
            gainNode.gain.setValueAtTime(0.1, ctx.currentTime);
            gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.2);
            oscillator.start(ctx.currentTime);
            oscillator.stop(ctx.currentTime + 0.2);
        } else if (type === 'flip') {
            // Sharp quick click for card flips
            oscillator.type = 'square';
            oscillator.frequency.setValueAtTime(1800, ctx.currentTime);
            oscillator.frequency.exponentialRampToValueAtTime(600, ctx.currentTime + 0.02);
            gainNode.gain.setValueAtTime(0.12, ctx.currentTime);
            gainNode.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.025);
            oscillator.start(ctx.currentTime);
            oscillator.stop(ctx.currentTime + 0.025);
        } else if (type === 'shuffle') {
            // Multiple quick sounds to simulate shuffling
            for (let i = 0; i < 8; i++) {
                const osc = ctx.createOscillator();
                const gain = ctx.createGain();
                osc.connect(gain);
                gain.connect(ctx.destination);
                osc.type = 'square';
                const time = ctx.currentTime + i * 0.06;
                osc.frequency.setValueAtTime(200 + Math.random() * 400, time);
                gain.gain.setValueAtTime(0.03, time);
                gain.gain.exponentialRampToValueAtTime(0.001, time + 0.05);
                osc.start(time);
                osc.stop(time + 0.05);
            }
            return; // Early return since we don't use the main oscillator
        } else if (type === 'reject') {
            // Low buzz for rejected action
            oscillator.type = 'sawtooth';
            oscillator.frequency.setValueAtTime(150, ctx.currentTime);
            oscillator.frequency.setValueAtTime(100, ctx.currentTime + 0.08);
            gainNode.gain.setValueAtTime(0.08, ctx.currentTime);
            gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.12);
            oscillator.start(ctx.currentTime);
            oscillator.stop(ctx.currentTime + 0.12);
        }
    }

    toggleSound() {
        this.soundEnabled = !this.soundEnabled;
        this.muteBtn.textContent = this.soundEnabled ? '🔊' : '🔇';
        this.playSound('click');
    }

    delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    initElements() {
        // Screens
        this.lobbyScreen = document.getElementById('lobby-screen');
        this.waitingScreen = document.getElementById('waiting-screen');
        this.gameScreen = document.getElementById('game-screen');

        // Lobby elements
        this.playerNameInput = document.getElementById('player-name');
        this.roomCodeInput = document.getElementById('room-code');
        this.createRoomBtn = document.getElementById('create-room-btn');
        this.joinRoomBtn = document.getElementById('join-room-btn');
        this.lobbyError = document.getElementById('lobby-error');

        // Waiting room elements
        this.displayRoomCode = document.getElementById('display-room-code');
        this.copyRoomCodeBtn = document.getElementById('copy-room-code');
        this.shareRoomLinkBtn = document.getElementById('share-room-link');
        this.playersList = document.getElementById('players-list');
        this.hostSettings = document.getElementById('host-settings');
        this.waitingMessage = document.getElementById('waiting-message');
        this.numDecksSelect = document.getElementById('num-decks');
        this.deckRecommendation = document.getElementById('deck-recommendation');
        this.numRoundsSelect = document.getElementById('num-rounds');
        this.initialFlipsSelect = document.getElementById('initial-flips');
        this.flipModeSelect = document.getElementById('flip-mode');
        this.knockPenaltyCheckbox = document.getElementById('knock-penalty');

        // Rules screen elements
        this.rulesScreen = document.getElementById('rules-screen');
        this.rulesBtn = document.getElementById('rules-btn');
        this.rulesBackBtn = document.getElementById('rules-back-btn');
        // House Rules - Point Modifiers
        this.superKingsCheckbox = document.getElementById('super-kings');
        this.tenPennyCheckbox = document.getElementById('ten-penny');
        // House Rules - Bonuses/Penalties
        this.knockBonusCheckbox = document.getElementById('knock-bonus');
        this.underdogBonusCheckbox = document.getElementById('underdog-bonus');
        this.tiedShameCheckbox = document.getElementById('tied-shame');
        this.blackjackCheckbox = document.getElementById('blackjack');
        this.wolfpackCheckbox = document.getElementById('wolfpack');
        // House Rules - New Variants
        this.flipAsActionCheckbox = document.getElementById('flip-as-action');
        this.fourOfAKindCheckbox = document.getElementById('four-of-a-kind');
        this.negativePairsCheckbox = document.getElementById('negative-pairs-keep-value');
        this.oneEyedJacksCheckbox = document.getElementById('one-eyed-jacks');
        this.knockEarlyCheckbox = document.getElementById('knock-early');
        this.wolfpackComboNote = document.getElementById('wolfpack-combo-note');
        this.startGameBtn = document.getElementById('start-game-btn');
        this.leaveRoomBtn = document.getElementById('leave-room-btn');
        this.addCpuBtn = document.getElementById('add-cpu-btn');
        this.removeCpuBtn = document.getElementById('remove-cpu-btn');
        this.cpuControlsSection = document.getElementById('cpu-controls-section');
        this.cpuSelectModal = document.getElementById('cpu-select-modal');
        this.cpuProfilesGrid = document.getElementById('cpu-profiles-grid');
        this.cancelCpuBtn = document.getElementById('cancel-cpu-btn');
        this.addSelectedCpusBtn = document.getElementById('add-selected-cpus-btn');

        // Game elements
        this.currentRoundSpan = document.getElementById('current-round');
        this.totalRoundsSpan = document.getElementById('total-rounds');
        this.statusMessage = document.getElementById('status-message');
        this.playerHeader = document.getElementById('player-header');
        this.yourScore = document.getElementById('your-score');
        this.muteBtn = document.getElementById('mute-btn');
        this.opponentsRow = document.getElementById('opponents-row');
        this.deckArea = document.querySelector('.deck-area');
        this.deck = document.getElementById('deck');
        this.discard = document.getElementById('discard');
        this.discardContent = document.getElementById('discard-content');
        this.discardBtn = document.getElementById('discard-btn');
        this.skipFlipBtn = document.getElementById('skip-flip-btn');
        this.knockEarlyBtn = document.getElementById('knock-early-btn');
        this.playerCards = document.getElementById('player-cards');
        this.playerArea = this.playerCards.closest('.player-area');
        this.swapAnimation = document.getElementById('swap-animation');
        this.swapCardFromHand = document.getElementById('swap-card-from-hand');
        this.heldCardSlot = document.getElementById('held-card-slot');
        this.heldCardDisplay = document.getElementById('held-card-display');
        this.heldCardContent = document.getElementById('held-card-content');
        this.heldCardFloating = document.getElementById('held-card-floating');
        this.heldCardFloatingContent = document.getElementById('held-card-floating-content');
        this.scoreboard = document.getElementById('scoreboard');
        this.scoreTable = document.getElementById('score-table').querySelector('tbody');
        this.standingsList = document.getElementById('standings-list');
        this.gameButtons = document.getElementById('game-buttons');
        this.nextRoundBtn = document.getElementById('next-round-btn');
        this.newGameBtn = document.getElementById('new-game-btn');
        this.leaveGameBtn = document.getElementById('leave-game-btn');
        this.activeRulesBar = document.getElementById('active-rules-bar');
        this.activeRulesList = document.getElementById('active-rules-list');
        this.finalTurnBadge = document.getElementById('final-turn-badge');

        // In-game auth elements
        this.gameUsername = document.getElementById('game-username');
        this.gameLogoutBtn = document.getElementById('game-logout-btn');
        this.authBar = document.getElementById('auth-bar');
    }

    bindEvents() {
        this.createRoomBtn.addEventListener('click', () => { this.playSound('click'); this.createRoom(); });
        this.joinRoomBtn.addEventListener('click', () => { this.playSound('click'); this.joinRoom(); });
        this.startGameBtn.addEventListener('click', () => { this.playSound('success'); this.startGame(); });
        this.leaveRoomBtn.addEventListener('click', () => { this.playSound('click'); this.leaveRoom(); });
        this.deck.addEventListener('click', () => { this.drawFromDeck(); });
        this.discard.addEventListener('click', () => { this.drawFromDiscard(); });
        this.discardBtn.addEventListener('click', () => { this.playSound('card'); this.discardDrawn(); });
        this.skipFlipBtn.addEventListener('click', () => { this.playSound('click'); this.skipFlip(); });
        this.knockEarlyBtn.addEventListener('click', () => { this.playSound('success'); this.knockEarly(); });
        this.nextRoundBtn.addEventListener('click', () => { this.playSound('click'); this.nextRound(); });
        this.newGameBtn.addEventListener('click', () => { this.playSound('click'); this.newGame(); });
        this.addCpuBtn.addEventListener('click', () => { this.playSound('click'); this.showCpuSelect(); });
        this.removeCpuBtn.addEventListener('click', () => { this.playSound('click'); this.removeCpu(); });
        this.cancelCpuBtn.addEventListener('click', () => { this.playSound('click'); this.hideCpuSelect(); });
        this.addSelectedCpusBtn.addEventListener('click', () => { this.playSound('success'); this.addSelectedCpus(); });
        this.muteBtn.addEventListener('click', () => this.toggleSound());
        this.leaveGameBtn.addEventListener('click', () => { this.playSound('click'); this.leaveGame(); });
        this.gameLogoutBtn.addEventListener('click', () => { this.playSound('click'); this.auth?.logout(); });

        // Copy room code to clipboard
        this.copyRoomCodeBtn.addEventListener('click', () => {
            this.playSound('click');
            this.copyRoomCode();
        });

        // Share room link
        this.shareRoomLinkBtn.addEventListener('click', () => {
            this.playSound('click');
            this.shareRoomLink();
        });

        // Enter key handlers
        this.playerNameInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') this.createRoomBtn.click();
        });
        this.roomCodeInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') this.joinRoomBtn.click();
        });

        // Auto-uppercase room code
        this.roomCodeInput.addEventListener('input', (e) => {
            e.target.value = e.target.value.toUpperCase();
        });

        // Update deck recommendation when deck selection changes
        this.numDecksSelect.addEventListener('change', () => {
            const playerCount = this.currentPlayers ? this.currentPlayers.length : 0;
            this.updateDeckRecommendation(playerCount);
        });

        // Show combo note when wolfpack + four-of-a-kind are both selected
        const updateWolfpackCombo = () => {
            if (this.wolfpackCheckbox.checked && this.fourOfAKindCheckbox.checked) {
                this.wolfpackComboNote.classList.remove('hidden');
            } else {
                this.wolfpackComboNote.classList.add('hidden');
            }
        };
        this.wolfpackCheckbox.addEventListener('change', updateWolfpackCombo);
        this.fourOfAKindCheckbox.addEventListener('change', updateWolfpackCombo);

        // Toggle scoreboard collapse on mobile
        const scoreboardTitle = this.scoreboard.querySelector('h4');
        if (scoreboardTitle) {
            scoreboardTitle.addEventListener('click', () => {
                if (window.innerWidth <= 700) {
                    this.scoreboard.classList.toggle('collapsed');
                }
            });
        }

        // Rules screen navigation
        if (this.rulesBtn) {
            this.rulesBtn.addEventListener('click', () => {
                this.playSound('click');
                this.showRulesScreen();
            });
        }
        if (this.rulesBackBtn) {
            this.rulesBackBtn.addEventListener('click', () => {
                this.playSound('click');
                this.showLobby();
            });
        }
    }

    showRulesScreen(scrollToSection = null) {
        this.showScreen(this.rulesScreen);
        if (scrollToSection) {
            const section = document.getElementById(scrollToSection);
            if (section) {
                section.scrollIntoView({ behavior: 'smooth' });
            }
        }
    }

    connect() {
        const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
        const host = window.location.host || 'localhost:8000';
        const wsUrl = `${protocol}//${host}/ws`;

        this.ws = new WebSocket(wsUrl);

        this.ws.onopen = () => {
            console.log('Connected to server');
        };

        this.ws.onmessage = (event) => {
            const data = JSON.parse(event.data);
            this.handleMessage(data);
        };

        this.ws.onclose = () => {
            console.log('Disconnected from server');
            this.showError('Connection lost. Please refresh the page.');
        };

        this.ws.onerror = (error) => {
            console.error('WebSocket error:', error);
            this.showError('Connection error. Please try again.');
        };
    }

    send(message) {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify(message));
        } else {
            console.error('WebSocket not ready, cannot send:', message.type);
            this.showError('Connection lost. Please refresh.');
        }
    }

    handleMessage(data) {
        console.log('Received:', data);

        switch (data.type) {
            case 'room_created':
                this.playerId = data.player_id;
                this.roomCode = data.room_code;
                this.isHost = true;
                this.showWaitingRoom();
                break;

            case 'room_joined':
                this.playerId = data.player_id;
                this.roomCode = data.room_code;
                this.isHost = false;
                this.showWaitingRoom();
                break;

            case 'player_joined':
                this.updatePlayersList(data.players);
                this.currentPlayers = data.players;
                break;

            case 'cpu_profiles':
                this.allProfiles = data.profiles;
                this.renderCpuSelect();
                break;

            case 'player_left':
                this.updatePlayersList(data.players);
                this.currentPlayers = data.players;
                break;

            case 'game_started':
            case 'round_started':
                // Clear any countdown from previous hole
                this.clearNextHoleCountdown();
                this.nextRoundBtn.classList.remove('waiting');
                // Clear round winner highlights
                this.roundWinnerNames = new Set();
                this.gameState = data.game_state;
                // Deep copy for previousState to avoid reference issues
                this.previousState = JSON.parse(JSON.stringify(data.game_state));
                // Reset all tracking for new round
                this.locallyFlippedCards = new Set();
                this.selectedCards = [];
                this.animatingPositions = new Set();
                this.opponentSwapAnimation = null;
                this.playSound('shuffle');
                this.showGameScreen();
                this.renderGame();
                break;

            case 'game_state':
                // State updates are instant, animations are fire-and-forget
                // Exception: Local player's swap animation defers state until complete

                // If local swap animation is running, defer this state update
                if (this.swapAnimationInProgress) {
                    this.updateSwapAnimation(data.game_state.discard_top);
                    this.pendingGameState = data.game_state;
                    break;
                }

                const oldState = this.gameState;
                const newState = data.game_state;

                // Update state FIRST (always)
                this.gameState = newState;

                // Clear local flip tracking if server confirmed our flips
                if (!newState.waiting_for_initial_flip && oldState?.waiting_for_initial_flip) {
                    this.locallyFlippedCards = new Set();
                }

                // Detect and fire animations (non-blocking, errors shouldn't break game)
                try {
                    this.triggerAnimationsForStateChange(oldState, newState);
                } catch (e) {
                    console.error('Animation error:', e);
                }

                // Render immediately with new state
                this.renderGame();
                break;

            case 'your_turn':
                // Brief delay to let animations settle
                setTimeout(() => {
                    // Build toast based on available actions
                    const canFlip = this.gameState && this.gameState.flip_as_action;
                    let canKnock = false;
                    if (this.gameState && this.gameState.knock_early) {
                        const myData = this.gameState.players.find(p => p.id === this.playerId);
                        const faceDownCount = myData ? myData.cards.filter(c => !c.face_up).length : 0;
                        canKnock = faceDownCount >= 1 && faceDownCount <= 2;
                    }
                    if (canFlip && canKnock) {
                        this.showToast('Your turn! Draw, flip, or knock', 'your-turn');
                    } else if (canFlip) {
                        this.showToast('Your turn! Draw or flip a card', 'your-turn');
                    } else if (canKnock) {
                        this.showToast('Your turn! Draw or knock', 'your-turn');
                    } else {
                        this.showToast('Your turn! Draw a card', 'your-turn');
                    }
                }, 200);
                break;

            case 'card_drawn':
                this.drawnCard = data.card;
                this.drawnFromDiscard = data.source === 'discard';
                this.showDrawnCard();
                this.renderGame(); // Re-render to update discard pile
                this.showToast('Swap with a card or discard', '', 3000);
                break;

            case 'can_flip':
                this.waitingForFlip = true;
                this.flipIsOptional = data.optional || false;
                if (this.flipIsOptional) {
                    this.showToast('Flip a card or skip', '', 3000);
                } else {
                    this.showToast('Flip a face-down card', '', 3000);
                }
                this.renderGame();
                break;

            case 'round_over':
                this.showScoreboard(data.scores, false, data.rankings);
                break;

            case 'game_over':
                this.showScoreboard(data.final_scores, true, data.rankings);
                break;

            case 'game_ended':
                // Host ended the game or player was kicked
                this.ws.close();
                this.showLobby();
                if (data.reason) {
                    this.showError(data.reason);
                }
                break;

            case 'error':
                this.showError(data.message);
                break;
        }
    }

    // Room Actions
    createRoom() {
        const name = this.playerNameInput.value.trim() || 'Player';
        this.connect();
        this.ws.onopen = () => {
            this.send({ type: 'create_room', player_name: name });
        };
    }

    joinRoom() {
        const name = this.playerNameInput.value.trim() || 'Player';
        const code = this.roomCodeInput.value.trim().toUpperCase();

        if (code.length !== 4) {
            this.showError('Please enter a 4-letter room code');
            return;
        }

        this.connect();
        this.ws.onopen = () => {
            this.send({ type: 'join_room', room_code: code, player_name: name });
        };
    }

    leaveRoom() {
        this.send({ type: 'leave_room' });
        this.ws.close();
        this.showLobby();
    }

    copyRoomCode() {
        if (!this.roomCode) return;
        this.copyToClipboard(this.roomCode, this.copyRoomCodeBtn);
    }

    shareRoomLink() {
        if (!this.roomCode) return;

        // Build shareable URL with room code
        const url = new URL(window.location.href);
        url.search = ''; // Clear existing params
        url.hash = '';   // Clear hash
        url.searchParams.set('room', this.roomCode);
        const shareUrl = url.toString();

        this.copyToClipboard(shareUrl, this.shareRoomLinkBtn);
    }

    copyToClipboard(text, feedbackBtn) {
        // Use execCommand which is more reliable across contexts
        const textarea = document.createElement('textarea');
        textarea.value = text;
        textarea.style.position = 'fixed';
        textarea.style.left = '-9999px';
        document.body.appendChild(textarea);
        textarea.select();

        let success = false;
        try {
            success = document.execCommand('copy');
        } catch (err) {
            console.error('Copy failed:', err);
        }
        document.body.removeChild(textarea);

        // Show visual feedback
        if (success && feedbackBtn) {
            const originalText = feedbackBtn.textContent;
            feedbackBtn.textContent = '✓';
            setTimeout(() => {
                feedbackBtn.textContent = originalText;
            }, 1500);
        }
    }

    startGame() {
        try {
            const decks = parseInt(this.numDecksSelect.value);
            const rounds = parseInt(this.numRoundsSelect.value);
            const initial_flips = parseInt(this.initialFlipsSelect.value);

            // Standard options
            const flip_mode = this.flipModeSelect.value;  // "never", "always", or "endgame"
            const knock_penalty = this.knockPenaltyCheckbox?.checked || false;

            // Joker mode (radio buttons)
            const jokerRadio = document.querySelector('input[name="joker-mode"]:checked');
            const joker_mode = jokerRadio ? jokerRadio.value : 'none';
            const use_jokers = joker_mode !== 'none';
            const lucky_swing = joker_mode === 'lucky-swing';
            const eagle_eye = joker_mode === 'eagle-eye';

            // House Rules - Point Modifiers
            const super_kings = this.superKingsCheckbox?.checked || false;
            const ten_penny = this.tenPennyCheckbox?.checked || false;

            // House Rules - Bonuses/Penalties
            const knock_bonus = this.knockBonusCheckbox?.checked || false;
            const underdog_bonus = this.underdogBonusCheckbox?.checked || false;
            const tied_shame = this.tiedShameCheckbox?.checked || false;
            const blackjack = this.blackjackCheckbox?.checked || false;
            const wolfpack = this.wolfpackCheckbox?.checked || false;

            // House Rules - New Variants
            const flip_as_action = this.flipAsActionCheckbox?.checked || false;
            const four_of_a_kind = this.fourOfAKindCheckbox?.checked || false;
            const negative_pairs_keep_value = this.negativePairsCheckbox?.checked || false;
            const one_eyed_jacks = this.oneEyedJacksCheckbox?.checked || false;
            const knock_early = this.knockEarlyCheckbox?.checked || false;

            this.send({
                type: 'start_game',
                decks,
                rounds,
                initial_flips,
                flip_mode,
                knock_penalty,
                use_jokers,
                lucky_swing,
                super_kings,
                ten_penny,
                knock_bonus,
                underdog_bonus,
                tied_shame,
                blackjack,
                eagle_eye,
                wolfpack,
                flip_as_action,
                four_of_a_kind,
                negative_pairs_keep_value,
                one_eyed_jacks,
                knock_early
            });
        } catch (error) {
            console.error('Error starting game:', error);
            this.showError('Error starting game. Please refresh.');
        }
    }

    showCpuSelect() {
        // Request available profiles from server
        this.selectedCpus = new Set();
        this.send({ type: 'get_cpu_profiles' });
        this.cpuSelectModal.classList.remove('hidden');
    }

    hideCpuSelect() {
        this.cpuSelectModal.classList.add('hidden');
        this.selectedCpus = new Set();
    }

    renderCpuSelect() {
        if (!this.allProfiles) return;

        // Get names of CPUs already in the game
        const usedNames = new Set(
            (this.currentPlayers || [])
                .filter(p => p.is_cpu)
                .map(p => p.name)
        );

        this.cpuProfilesGrid.innerHTML = '';

        this.allProfiles.forEach(profile => {
            const div = document.createElement('div');
            const isUsed = usedNames.has(profile.name);
            const isSelected = this.selectedCpus && this.selectedCpus.has(profile.name);
            div.className = 'profile-card' + (isUsed ? ' unavailable' : '') + (isSelected ? ' selected' : '');

            const avatar = this.getCpuAvatar(profile.name);
            const checkbox = isUsed ? '' : `<div class="profile-checkbox">${isSelected ? '✓' : ''}</div>`;

            div.innerHTML = `
                ${checkbox}
                <div class="profile-avatar">${avatar}</div>
                <div class="profile-name">${profile.name}</div>
                <div class="profile-style">${profile.style}</div>
                ${isUsed ? '<div class="profile-in-game">In Game</div>' : ''}
            `;

            if (!isUsed) {
                div.addEventListener('click', () => this.toggleCpuSelection(profile.name));
            }

            this.cpuProfilesGrid.appendChild(div);
        });

        this.updateAddCpuButton();
    }

    getCpuAvatar(name) {
        const avatars = {
            'Sofia': `<svg viewBox="0 0 40 40"><path d="M8 19 Q8 5 20 5 Q32 5 32 19 L32 29 Q28 31 26 27 L26 19 M8 19 L8 29 Q12 31 14 27 L14 19" fill="#c44d00"/><circle cx="20" cy="19" r="9" fill="#e8b4b8"/><circle cx="17" cy="18" r="1.5" fill="#333"/><circle cx="23" cy="18" r="1.5" fill="#333"/><path d="M17 22 Q20 24 23 22" stroke="#333" fill="none" stroke-width="1.2"/><path d="M11 10 Q15 7 20 9 Q25 7 29 10 Q25 13 20 12 Q15 13 11 10" fill="#c44d00"/></svg>`,
            'Maya': `<svg viewBox="0 0 40 40"><circle cx="20" cy="19" r="9" fill="#d4a574"/><circle cx="17" cy="17" r="1.5" fill="#333"/><circle cx="23" cy="17" r="1.5" fill="#333"/><path d="M16 22 L24 22" stroke="#333" stroke-width="1.5"/><path d="M11 15 Q11 8 20 8 Q29 8 29 15" fill="#5c4033"/><ellipse cx="32" cy="17" rx="4" ry="6" fill="#5c4033"/><circle cx="30" cy="15" r="2" fill="#e91e63"/></svg>`,
            'Priya': `<svg viewBox="0 0 40 40"><circle cx="20" cy="21" r="9" fill="#c4956a"/><path d="M12 15 Q12 8 20 8 Q28 8 28 15" fill="#111"/><path d="M12 14 Q10 16 11 20" stroke="#111" stroke-width="2" fill="none"/><path d="M28 14 Q30 16 29 20" stroke="#111" stroke-width="2" fill="none"/><circle cx="17" cy="20" r="1.5" fill="#333"/><circle cx="23" cy="20" r="1.5" fill="#333"/><path d="M17 25 Q20 27 23 25" stroke="#333" fill="none" stroke-width="1.2"/><circle cx="20" cy="17" r="1" fill="#e74c3c"/></svg>`,
            'Marcus': `<svg viewBox="0 0 40 40"><circle cx="20" cy="21" r="10" fill="#a67c52"/><circle cx="17" cy="19" r="2" fill="#333"/><circle cx="23" cy="19" r="2" fill="#333"/><path d="M16 25 Q20 27 24 25" stroke="#333" fill="none" stroke-width="1.5"/><rect x="10" y="8" width="20" height="6" rx="2" fill="#333"/></svg>`,
            'Kenji': `<svg viewBox="0 0 40 40"><circle cx="20" cy="20" r="10" fill="#f0d5a8"/><circle cx="17" cy="18" r="2" fill="#333"/><circle cx="23" cy="18" r="2" fill="#333"/><path d="M17 23 L23 23" stroke="#333" stroke-width="1.5"/><path d="M10 16 Q10 8 20 8 Q30 8 30 16 L28 14 L26 16 L24 13 L22 16 L20 12 L18 16 L16 13 L14 16 L12 14 Z" fill="#1a1a1a"/></svg>`,
            'Diego': `<svg viewBox="0 0 40 40"><circle cx="20" cy="20" r="10" fill="#c9a86c"/><circle cx="17" cy="18" r="2" fill="#333"/><circle cx="23" cy="18" r="2" fill="#333"/><path d="M15 23 Q20 28 25 23" stroke="#333" fill="none" stroke-width="1.5"/><path d="M10 14 Q15 9 20 12 Q25 9 30 14" stroke="#2c1810" fill="none" stroke-width="3"/><rect x="17" y="26" width="6" height="4" rx="1" fill="#4a3728"/></svg>`,
            'River': `<svg viewBox="0 0 40 40"><circle cx="20" cy="21" r="9" fill="#e0c8a8"/><path d="M10 19 Q10 11 20 11 Q30 11 30 19" fill="#7c5e3c"/><circle cx="17" cy="20" r="1.5" fill="#333"/><circle cx="23" cy="20" r="1.5" fill="#333"/><path d="M17 24 Q20 26 23 24" stroke="#333" fill="none" stroke-width="1.2"/><path d="M6 17 Q6 9 20 9 Q34 9 34 17" stroke="#333" stroke-width="2" fill="none"/><ellipse cx="6" cy="21" rx="4" ry="5" fill="#222"/><ellipse cx="34" cy="21" rx="4" ry="5" fill="#222"/><ellipse cx="6" cy="21" rx="2.5" ry="3.5" fill="#444"/><ellipse cx="34" cy="21" rx="2.5" ry="3.5" fill="#444"/></svg>`,
            'Sage': `<svg viewBox="0 0 40 40"><circle cx="20" cy="26" r="10" fill="#d4b896"/><circle cx="17" cy="24" r="2" fill="#333"/><circle cx="23" cy="24" r="2" fill="#333"/><path d="M17 30 L23 28" stroke="#333" stroke-width="1.5"/><path d="M8 18 L20 1 L32 18 Z" fill="#3a3a80"/><ellipse cx="20" cy="18" rx="14" ry="4" fill="#3a3a80"/><circle cx="16" cy="12" r="1" fill="#ffd700"/><circle cx="24" cy="8" r="1.2" fill="#ffd700"/></svg>`
        };
        return avatars[name] || `<svg viewBox="0 0 40 40"><circle cx="20" cy="16" r="10" fill="#ccc"/><circle cx="17" cy="14" r="2" fill="#333"/><circle cx="23" cy="14" r="2" fill="#333"/></svg>`;
    }

    toggleCpuSelection(profileName) {
        if (!this.selectedCpus) this.selectedCpus = new Set();

        if (this.selectedCpus.has(profileName)) {
            this.selectedCpus.delete(profileName);
        } else {
            this.selectedCpus.add(profileName);
        }
        this.renderCpuSelect();
    }

    updateAddCpuButton() {
        const count = this.selectedCpus ? this.selectedCpus.size : 0;
        this.addSelectedCpusBtn.textContent = count > 0 ? `Add ${count} CPU${count > 1 ? 's' : ''}` : 'Add';
        this.addSelectedCpusBtn.disabled = count === 0;
    }

    addSelectedCpus() {
        if (!this.selectedCpus || this.selectedCpus.size === 0) return;

        this.selectedCpus.forEach(profileName => {
            this.send({ type: 'add_cpu', profile_name: profileName });
        });
        this.hideCpuSelect();
    }

    removeCpu() {
        this.send({ type: 'remove_cpu' });
    }

    // Game Actions
    drawFromDeck() {
        if (!this.isMyTurn() || this.drawnCard || this.gameState.has_drawn_card) {
            if (this.gameState && !this.gameState.waiting_for_initial_flip) {
                this.playSound('reject');
            }
            return;
        }
        if (this.gameState.waiting_for_initial_flip) return;
        this.playSound('card');
        this.send({ type: 'draw', source: 'deck' });
    }

    drawFromDiscard() {
        // If holding a card drawn from discard, clicking discard puts it back
        if (this.drawnCard && !this.gameState.can_discard) {
            this.playSound('click');
            this.cancelDraw();
            return;
        }

        if (!this.isMyTurn() || this.drawnCard || this.gameState.has_drawn_card) {
            if (this.gameState && !this.gameState.waiting_for_initial_flip) {
                this.playSound('reject');
            }
            return;
        }
        if (this.gameState.waiting_for_initial_flip) return;
        if (!this.gameState.discard_top) return;
        this.playSound('card');
        this.send({ type: 'draw', source: 'discard' });
    }

    discardDrawn() {
        if (!this.drawnCard) return;
        const discardedCard = this.drawnCard;
        this.send({ type: 'discard' });
        this.drawnCard = null;
        this.hideToast();
        this.discardBtn.classList.add('hidden');

        // Pre-emptively skip the flip animation - the server may broadcast the new state
        // before our animation completes, and we don't want renderGame() to trigger
        // the flip-in animation (which starts with opacity: 0, causing a flash)
        this.skipNextDiscardFlip = true;
        // Also update lastDiscardKey so renderGame() won't see a "change"
        this.lastDiscardKey = `${discardedCard.rank}-${discardedCard.suit}`;

        // Swoop animation: deck → discard (card is always held over deck)
        this.animateDeckToDiscardSwoop(discardedCard);
    }

    // Swoop animation for discarding a card drawn from deck
    animateDeckToDiscardSwoop(card) {
        const deckRect = this.deck.getBoundingClientRect();
        const discardRect = this.discard.getBoundingClientRect();
        const floater = this.heldCardFloating;

        // Reset any previous animation state
        floater.classList.remove('dropping', 'swooping', 'landed');

        // Instantly position at deck (card appears to come from deck)
        floater.style.transition = 'none';
        floater.style.left = `${deckRect.left}px`;
        floater.style.top = `${deckRect.top}px`;
        floater.style.width = `${deckRect.width}px`;
        floater.style.height = `${deckRect.height}px`;
        floater.style.transform = 'scale(1) rotate(0deg)';

        // Force reflow
        floater.offsetHeight;

        // Start swoop to discard
        floater.style.transition = '';
        floater.classList.add('swooping');
        floater.style.left = `${discardRect.left}px`;
        floater.style.top = `${discardRect.top}px`;
        floater.style.width = `${discardRect.width}px`;
        floater.style.height = `${discardRect.height}px`;

        this.playSound('card');

        // After swoop completes, settle and show on discard pile
        setTimeout(() => {
            floater.classList.add('landed');

            setTimeout(() => {
                floater.classList.add('hidden');
                floater.classList.remove('swooping', 'landed');
                // Clear all inline styles from the animation
                floater.style.cssText = '';
                this.updateDiscardPileDisplay(card);
                this.pulseDiscardLand();
                this.skipNextDiscardFlip = true;
            }, 150); // Brief settle
        }, 350); // Match swoop transition duration
    }

    // Update the discard pile display with a card
    updateDiscardPileDisplay(card) {
        this.discard.classList.remove('picked-up', 'disabled');
        this.discard.classList.add('has-card', 'card-front');
        this.discard.classList.remove('red', 'black', 'joker');

        if (card.rank === '★') {
            this.discard.classList.add('joker');
            const jokerIcon = card.suit === 'hearts' ? '🐉' : '👹';
            this.discardContent.innerHTML = `<span class="joker-icon">${jokerIcon}</span><span class="joker-label">Joker</span>`;
        } else {
            this.discard.classList.add(card.suit === 'hearts' || card.suit === 'diamonds' ? 'red' : 'black');
            this.discardContent.innerHTML = this.renderCardContent(card);
        }
        this.lastDiscardKey = `${card.rank}-${card.suit}`;
    }

    cancelDraw() {
        if (!this.drawnCard) return;
        const cardToReturn = this.drawnCard;
        const wasFromDiscard = this.drawnFromDiscard;
        this.send({ type: 'cancel_draw' });
        this.drawnCard = null;
        this.hideToast();

        if (wasFromDiscard) {
            // Animate card from deck position back to discard pile
            this.animateDeckToDiscardReturn(cardToReturn);
        } else {
            this.hideDrawnCard();
        }
    }

    // Animate returning a card from deck position to discard pile (for cancel draw from discard)
    animateDeckToDiscardReturn(card) {
        const discardRect = this.discard.getBoundingClientRect();
        const floater = this.heldCardFloating;

        // Add swooping class for smooth transition
        floater.classList.add('swooping');
        floater.style.left = `${discardRect.left}px`;
        floater.style.top = `${discardRect.top}px`;
        floater.style.width = `${discardRect.width}px`;
        floater.style.height = `${discardRect.height}px`;

        this.playSound('card');

        // After swoop completes, hide floater and update discard pile
        setTimeout(() => {
            floater.classList.add('landed');

            setTimeout(() => {
                floater.classList.add('hidden');
                floater.classList.remove('swooping', 'landed');
                floater.style.cssText = '';
                this.updateDiscardPileDisplay(card);
                this.pulseDiscardLand();
            }, 150);
        }, 350);
    }

    swapCard(position) {
        if (!this.drawnCard) return;
        this.send({ type: 'swap', position });
        this.drawnCard = null;
        this.hideDrawnCard();
    }

    // Animate player swapping drawn card with a card in their hand
    // Uses flip-in-place + teleport (no zipping movement)
    animateSwap(position) {
        const cardElements = this.playerCards.querySelectorAll('.card');
        const handCardEl = cardElements[position];
        if (!handCardEl) {
            this.swapCard(position);
            return;
        }

        // Check if card is already face-up
        const myData = this.getMyPlayerData();
        const card = myData?.cards[position];
        const isAlreadyFaceUp = card?.face_up;

        // Get positions
        const handRect = handCardEl.getBoundingClientRect();

        // Set up the animated card at hand position
        const swapCard = this.swapCardFromHand;
        if (!swapCard) {
            // Animation element missing - fall back to non-animated swap
            console.error('Swap animation element missing, falling back to direct swap');
            this.swapCard(position);
            return;
        }
        const swapCardFront = swapCard.querySelector('.swap-card-front');

        // Position at the hand card location
        swapCard.style.left = handRect.left + 'px';
        swapCard.style.top = handRect.top + 'px';
        swapCard.style.width = handRect.width + 'px';
        swapCard.style.height = handRect.height + 'px';

        // Reset state - no moving class needed
        swapCard.classList.remove('flipping', 'moving');
        swapCardFront.innerHTML = '';
        swapCardFront.className = 'swap-card-front';

        // Mark animating
        this.swapAnimationInProgress = true;
        this.swapAnimationCardEl = handCardEl;
        this.swapAnimationHandCardEl = handCardEl;

        if (isAlreadyFaceUp && card) {
            // FACE-UP CARD: Subtle pulse animation (no flip needed)
            this.swapAnimationContentSet = true;

            // Apply subtle swap pulse to both cards
            handCardEl.classList.add('swap-pulse');
            this.heldCardFloating.classList.add('swap-pulse');

            // Play a soft sound for the swap
            this.playSound('card');

            // Send swap and let render handle the update
            this.send({ type: 'swap', position });
            this.drawnCard = null;
            this.skipNextDiscardFlip = true;

            // Complete after pulse animation
            setTimeout(() => {
                handCardEl.classList.remove('swap-pulse');
                this.heldCardFloating.classList.remove('swap-pulse');
                this.completeSwapAnimation(null);
            }, 440);
        } else {
            // FACE-DOWN CARD: Flip in place to reveal, then teleport

            // Hide the actual hand card
            handCardEl.classList.add('swap-out');
            this.swapAnimation.classList.remove('hidden');

            // Store references for updateSwapAnimation
            this.swapAnimationFront = swapCardFront;
            this.swapAnimationCard = swapCard;
            this.swapAnimationContentSet = false;

            // Send swap - the flip will happen in updateSwapAnimation when server responds
            this.send({ type: 'swap', position });
            this.drawnCard = null;
            this.skipNextDiscardFlip = true;
        }
    }

    // Update the animated card with actual card content when server responds
    updateSwapAnimation(card) {
        // Safety: if animation references are missing, complete immediately to avoid freeze
        if (!this.swapAnimationFront || !card) {
            if (this.swapAnimationInProgress && !this.swapAnimationContentSet) {
                console.error('Swap animation incomplete: missing front element or card data');
                this.completeSwapAnimation(null);
            }
            return;
        }

        // Skip if we already set the content (face-up card swap)
        if (this.swapAnimationContentSet) return;

        // Set card color class
        this.swapAnimationFront.className = 'swap-card-front';
        if (card.rank === '★') {
            this.swapAnimationFront.classList.add('joker');
            const jokerIcon = card.suit === 'hearts' ? '🐉' : '👹';
            this.swapAnimationFront.innerHTML = `<span class="joker-icon">${jokerIcon}</span><span class="joker-label">Joker</span>`;
        } else {
            if (card.suit === 'hearts' || card.suit === 'diamonds') {
                this.swapAnimationFront.classList.add('red');
            } else {
                this.swapAnimationFront.classList.add('black');
            }
            this.swapAnimationFront.innerHTML = `${card.rank}<br>${this.getSuitSymbol(card.suit)}`;
        }

        this.swapAnimationContentSet = true;

        // Quick flip to reveal, then complete - server will pause before next turn
        if (this.swapAnimationCard) {
            // Step 1: Flip to reveal (quick)
            this.swapAnimationCard.classList.add('flipping');
            this.playSound('flip');

            // Step 2: Brief pulse after flip completes
            setTimeout(() => {
                this.swapAnimationCard.classList.add('swap-pulse');
                this.playSound('card');
            }, 350);

            // Step 3: Complete animation - the pause to see the result happens
            // on the server side before the next CPU turn starts
            setTimeout(() => {
                this.completeSwapAnimation(null);
            }, 550);
        } else {
            // Fallback: animation element missing, complete immediately to avoid freeze
            console.error('Swap animation element missing, completing immediately');
            this.completeSwapAnimation(null);
        }
    }

    completeSwapAnimation(heldCard) {
        // Hide everything
        this.swapAnimation.classList.add('hidden');
        if (this.swapAnimationCard) {
            this.swapAnimationCard.classList.remove('hidden', 'flipping', 'moving', 'swap-pulse');
        }
        if (heldCard) {
            heldCard.classList.remove('flipping', 'moving');
            heldCard.classList.add('hidden');
        }
        if (this.swapAnimationHandCardEl) {
            this.swapAnimationHandCardEl.classList.remove('swap-out');
        }
        this.discard.classList.remove('swap-to-hand');
        this.swapAnimationInProgress = false;
        this.swapAnimationFront = null;
        this.swapAnimationCard = null;
        this.swapAnimationDiscardRect = null;
        this.swapAnimationHandCardEl = null;
        this.swapAnimationHandRect = null;
        this.discardBtn.classList.add('hidden');
        this.heldCardFloating.classList.add('hidden');

        if (this.pendingGameState) {
            this.gameState = this.pendingGameState;
            this.pendingGameState = null;
            this.renderGame();
        }
    }

    flipCard(position) {
        this.send({ type: 'flip_card', position });
        this.waitingForFlip = false;
        this.flipIsOptional = false;
    }

    skipFlip() {
        if (!this.flipIsOptional) return;
        this.send({ type: 'skip_flip' });
        this.waitingForFlip = false;
        this.flipIsOptional = false;
        this.hideToast();
    }

    knockEarly() {
        // Flip all remaining face-down cards to go out early
        if (!this.gameState || !this.gameState.knock_early) return;
        this.send({ type: 'knock_early' });
        this.hideToast();
    }

    // Fire-and-forget animation triggers based on state changes
    triggerAnimationsForStateChange(oldState, newState) {
        if (!oldState) return;

        // Check for discard pile changes
        const newDiscard = newState.discard_top;
        const oldDiscard = oldState.discard_top;
        const discardChanged = newDiscard && (!oldDiscard ||
            newDiscard.rank !== oldDiscard.rank ||
            newDiscard.suit !== oldDiscard.suit);

        const previousPlayerId = oldState.current_player_id;
        const wasOtherPlayer = previousPlayerId && previousPlayerId !== this.playerId;

        // Detect which pile opponent drew from and pulse it
        if (wasOtherPlayer && discardChanged) {
            const oldPlayer = oldState.players.find(p => p.id === previousPlayerId);
            const newPlayer = newState.players.find(p => p.id === previousPlayerId);

            if (oldPlayer && newPlayer && oldDiscard) {
                // Check if any of their cards now matches the old discard top
                // This means they took from discard pile
                let tookFromDiscard = false;
                for (let i = 0; i < 6; i++) {
                    const newCard = newPlayer.cards[i];
                    if (newCard?.face_up &&
                        newCard.rank === oldDiscard.rank &&
                        newCard.suit === oldDiscard.suit) {
                        tookFromDiscard = true;
                        break;
                    }
                }

                // Pulse the appropriate pile
                this.pulseDrawPile(tookFromDiscard ? 'discard' : 'deck');

                // Show CPU action announcement for draw
                if (oldPlayer.is_cpu) {
                    this.showCpuAction(oldPlayer.name, tookFromDiscard ? 'draw-discard' : 'draw-deck', tookFromDiscard ? oldDiscard : null);
                }
            } else {
                // No old discard or couldn't detect - assume deck
                this.pulseDrawPile('deck');

                // Show CPU action announcement
                if (oldPlayer?.is_cpu) {
                    this.showCpuAction(oldPlayer.name, 'draw-deck');
                }
            }
        }

        if (discardChanged) {
            // Check if the previous player actually SWAPPED (has a new face-up card)
            // vs just discarding the drawn card (no hand change)
            const oldPlayer = oldState.players.find(p => p.id === previousPlayerId);
            const newPlayer = newState.players.find(p => p.id === previousPlayerId);

            if (oldPlayer && newPlayer) {
                // Find the position that changed
                // Could be: face-down -> face-up (new reveal)
                // Or: different card at same position (replaced visible card)
                // Or: card identity became known (null -> value, indicates swap)
                let swappedPosition = -1;
                let wasFaceUp = false;  // Track if old card was already face-up

                for (let i = 0; i < 6; i++) {
                    const oldCard = oldPlayer.cards[i];
                    const newCard = newPlayer.cards[i];
                    const wasUp = oldCard?.face_up;
                    const isUp = newCard?.face_up;

                    // Case 1: face-down became face-up (needs flip)
                    if (!wasUp && isUp) {
                        swappedPosition = i;
                        wasFaceUp = false;
                        break;
                    }
                    // Case 2: both face-up but different card (no flip needed)
                    if (wasUp && isUp && oldCard.rank && newCard.rank) {
                        if (oldCard.rank !== newCard.rank || oldCard.suit !== newCard.suit) {
                            swappedPosition = i;
                            wasFaceUp = true;  // Face-to-face swap
                            break;
                        }
                    }
                    // Case 3: Card identity became known (opponent's hidden card was swapped)
                    // This handles race conditions where face_up might not be updated yet
                    if (!oldCard?.rank && newCard?.rank) {
                        swappedPosition = i;
                        wasFaceUp = false;
                        break;
                    }
                }

                // Check if opponent's cards are completely unchanged (server might send split updates)
                const cardsIdentical = wasOtherPlayer && JSON.stringify(oldPlayer.cards) === JSON.stringify(newPlayer.cards);

                if (swappedPosition >= 0 && wasOtherPlayer) {
                    // Opponent swapped - animate from the actual position that changed
                    this.fireSwapAnimation(previousPlayerId, newDiscard, swappedPosition, wasFaceUp);
                    // Show CPU swap announcement
                    if (oldPlayer.is_cpu) {
                        this.showCpuAction(oldPlayer.name, 'swap');
                    }
                } else if (swappedPosition < 0 && !cardsIdentical) {
                    // Player drew and discarded without swapping
                    // Only fire if cards actually differ (avoid race condition with split server updates)
                    // Show animation for other players, just pulse for local player
                    this.fireDiscardAnimation(newDiscard, previousPlayerId);
                    // Show CPU discard announcement
                    if (wasOtherPlayer && oldPlayer?.is_cpu) {
                        this.showCpuAction(oldPlayer.name, 'discard', newDiscard);
                    }
                }
                // Skip the card-flip-in animation since we just did our own
                this.skipNextDiscardFlip = true;
            }
        }

        // Handle delayed card updates (server sends split updates: discard first, then cards)
        // Check if opponent cards changed even when discard didn't change
        if (!discardChanged && wasOtherPlayer && previousPlayerId) {
            const oldPlayer = oldState.players.find(p => p.id === previousPlayerId);
            const newPlayer = newState.players.find(p => p.id === previousPlayerId);

            if (oldPlayer && newPlayer) {
                // Check for card changes that indicate a swap we missed
                for (let i = 0; i < 6; i++) {
                    const oldCard = oldPlayer.cards[i];
                    const newCard = newPlayer.cards[i];

                    // Card became visible (swap completed in delayed update)
                    if (!oldCard?.face_up && newCard?.face_up) {
                        this.fireSwapAnimation(previousPlayerId, newState.discard_top, i, false);
                        if (oldPlayer.is_cpu) {
                            this.showCpuAction(oldPlayer.name, 'swap');
                        }
                        break;
                    }
                    // Card identity became known
                    if (!oldCard?.rank && newCard?.rank) {
                        this.fireSwapAnimation(previousPlayerId, newState.discard_top, i, false);
                        if (oldPlayer.is_cpu) {
                            this.showCpuAction(oldPlayer.name, 'swap');
                        }
                        break;
                    }
                }
            }
        }
    }

    // Flash animation on deck or discard pile to show where opponent drew from
    pulseDrawPile(source) {
        const T = window.TIMING?.feedback || {};
        const pile = source === 'discard' ? this.discard : this.deck;
        pile.classList.remove('draw-pulse');
        // Trigger reflow to restart animation
        void pile.offsetWidth;
        pile.classList.add('draw-pulse');
        // Remove class after animation completes
        setTimeout(() => pile.classList.remove('draw-pulse'), T.drawPulse || 450);
    }

    // Pulse discard pile when a card lands on it
    pulseDiscardLand() {
        const T = window.TIMING?.feedback || {};
        this.discard.classList.remove('discard-land');
        void this.discard.offsetWidth;
        this.discard.classList.add('discard-land');
        setTimeout(() => this.discard.classList.remove('discard-land'), T.discardLand || 460);
    }

    // Fire animation for discard without swap (card lands on discard pile face-up)
    // Shows card moving from deck to discard for other players only
    fireDiscardAnimation(discardCard, fromPlayerId = null) {
        // Only show animation for other players - local player already knows what they did
        const isOtherPlayer = fromPlayerId && fromPlayerId !== this.playerId;

        if (isOtherPlayer && discardCard) {
            // Show card traveling from deck to discard pile
            this.animateDeckToDiscard(discardCard);
        }
        // Skip animation entirely for local player
    }

    // Animate a card moving from deck to discard pile (for draw-and-discard by other players)
    animateDeckToDiscard(card) {
        const deckRect = this.deck.getBoundingClientRect();
        const discardRect = this.discard.getBoundingClientRect();

        // Create temporary card element
        const animCard = document.createElement('div');
        animCard.className = 'real-card anim-card';
        animCard.innerHTML = `
            <div class="card-inner">
                <div class="card-face card-face-front"></div>
                <div class="card-face card-face-back"><span>?</span></div>
            </div>
        `;

        // Position at deck
        animCard.style.position = 'fixed';
        animCard.style.left = `${deckRect.left}px`;
        animCard.style.top = `${deckRect.top}px`;
        animCard.style.width = `${deckRect.width}px`;
        animCard.style.height = `${deckRect.height}px`;
        animCard.style.zIndex = '1000';
        animCard.style.transition = `left ${window.TIMING?.card?.move || 270}ms ease-out, top ${window.TIMING?.card?.move || 270}ms ease-out`;

        const inner = animCard.querySelector('.card-inner');
        const front = animCard.querySelector('.card-face-front');

        // Start face-down (back showing)
        inner.classList.add('flipped');

        // Set up the front face content for the flip
        front.className = 'card-face card-face-front';
        if (card.rank === '★') {
            front.classList.add('joker');
            const jokerIcon = card.suit === 'hearts' ? '🐉' : '👹';
            front.innerHTML = `<span class="joker-icon">${jokerIcon}</span><span class="joker-label">Joker</span>`;
        } else {
            front.classList.add(this.isRedSuit(card.suit) ? 'red' : 'black');
            front.innerHTML = `${card.rank}<br>${this.getSuitSymbol(card.suit)}`;
        }

        document.body.appendChild(animCard);

        // Small delay then start moving and flipping
        setTimeout(() => {
            this.playSound('card');
            // Move to discard position
            animCard.style.left = `${discardRect.left}px`;
            animCard.style.top = `${discardRect.top}px`;
            // Flip to show face
            inner.classList.remove('flipped');
        }, 50);

        // Clean up after animation
        const moveDuration = window.TIMING?.card?.move || 270;
        const pauseAfter = window.TIMING?.pause?.afterDiscard || 550;
        setTimeout(() => {
            animCard.remove();
            this.pulseDiscardLand();
        }, moveDuration + pauseAfter);
    }

    // Get rotation angle from an element's computed transform
    getElementRotation(element) {
        if (!element) return 0;
        const style = window.getComputedStyle(element);
        const transform = style.transform;
        if (!transform || transform === 'none') return 0;

        // Parse rotation from transform matrix
        const values = transform.split('(')[1]?.split(')')[0]?.split(',');
        if (values && values.length >= 2) {
            const a = parseFloat(values[0]);
            const b = parseFloat(values[1]);
            return Math.round(Math.atan2(b, a) * (180 / Math.PI));
        }
        return 0;
    }

    // Fire a swap animation (non-blocking) - flip in place at opponent's position
    // Uses flip-in-place for face-down cards, subtle pulse for face-up cards
    fireSwapAnimation(playerId, discardCard, position, wasFaceUp = false) {
        // Track this animation so renderGame can apply swap-out class
        this.opponentSwapAnimation = { playerId, position };

        // Find source position - the actual card that was swapped
        const opponentAreas = this.opponentsRow.querySelectorAll('.opponent-area');
        let sourceRect = null;
        let sourceCardEl = null;
        let sourceRotation = 0;

        for (const area of opponentAreas) {
            const nameEl = area.querySelector('h4');
            const player = this.gameState?.players.find(p => p.id === playerId);
            if (nameEl && player && nameEl.textContent.includes(player.name)) {
                const cards = area.querySelectorAll('.card');
                if (cards.length > position && position >= 0) {
                    sourceCardEl = cards[position];
                    sourceRect = sourceCardEl.getBoundingClientRect();
                    // Get rotation from the opponent area (parent has the arch rotation)
                    sourceRotation = this.getElementRotation(area);
                }
                break;
            }
        }

        // Face-to-face swap: use subtle pulse on the card, no flip needed
        if (wasFaceUp && sourceCardEl) {
            sourceCardEl.classList.add('swap-pulse');
            this.playSound('card');
            const pulseDuration = window.TIMING?.feedback?.discardPickup || 400;
            setTimeout(() => {
                sourceCardEl.classList.remove('swap-pulse');
            }, pulseDuration);
            return;
        }

        // Face-down to face-up: flip to reveal, pause to see it, then pulse before swap
        if (!sourceRect) {
            // Fallback: just show flip at discard position
            const discardRect = this.discard.getBoundingClientRect();
            sourceRect = { left: discardRect.left, top: discardRect.top, width: discardRect.width, height: discardRect.height };
        }

        const swapCard = this.swapCardFromHand;
        const swapCardFront = swapCard.querySelector('.swap-card-front');

        // Position at opponent's card location (flip in place there)
        swapCard.style.left = sourceRect.left + 'px';
        swapCard.style.top = sourceRect.top + 'px';
        swapCard.style.width = sourceRect.width + 'px';
        swapCard.style.height = sourceRect.height + 'px';
        swapCard.classList.remove('flipping', 'moving', 'swap-pulse');

        // Apply source rotation to match the arch layout
        swapCard.style.transform = `rotate(${sourceRotation}deg)`;

        // Set card content (the card being discarded - what was hidden)
        swapCardFront.className = 'swap-card-front';
        if (discardCard.rank === '★') {
            swapCardFront.classList.add('joker');
            const jokerIcon = discardCard.suit === 'hearts' ? '🐉' : '👹';
            swapCardFront.innerHTML = `<span class="joker-icon">${jokerIcon}</span><span class="joker-label">Joker</span>`;
        } else {
            swapCardFront.classList.add(discardCard.suit === 'hearts' || discardCard.suit === 'diamonds' ? 'red' : 'black');
            swapCardFront.innerHTML = `${discardCard.rank}<br>${this.getSuitSymbol(discardCard.suit)}`;
        }

        if (sourceCardEl) sourceCardEl.classList.add('swap-out');
        this.swapAnimation.classList.remove('hidden');

        // Use centralized timing - no beat pauses, continuous flow
        const flipTime = window.TIMING?.card?.flip || 400;

        // Step 1: Flip to reveal the hidden card
        swapCard.classList.add('flipping');
        this.playSound('flip');

        // Step 2: After flip completes, clean up immediately
        setTimeout(() => {
            this.swapAnimation.classList.add('hidden');
            swapCard.classList.remove('flipping');
            swapCard.style.transform = '';
            this.opponentSwapAnimation = null;
            this.pulseDiscardLand();
        }, flipTime);
    }

    // Fire a flip animation for local player's card (non-blocking)
    fireLocalFlipAnimation(position, cardData) {
        const key = `local-${position}`;
        if (this.animatingPositions.has(key)) return;
        this.animatingPositions.add(key);

        const cardElements = this.playerCards.querySelectorAll('.card');
        const cardEl = cardElements[position];
        if (!cardEl) {
            this.animatingPositions.delete(key);
            return;
        }

        const cardRect = cardEl.getBoundingClientRect();
        const swapCard = this.swapCardFromHand;
        const swapCardFront = swapCard.querySelector('.swap-card-front');

        swapCard.style.left = cardRect.left + 'px';
        swapCard.style.top = cardRect.top + 'px';
        swapCard.style.width = cardRect.width + 'px';
        swapCard.style.height = cardRect.height + 'px';
        swapCard.classList.remove('flipping', 'moving');

        // Set card content
        swapCardFront.className = 'swap-card-front';
        if (cardData.rank === '★') {
            swapCardFront.classList.add('joker');
            const jokerIcon = cardData.suit === 'hearts' ? '🐉' : '👹';
            swapCardFront.innerHTML = `<span class="joker-icon">${jokerIcon}</span><span class="joker-label">Joker</span>`;
        } else {
            swapCardFront.classList.add(cardData.suit === 'hearts' || cardData.suit === 'diamonds' ? 'red' : 'black');
            const suitSymbol = { hearts: '♥', diamonds: '♦', clubs: '♣', spades: '♠' }[cardData.suit];
            swapCardFront.innerHTML = `${cardData.rank}<br>${suitSymbol}`;
        }

        cardEl.classList.add('swap-out');
        this.swapAnimation.classList.remove('hidden');

        // Use centralized timing
        const preFlip = window.TIMING?.pause?.beforeFlip || 50;
        const flipDuration = window.TIMING?.card?.flip || 540;

        setTimeout(() => {
            swapCard.classList.add('flipping');
            this.playSound('flip');
        }, preFlip);

        setTimeout(() => {
            this.swapAnimation.classList.add('hidden');
            swapCard.classList.remove('flipping');
            cardEl.classList.remove('swap-out');
            this.animatingPositions.delete(key);
        }, preFlip + flipDuration);
    }

    // Fire a flip animation for opponent card (non-blocking)
    fireFlipAnimation(playerId, position, cardData) {
        // Skip if already animating this position
        const key = `${playerId}-${position}`;
        if (this.animatingPositions.has(key)) return;
        this.animatingPositions.add(key);

        // Find the card element and parent area (for rotation)
        const opponentAreas = this.opponentsRow.querySelectorAll('.opponent-area');
        let cardEl = null;
        let sourceRotation = 0;

        for (const area of opponentAreas) {
            const nameEl = area.querySelector('h4');
            const player = this.gameState?.players.find(p => p.id === playerId);
            if (nameEl && player && nameEl.textContent.includes(player.name)) {
                const cards = area.querySelectorAll('.card');
                cardEl = cards[position];
                sourceRotation = this.getElementRotation(area);
                break;
            }
        }

        if (!cardEl) {
            this.animatingPositions.delete(key);
            return;
        }

        const cardRect = cardEl.getBoundingClientRect();
        const swapCard = this.swapCardFromHand;
        const swapCardFront = swapCard.querySelector('.swap-card-front');

        swapCard.style.left = cardRect.left + 'px';
        swapCard.style.top = cardRect.top + 'px';
        swapCard.style.width = cardRect.width + 'px';
        swapCard.style.height = cardRect.height + 'px';
        swapCard.classList.remove('flipping', 'moving');

        // Apply rotation to match the arch layout
        swapCard.style.transform = `rotate(${sourceRotation}deg)`;

        // Set card content
        swapCardFront.className = 'swap-card-front';
        if (cardData.rank === '★') {
            swapCardFront.classList.add('joker');
            const jokerIcon = cardData.suit === 'hearts' ? '🐉' : '👹';
            swapCardFront.innerHTML = `<span class="joker-icon">${jokerIcon}</span><span class="joker-label">Joker</span>`;
        } else {
            swapCardFront.classList.add(cardData.suit === 'hearts' || cardData.suit === 'diamonds' ? 'red' : 'black');
            const suitSymbol = { hearts: '♥', diamonds: '♦', clubs: '♣', spades: '♠' }[cardData.suit];
            swapCardFront.innerHTML = `${cardData.rank}<br>${suitSymbol}`;
        }

        cardEl.classList.add('swap-out');
        this.swapAnimation.classList.remove('hidden');

        // Use centralized timing
        const preFlip = window.TIMING?.pause?.beforeFlip || 50;
        const flipDuration = window.TIMING?.card?.flip || 540;

        setTimeout(() => {
            swapCard.classList.add('flipping');
            this.playSound('flip');
        }, preFlip);

        setTimeout(() => {
            this.swapAnimation.classList.add('hidden');
            swapCard.classList.remove('flipping');
            swapCard.style.transform = '';
            cardEl.classList.remove('swap-out');
            this.animatingPositions.delete(key);
        }, preFlip + flipDuration);
    }

    handleCardClick(position) {
        const myData = this.getMyPlayerData();
        if (!myData) return;

        const card = myData.cards[position];

        // Check for flip-as-action: can flip face-down card instead of drawing
        const canFlipAsAction = this.gameState.flip_as_action &&
                                this.isMyTurn() &&
                                !this.drawnCard &&
                                !this.gameState.has_drawn_card &&
                                !card.face_up &&
                                !this.gameState.waiting_for_initial_flip;
        if (canFlipAsAction) {
            this.playSound('flip');
            this.fireLocalFlipAnimation(position, card);
            this.send({ type: 'flip_as_action', position });
            this.hideToast();
            return;
        }

        // Check if action is allowed - if not, play reject sound
        const canAct = this.gameState.waiting_for_initial_flip ||
                       this.drawnCard ||
                       this.waitingForFlip;
        if (!canAct) {
            this.playSound('reject');
            return;
        }

        // Initial flip phase
        if (this.gameState.waiting_for_initial_flip) {
            if (card.face_up) return;
            // Use Set to prevent duplicates - check both tracking mechanisms
            if (this.locallyFlippedCards.has(position)) return;
            if (this.selectedCards.includes(position)) return;

            const requiredFlips = this.gameState.initial_flips || 2;

            // Track locally and animate immediately
            this.locallyFlippedCards.add(position);
            this.selectedCards.push(position);

            // Fire flip animation (non-blocking)
            this.fireLocalFlipAnimation(position, card);

            // Re-render to show flipped state
            this.renderGame();

            // Use Set to ensure unique positions when sending to server
            const uniquePositions = [...new Set(this.selectedCards)];
            if (uniquePositions.length === requiredFlips) {
                this.send({ type: 'flip_initial', positions: uniquePositions });
                this.selectedCards = [];
                // Note: locallyFlippedCards is cleared when server confirms (in game_state handler)
                this.hideToast();
            } else {
                const remaining = requiredFlips - uniquePositions.length;
                this.showToast(`Select ${remaining} more card${remaining > 1 ? 's' : ''} to flip`, '', 5000);
            }
            return;
        }

        // Swap with drawn card
        if (this.drawnCard) {
            this.animateSwap(position);
            this.hideToast();
            return;
        }

        // Flip after discarding from deck (flip_on_discard variant)
        if (this.waitingForFlip && !card.face_up) {
            // Animate immediately, then send to server
            this.fireLocalFlipAnimation(position, card);
            this.flipCard(position);
            this.hideToast();
            return;
        }
    }

    nextRound() {
        this.clearNextHoleCountdown();
        this.send({ type: 'next_round' });
        this.gameButtons.classList.add('hidden');
        this.nextRoundBtn.classList.remove('waiting');
    }

    newGame() {
        this.leaveRoom();
    }

    leaveGame() {
        if (this.isHost) {
            // Host ending game affects everyone
            if (confirm('End game for all players?')) {
                this.send({ type: 'end_game' });
            }
        } else {
            // Regular player just leaves
            if (confirm('Leave this game?')) {
                this.send({ type: 'leave_game' });
                this.ws.close();
                this.showLobby();
            }
        }
    }

    // UI Helpers
    showScreen(screen) {
        this.lobbyScreen.classList.remove('active');
        this.waitingScreen.classList.remove('active');
        this.gameScreen.classList.remove('active');
        if (this.rulesScreen) {
            this.rulesScreen.classList.remove('active');
        }
        screen.classList.add('active');

        // Handle auth bar visibility - hide global bar during game, show in-game controls instead
        const isGameScreen = screen === this.gameScreen;
        const user = this.auth?.user;

        if (isGameScreen && user) {
            // Hide global auth bar, show in-game auth controls
            this.authBar?.classList.add('hidden');
            this.gameUsername.textContent = user.username;
            this.gameUsername.classList.remove('hidden');
            this.gameLogoutBtn.classList.remove('hidden');
        } else {
            // Show global auth bar (if logged in), hide in-game auth controls
            if (user) {
                this.authBar?.classList.remove('hidden');
            }
            this.gameUsername.classList.add('hidden');
            this.gameLogoutBtn.classList.add('hidden');
        }
    }

    showLobby() {
        this.showScreen(this.lobbyScreen);
        this.lobbyError.textContent = '';
        this.roomCode = null;
        this.playerId = null;
        this.isHost = false;
        this.gameState = null;
        this.previousState = null;
    }

    showWaitingRoom() {
        this.showScreen(this.waitingScreen);
        this.displayRoomCode.textContent = this.roomCode;

        if (this.isHost) {
            this.hostSettings.classList.remove('hidden');
            this.cpuControlsSection.classList.remove('hidden');
            this.waitingMessage.classList.add('hidden');
        } else {
            this.hostSettings.classList.add('hidden');
            this.cpuControlsSection.classList.add('hidden');
            this.waitingMessage.classList.remove('hidden');
        }
    }

    showGameScreen() {
        this.showScreen(this.gameScreen);
        this.gameButtons.classList.add('hidden');
        this.drawnCard = null;
        this.selectedCards = [];
        this.waitingForFlip = false;
        this.previousState = null;
        // Update leave button text based on role
        this.leaveGameBtn.textContent = this.isHost ? 'End Game' : 'Leave';
        // Update active rules bar
        this.updateActiveRulesBar();
    }

    updateActiveRulesBar() {
        if (!this.gameState) {
            this.activeRulesBar.classList.add('hidden');
            return;
        }

        const rules = this.gameState.active_rules || [];
        if (rules.length === 0) {
            // Show "Standard Rules" when no variants selected
            this.activeRulesList.innerHTML = '<span class="rule-tag standard">Standard</span>';
        } else if (rules.length <= 2) {
            // Show all rules if 2 or fewer
            this.activeRulesList.innerHTML = rules
                .map(rule => `<span class="rule-tag">${rule}</span>`)
                .join('');
        } else {
            // Show first 2 rules + "+N more" with tooltip
            const displayed = rules.slice(0, 2);
            const hidden = rules.slice(2);
            const moreCount = hidden.length;
            const tooltip = hidden.join(', ');

            this.activeRulesList.innerHTML = displayed
                .map(rule => `<span class="rule-tag">${rule}</span>`)
                .join('') +
                `<span class="rule-tag rule-more" title="${tooltip}">+${moreCount} more</span>`;
        }
        this.activeRulesBar.classList.remove('hidden');
    }

    showError(message) {
        this.lobbyError.textContent = message;
        this.playSound('reject');
        console.error('Game error:', message);
    }

    updatePlayersList(players) {
        this.playersList.innerHTML = '';
        players.forEach(player => {
            const li = document.createElement('li');
            let badges = '';
            if (player.is_host) badges += '<span class="host-badge">HOST</span>';
            if (player.is_cpu) badges += '<span class="cpu-badge">CPU</span>';

            li.innerHTML = `
                <span>${player.name}</span>
                <span>${badges}</span>
            `;
            if (player.id === this.playerId) {
                li.style.background = 'rgba(244, 164, 96, 0.3)';
            }
            this.playersList.appendChild(li);

            if (player.id === this.playerId && player.is_host) {
                this.isHost = true;
                this.hostSettings.classList.remove('hidden');
                this.cpuControlsSection.classList.remove('hidden');
                this.waitingMessage.classList.add('hidden');
            }
        });

        // Auto-select 2 decks when reaching 4+ players (host only)
        const prevCount = this.currentPlayers ? this.currentPlayers.length : 0;
        if (this.isHost && prevCount < 4 && players.length >= 4) {
            this.numDecksSelect.value = '2';
        }

        // Update deck recommendation visibility
        this.updateDeckRecommendation(players.length);
    }

    updateDeckRecommendation(playerCount) {
        if (!this.isHost || !this.deckRecommendation) return;

        const decks = parseInt(this.numDecksSelect.value);
        // Show recommendation if 4+ players and only 1 deck selected
        if (playerCount >= 4 && decks < 2) {
            this.deckRecommendation.classList.remove('hidden');
        } else {
            this.deckRecommendation.classList.add('hidden');
        }
    }

    isMyTurn() {
        return this.gameState && this.gameState.current_player_id === this.playerId;
    }

    getMyPlayerData() {
        if (!this.gameState) return null;
        return this.gameState.players.find(p => p.id === this.playerId);
    }

    setStatus(message, type = '') {
        this.statusMessage.textContent = message;
        this.statusMessage.className = 'status-message' + (type ? ' ' + type : '');
    }

    // Show CPU action announcement in status bar
    showCpuAction(playerName, action, card = null) {
        const suitSymbol = card ? this.getSuitSymbol(card.suit) : '';
        const messages = {
            'draw-deck': `${playerName} draws from deck`,
            'draw-discard': card ? `${playerName} takes ${card.rank}${suitSymbol}` : `${playerName} takes from discard`,
            'swap': `${playerName} swaps a card`,
            'discard': card ? `${playerName} discards ${card.rank}${suitSymbol}` : `${playerName} discards`,
        };
        const message = messages[action];
        if (message) {
            this.setStatus(message, 'cpu-action');
        }
    }

    // Update CPU considering visual state on discard pile
    updateCpuConsideringState() {
        if (!this.gameState || !this.discard) return;

        const currentPlayer = this.gameState.players.find(p => p.id === this.gameState.current_player_id);
        const isCpuTurn = currentPlayer && currentPlayer.is_cpu;
        const hasNotDrawn = !this.gameState.has_drawn_card;

        if (isCpuTurn && hasNotDrawn) {
            this.discard.classList.add('cpu-considering');
        } else {
            this.discard.classList.remove('cpu-considering');
        }
    }

    showToast(message, type = '', duration = 2500) {
        // For compatibility - just set the status message
        this.setStatus(message, type);
    }

    hideToast() {
        // Restore default status based on game state
        this.updateStatusFromGameState();
    }

    updateStatusFromGameState() {
        if (!this.gameState) {
            this.setStatus('');
            this.finalTurnBadge.classList.add('hidden');
            return;
        }

        // Check for round/game over states
        if (this.gameState.phase === 'round_over') {
            this.setStatus('Hole Complete!', 'round-over');
            this.finalTurnBadge.classList.add('hidden');
            return;
        }
        if (this.gameState.phase === 'game_over') {
            this.setStatus('Game Over!', 'game-over');
            this.finalTurnBadge.classList.add('hidden');
            return;
        }

        const isFinalTurn = this.gameState.phase === 'final_turn';
        const currentPlayer = this.gameState.players.find(p => p.id === this.gameState.current_player_id);

        // Show/hide final turn badge separately
        if (isFinalTurn) {
            this.finalTurnBadge.classList.remove('hidden');
        } else {
            this.finalTurnBadge.classList.add('hidden');
        }

        if (currentPlayer && currentPlayer.id !== this.playerId) {
            this.setStatus(`${currentPlayer.name}'s turn`);
        } else if (this.isMyTurn()) {
            if (!this.drawnCard && !this.gameState.has_drawn_card) {
                // Build status message based on available actions
                let options = ['draw'];
                if (this.gameState.flip_as_action) options.push('flip');
                // Check knock early eligibility
                const myData = this.gameState.players.find(p => p.id === this.playerId);
                const faceDownCount = myData ? myData.cards.filter(c => !c.face_up).length : 0;
                if (this.gameState.knock_early && faceDownCount >= 1 && faceDownCount <= 2) {
                    options.push('knock');
                }
                if (options.length === 1) {
                    this.setStatus('Your turn - draw a card', 'your-turn');
                } else {
                    this.setStatus(`Your turn - ${options.join('/')}`, 'your-turn');
                }
            } else {
                this.setStatus('Your turn - draw a card', 'your-turn');
            }
        } else {
            this.setStatus('');
        }
    }

    showDrawnCard() {
        // Show drawn card floating over the draw pile (deck), regardless of source
        const card = this.drawnCard;
        this.displayHeldCard(card, true);
    }

    // Display held card floating above and between deck and discard - for any player
    // isLocalPlayerHolding: true if this is the local player's card (shows discard button, pulse glow)
    displayHeldCard(card, isLocalPlayerHolding) {
        if (!card) {
            this.hideDrawnCard();
            return;
        }

        // Set up the floating held card display
        this.heldCardFloating.className = 'card card-front held-card-floating';
        // Clear any inline styles left over from swoop animations
        this.heldCardFloating.style.cssText = '';

        // Position centered above and between deck and discard
        const deckRect = this.deck.getBoundingClientRect();
        const discardRect = this.discard.getBoundingClientRect();

        // Calculate center point between deck and discard
        const centerX = (deckRect.left + deckRect.right + discardRect.left + discardRect.right) / 4;
        const cardWidth = deckRect.width;
        const cardHeight = deckRect.height;

        // Position card centered, overlapping both piles (lower than before)
        const overlapOffset = cardHeight * 0.35; // More overlap = lower position
        const cardLeft = centerX - cardWidth / 2;
        const cardTop = deckRect.top - overlapOffset;
        this.heldCardFloating.style.left = `${cardLeft}px`;
        this.heldCardFloating.style.top = `${cardTop}px`;
        this.heldCardFloating.style.width = `${cardWidth}px`;
        this.heldCardFloating.style.height = `${cardHeight}px`;

        // Position discard button attached to right side of held card
        const scaledWidth = cardWidth * 1.15; // Account for scale transform
        const scaledHeight = cardHeight * 1.15;
        const buttonLeft = cardLeft + scaledWidth / 2 + cardWidth / 2; // Right edge of scaled card (no gap)
        const buttonTop = cardTop + (scaledHeight - cardHeight) / 2 + cardHeight * 0.3; // Vertically centered on card
        this.discardBtn.style.left = `${buttonLeft}px`;
        this.discardBtn.style.top = `${buttonTop}px`;

        if (card.rank === '★') {
            this.heldCardFloating.classList.add('joker');
            const jokerIcon = card.suit === 'hearts' ? '🐉' : '👹';
            this.heldCardFloatingContent.innerHTML = `<span class="joker-icon">${jokerIcon}</span><span class="joker-label">Joker</span>`;
        } else {
            if (this.isRedSuit(card.suit)) {
                this.heldCardFloating.classList.add('red');
            } else {
                this.heldCardFloating.classList.add('black');
            }
            this.heldCardFloatingContent.innerHTML = `${card.rank}<br>${this.getSuitSymbol(card.suit)}`;
        }

        // Show the floating card
        this.heldCardFloating.classList.remove('hidden');

        // Add pulse glow if it's local player's turn to act on the card
        if (isLocalPlayerHolding) {
            this.heldCardFloating.classList.add('your-turn-pulse');
            this.discardBtn.classList.remove('hidden');
        } else {
            this.heldCardFloating.classList.remove('your-turn-pulse');
            this.discardBtn.classList.add('hidden');
        }
    }

    hideDrawnCard() {
        // Hide the floating held card
        this.heldCardFloating.classList.add('hidden');
        this.heldCardFloating.classList.remove('your-turn-pulse');
        // Clear any inline styles from animations
        this.heldCardFloating.style.cssText = '';
        this.discardBtn.classList.add('hidden');
        // Clear button positioning
        this.discardBtn.style.left = '';
        this.discardBtn.style.top = '';
    }

    isRedSuit(suit) {
        return suit === 'hearts' || suit === 'diamonds';
    }

    calculateShowingScore(cards) {
        if (!cards || cards.length !== 6) return 0;

        // Use card values from server (includes house rules) or defaults
        const cardValues = this.gameState?.card_values || {
            'A': 1, '2': -2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7,
            '8': 8, '9': 9, '10': 10, 'J': 10, 'Q': 10, 'K': 0, '★': -2
        };

        const getCardValue = (card) => {
            if (!card.face_up) return 0;
            return cardValues[card.rank] ?? 0;
        };

        // Check for column pairs (cards in same column cancel out if matching)
        let total = 0;
        for (let col = 0; col < 3; col++) {
            const topCard = cards[col];
            const bottomCard = cards[col + 3];

            const topUp = topCard.face_up;
            const bottomUp = bottomCard.face_up;

            // If both face up and matching rank, they cancel (score 0)
            if (topUp && bottomUp && topCard.rank === bottomCard.rank) {
                // Matching pair = 0 points for both
                continue;
            }

            // Otherwise add individual values
            total += getCardValue(topCard);
            total += getCardValue(bottomCard);
        }

        return total;
    }

    getSuitSymbol(suit) {
        const symbols = {
            hearts: '♥',
            diamonds: '♦',
            clubs: '♣',
            spades: '♠'
        };
        return symbols[suit] || '';
    }

    renderCardContent(card) {
        if (!card || !card.face_up) return '';
        // Jokers - use suit to determine icon (hearts = dragon, spades = oni)
        if (card.rank === '★') {
            const jokerIcon = card.suit === 'hearts' ? '🐉' : '👹';
            return `<span class="joker-icon">${jokerIcon}</span><span class="joker-label">Joker</span>`;
        }
        return `${card.rank}<br>${this.getSuitSymbol(card.suit)}`;
    }

    renderGame() {
        if (!this.gameState) return;

        // Update CPU considering visual state
        this.updateCpuConsideringState();

        // Update header
        this.currentRoundSpan.textContent = this.gameState.current_round;
        this.totalRoundsSpan.textContent = this.gameState.total_rounds;

        // Show/hide final turn badge
        const isFinalTurn = this.gameState.phase === 'final_turn';
        if (isFinalTurn) {
            this.finalTurnBadge.classList.remove('hidden');
        } else {
            this.finalTurnBadge.classList.add('hidden');
        }

        // Toggle not-my-turn class to disable hover effects when it's not player's turn
        const isMyTurn = this.isMyTurn();
        this.gameScreen.classList.toggle('not-my-turn', !isMyTurn);

        // Update status message (handled by specific actions, but set default here)
        const currentPlayer = this.gameState.players.find(p => p.id === this.gameState.current_player_id);
        if (currentPlayer && currentPlayer.id !== this.playerId) {
            this.setStatus(`${currentPlayer.name}'s turn`);
        }

        // Update player header (name + score like opponents)
        const me = this.gameState.players.find(p => p.id === this.playerId);
        if (me) {
            // Calculate visible score from face-up cards
            const showingScore = this.calculateShowingScore(me.cards);
            this.yourScore.textContent = showingScore;

            // Check if player won the round
            const isRoundWinner = this.roundWinnerNames.has(me.name);
            this.playerArea.classList.toggle('round-winner', isRoundWinner);

            // Update player name in header (truncate if needed)
            const displayName = me.name.length > 12 ? me.name.substring(0, 11) + '…' : me.name;
            const checkmark = me.all_face_up ? ' ✓' : '';
            // Update player name span with crown if winner
            const playerNameSpan = this.playerHeader.querySelector('.player-name');
            const crownHtml = isRoundWinner ? '<span class="winner-crown">👑</span>' : '';
            playerNameSpan.innerHTML = crownHtml + displayName + checkmark;
        }

        // Update discard pile
        // Check if ANY player is holding a card (local or remote/CPU)
        const anyPlayerHolding = this.drawnCard || this.gameState.drawn_card;
        if (anyPlayerHolding) {
            // Someone is holding a drawn card - show discard pile as greyed/disabled
            // If drawn from discard, show what's underneath (new discard_top or empty)
            // If drawn from deck, show current discard_top greyed
            this.discard.classList.add('picked-up');
            this.discard.classList.remove('holding');

            if (this.gameState.discard_top) {
                const discardCard = this.gameState.discard_top;
                this.discard.classList.add('has-card', 'card-front');
                this.discard.classList.remove('card-back', 'red', 'black', 'joker');

                if (discardCard.rank === '★') {
                    this.discard.classList.add('joker');
                } else if (this.isRedSuit(discardCard.suit)) {
                    this.discard.classList.add('red');
                } else {
                    this.discard.classList.add('black');
                }
                this.discardContent.innerHTML = this.renderCardContent(discardCard);
            } else {
                // No card underneath - show empty
                this.discard.classList.remove('has-card', 'card-front', 'red', 'black', 'joker');
                this.discardContent.innerHTML = '';
            }
        } else {
            // Not holding - show normal discard pile
            this.discard.classList.remove('picked-up');

            // Skip discard update during opponent swap animation - animation handles the visual
            if (this.opponentSwapAnimation) {
                // Don't update discard content; animation overlay shows the swap
            } else if (this.gameState.discard_top) {
                const discardCard = this.gameState.discard_top;
                const cardKey = `${discardCard.rank}-${discardCard.suit}`;

                // Only animate discard flip during active gameplay, not at round/game end
                const isActivePlay = this.gameState.phase !== 'round_over' &&
                                     this.gameState.phase !== 'game_over';
                const shouldAnimate = isActivePlay && this.lastDiscardKey &&
                                      this.lastDiscardKey !== cardKey && !this.skipNextDiscardFlip;

                this.skipNextDiscardFlip = false;
                this.lastDiscardKey = cardKey;

                // Set card content and styling FIRST (before any animation)
                this.discard.classList.add('has-card', 'card-front');
                this.discard.classList.remove('card-back', 'red', 'black', 'joker', 'holding');

                if (discardCard.rank === '★') {
                    this.discard.classList.add('joker');
                } else if (this.isRedSuit(discardCard.suit)) {
                    this.discard.classList.add('red');
                } else {
                    this.discard.classList.add('black');
                }
                this.discardContent.innerHTML = this.renderCardContent(discardCard);

                // THEN animate if needed (content is already set, so no blank flash)
                if (shouldAnimate) {
                    // Remove any existing animation first to allow re-trigger
                    this.discard.classList.remove('card-flip-in');
                    void this.discard.offsetWidth; // Force reflow
                    this.discard.classList.add('card-flip-in');
                    const flipInDuration = window.TIMING?.feedback?.cardFlipIn || 560;
                    setTimeout(() => this.discard.classList.remove('card-flip-in'), flipInDuration);
                }
            } else {
                this.discard.classList.remove('has-card', 'card-front', 'red', 'black', 'joker', 'holding');
                this.discardContent.innerHTML = '';
                this.lastDiscardKey = null;
            }
            this.discardBtn.classList.add('hidden');
        }

        // Show held card for ANY player who has drawn (consistent visual regardless of whose turn)
        // Local player uses this.drawnCard, others use gameState.drawn_card
        if (this.drawnCard) {
            // Local player is holding - show with pulse and discard button
            this.displayHeldCard(this.drawnCard, true);
        } else if (this.gameState.drawn_card && this.gameState.drawn_player_id) {
            // Another player is holding - show without pulse/button
            const isLocalPlayer = this.gameState.drawn_player_id === this.playerId;
            this.displayHeldCard(this.gameState.drawn_card, isLocalPlayer);
        } else {
            // No one holding a card
            this.hideDrawnCard();
        }

        // Update deck/discard clickability and visual state
        const hasDrawn = this.drawnCard || this.gameState.has_drawn_card;
        const canDraw = this.isMyTurn() && !hasDrawn && !this.gameState.waiting_for_initial_flip;

        // Pulse the deck area when it's player's turn to draw
        this.deckArea.classList.toggle('your-turn-to-draw', canDraw);

        this.deck.classList.toggle('clickable', canDraw);
        // Show disabled on deck when any player has drawn (consistent dimmed look)
        this.deck.classList.toggle('disabled', hasDrawn);

        this.discard.classList.toggle('clickable', canDraw && this.gameState.discard_top);
        // Disabled state handled by picked-up class when anyone is holding

        // Render opponents in a single row
        const opponents = this.gameState.players.filter(p => p.id !== this.playerId);

        this.opponentsRow.innerHTML = '';

        // Don't highlight current player during round/game over
        const isPlaying = this.gameState.phase !== 'round_over' && this.gameState.phase !== 'game_over';

        opponents.forEach((player) => {
            const div = document.createElement('div');
            div.className = 'opponent-area';
            if (isPlaying && player.id === this.gameState.current_player_id) {
                div.classList.add('current-turn');
            }

            const isRoundWinner = this.roundWinnerNames.has(player.name);
            if (isRoundWinner) {
                div.classList.add('round-winner');
            }

            const displayName = player.name.length > 12 ? player.name.substring(0, 11) + '…' : player.name;
            const showingScore = this.calculateShowingScore(player.cards);
            const crownHtml = isRoundWinner ? '<span class="winner-crown">👑</span>' : '';

            div.innerHTML = `
                <h4><span class="opponent-name">${crownHtml}${displayName}${player.all_face_up ? ' ✓' : ''}</span><span class="opponent-showing">${showingScore}</span></h4>
                <div class="card-grid">
                    ${player.cards.map(card => this.renderCard(card, false, false)).join('')}
                </div>
            `;

            this.opponentsRow.appendChild(div);
        });

        // Render player's cards
        const myData = this.getMyPlayerData();
        if (myData) {
            this.playerCards.innerHTML = '';

            myData.cards.forEach((card, index) => {
                // Check if this card was locally flipped (immediate feedback)
                const isLocallyFlipped = this.locallyFlippedCards.has(index);

                // Create a display card that shows face-up if locally flipped
                const displayCard = isLocallyFlipped
                    ? { ...card, face_up: true }
                    : card;

                // Check if clickable during initial flip
                const isInitialFlipClickable = this.gameState.waiting_for_initial_flip && !card.face_up && !isLocallyFlipped;

                const isClickable = (
                    isInitialFlipClickable ||
                    (this.drawnCard) ||
                    (this.waitingForFlip && !card.face_up)
                );
                const isSelected = this.selectedCards.includes(index);

                const cardEl = document.createElement('div');
                cardEl.innerHTML = this.renderCard(displayCard, isClickable, isSelected);

                // Add pulse animation during initial flip phase
                if (isInitialFlipClickable) {
                    cardEl.firstChild.classList.add('initial-flip-pulse');
                }

                cardEl.firstChild.addEventListener('click', () => this.handleCardClick(index));
                this.playerCards.appendChild(cardEl.firstChild);
            });
        }

        // Show flip prompt for initial flip
        // Show flip prompt during initial flip phase
        if (this.gameState.waiting_for_initial_flip) {
            const requiredFlips = this.gameState.initial_flips || 2;
            const flippedCount = this.locallyFlippedCards.size;
            const remaining = requiredFlips - flippedCount;
            if (remaining > 0) {
                this.setStatus(`Select ${remaining} card${remaining > 1 ? 's' : ''} to flip`, 'your-turn');
            }
        }

        // Disable discard button if can't discard (must_swap_discard rule)
        if (this.drawnCard && !this.gameState.can_discard) {
            this.discardBtn.disabled = true;
            this.discardBtn.classList.add('disabled');
        } else {
            this.discardBtn.disabled = false;
            this.discardBtn.classList.remove('disabled');
        }

        // Show/hide skip flip button (only when flip is optional in endgame mode)
        if (this.waitingForFlip && this.flipIsOptional) {
            this.skipFlipBtn.classList.remove('hidden');
        } else {
            this.skipFlipBtn.classList.add('hidden');
        }

        // Show/hide knock early button (when knock_early rule is enabled)
        // Conditions: rule enabled, my turn, no drawn card, have 1-2 face-down cards
        const canKnockEarly = this.gameState.knock_early &&
                              this.isMyTurn() &&
                              !this.drawnCard &&
                              !this.gameState.has_drawn_card &&
                              !this.gameState.waiting_for_initial_flip;
        if (canKnockEarly) {
            // Count face-down cards for current player
            const myData = this.gameState.players.find(p => p.id === this.playerId);
            const faceDownCount = myData ? myData.cards.filter(c => !c.face_up).length : 0;
            if (faceDownCount >= 1 && faceDownCount <= 2) {
                this.knockEarlyBtn.classList.remove('hidden');
            } else {
                this.knockEarlyBtn.classList.add('hidden');
            }
        } else {
            this.knockEarlyBtn.classList.add('hidden');
        }

        // Update scoreboard panel
        this.updateScorePanel();
    }

    updateScorePanel() {
        if (!this.gameState) return;

        // Update standings (left panel)
        this.updateStandings();

        // Skip score table update during round_over/game_over - showScoreboard handles these
        if (this.gameState.phase === 'round_over' || this.gameState.phase === 'game_over') {
            return;
        }

        // Update score table (right panel)
        this.scoreTable.innerHTML = '';

        this.gameState.players.forEach(player => {
            const tr = document.createElement('tr');

            // Highlight current player (but not during round/game over)
            const isPlaying = this.gameState.phase !== 'round_over' && this.gameState.phase !== 'game_over';
            if (isPlaying && player.id === this.gameState.current_player_id) {
                tr.classList.add('current-player');
            }

            // Truncate long names
            const displayName = player.name.length > 12
                ? player.name.substring(0, 11) + '…'
                : player.name;

            const roundScore = player.score !== null ? player.score : '-';
            const roundsWon = player.rounds_won || 0;

            tr.innerHTML = `
                <td>${displayName}</td>
                <td>${roundScore}</td>
                <td>${player.total_score}</td>
                <td>${roundsWon}</td>
            `;
            this.scoreTable.appendChild(tr);
        });
    }

    updateStandings() {
        if (!this.gameState || !this.standingsList) return;

        // Sort by total points (lowest wins) - top 4
        const byPoints = [...this.gameState.players].sort((a, b) => a.total_score - b.total_score).slice(0, 4);
        // Sort by holes won (most wins) - top 4
        const byHoles = [...this.gameState.players].sort((a, b) => b.rounds_won - a.rounds_won).slice(0, 4);

        // Build points ranking
        let pointsRank = 0;
        let prevPoints = null;
        const pointsHtml = byPoints.map((p, i) => {
            if (p.total_score !== prevPoints) {
                pointsRank = i;
                prevPoints = p.total_score;
            }
            const medal = pointsRank === 0 ? '🥇' : pointsRank === 1 ? '🥈' : pointsRank === 2 ? '🥉' : '4.';
            const name = p.name.length > 8 ? p.name.substring(0, 7) + '…' : p.name;
            return `<div class="rank-row ${pointsRank === 0 ? 'leader' : ''}"><span class="rank-pos">${medal}</span><span class="rank-name">${name}</span><span class="rank-val">${p.total_score} pts</span></div>`;
        }).join('');

        // Build holes won ranking
        let holesRank = 0;
        let prevHoles = null;
        const holesHtml = byHoles.map((p, i) => {
            if (p.rounds_won !== prevHoles) {
                holesRank = i;
                prevHoles = p.rounds_won;
            }
            const medal = p.rounds_won === 0 ? '-' :
                          holesRank === 0 ? '🥇' : holesRank === 1 ? '🥈' : holesRank === 2 ? '🥉' : '4.';
            const name = p.name.length > 8 ? p.name.substring(0, 7) + '…' : p.name;
            return `<div class="rank-row ${holesRank === 0 && p.rounds_won > 0 ? 'leader' : ''}"><span class="rank-pos">${medal}</span><span class="rank-name">${name}</span><span class="rank-val">${p.rounds_won} wins</span></div>`;
        }).join('');

        this.standingsList.innerHTML = `
            <div class="standings-section">
                <div class="standings-title">By Score</div>
                ${pointsHtml}
            </div>
            <div class="standings-section">
                <div class="standings-title">By Holes</div>
                ${holesHtml}
            </div>
        `;
    }

    renderCard(card, clickable, selected) {
        let classes = 'card';
        let content = '';

        if (card.face_up) {
            classes += ' card-front';
            if (card.rank === '★') {
                classes += ' joker';
            } else if (this.isRedSuit(card.suit)) {
                classes += ' red';
            } else {
                classes += ' black';
            }
            content = this.renderCardContent(card);
        } else {
            classes += ' card-back';
        }

        if (clickable) classes += ' clickable';
        if (selected) classes += ' selected';

        return `<div class="${classes}">${content}</div>`;
    }

    showScoreboard(scores, isFinal, rankings) {
        this.scoreTable.innerHTML = '';

        // Clear the final turn badge and status message
        this.finalTurnBadge.classList.add('hidden');
        if (isFinal) {
            this.setStatus('Game Over!');
        } else {
            this.setStatus('Hole complete');
        }

        // Find round winner(s) - lowest round score (not total)
        const roundScores = scores.map(s => s.score);
        const minRoundScore = Math.min(...roundScores);
        this.roundWinnerNames = new Set(
            scores.filter(s => s.score === minRoundScore).map(s => s.name)
        );

        // Re-render to show winner highlights
        this.renderGame();

        const minScore = Math.min(...scores.map(s => s.total || s.score || 0));

        scores.forEach(score => {
            const tr = document.createElement('tr');
            const total = score.total !== undefined ? score.total : score.score;
            const roundScore = score.score !== undefined ? score.score : '-';
            const roundsWon = score.rounds_won || 0;

            // Truncate long names
            const displayName = score.name.length > 12
                ? score.name.substring(0, 11) + '…'
                : score.name;

            if (total === minScore) {
                tr.classList.add('winner');
            }

            tr.innerHTML = `
                <td>${displayName}</td>
                <td>${roundScore}</td>
                <td>${total}</td>
                <td>${roundsWon}</td>
            `;
            this.scoreTable.appendChild(tr);
        });

        // Show rankings announcement only for final results
        const existingAnnouncement = document.getElementById('rankings-announcement');
        if (existingAnnouncement) existingAnnouncement.remove();

        if (isFinal) {
            // Show big final results modal instead of side panel stuff
            this.showFinalResultsModal(rankings, scores);
            return;
        }

        // Show game buttons
        this.gameButtons.classList.remove('hidden');
        this.newGameBtn.classList.add('hidden');
        this.nextRoundBtn.classList.remove('hidden');

        // Start countdown for next hole
        this.startNextHoleCountdown();
    }

    startNextHoleCountdown() {
        // Clear any existing countdown
        if (this.nextHoleCountdownInterval) {
            clearInterval(this.nextHoleCountdownInterval);
        }

        const COUNTDOWN_SECONDS = 15;
        let remaining = COUNTDOWN_SECONDS;

        const updateButton = () => {
            if (this.isHost) {
                this.nextRoundBtn.textContent = `Next Hole (${remaining}s)`;
                this.nextRoundBtn.disabled = false;
            } else {
                this.nextRoundBtn.textContent = `Next hole in ${remaining}s...`;
                this.nextRoundBtn.disabled = true;
                this.nextRoundBtn.classList.add('waiting');
            }
        };

        updateButton();

        this.nextHoleCountdownInterval = setInterval(() => {
            remaining--;

            if (remaining <= 0) {
                clearInterval(this.nextHoleCountdownInterval);
                this.nextHoleCountdownInterval = null;

                // Auto-advance if host
                if (this.isHost) {
                    this.nextRound();
                } else {
                    this.nextRoundBtn.textContent = 'Waiting for host...';
                }
            } else {
                updateButton();
            }
        }, 1000);
    }

    clearNextHoleCountdown() {
        if (this.nextHoleCountdownInterval) {
            clearInterval(this.nextHoleCountdownInterval);
            this.nextHoleCountdownInterval = null;
        }
    }

    showRankingsAnnouncement(rankings, isFinal) {
        // Remove existing announcement if any
        const existing = document.getElementById('rankings-announcement');
        if (existing) existing.remove();
        const existingVictory = document.getElementById('double-victory-banner');
        if (existingVictory) existingVictory.remove();

        if (!rankings) return;

        const announcement = document.createElement('div');
        announcement.id = 'rankings-announcement';
        announcement.className = 'rankings-announcement';

        const title = isFinal ? 'Final Results' : 'Current Standings';

        // Check for double victory (same player leads both categories) - only at game end
        const pointsLeader = rankings.by_points[0];
        const holesLeader = rankings.by_holes_won[0];
        const isDoubleVictory = isFinal && pointsLeader && holesLeader &&
            pointsLeader.name === holesLeader.name &&
            holesLeader.rounds_won > 0;

        // Build points ranking (lowest wins) with tie handling
        let pointsRank = 0;
        let prevPoints = null;
        const pointsHtml = rankings.by_points.map((p, i) => {
            if (p.total !== prevPoints) {
                pointsRank = i;
                prevPoints = p.total;
            }
            const medal = pointsRank === 0 ? '🥇' : pointsRank === 1 ? '🥈' : pointsRank === 2 ? '🥉' : `${pointsRank + 1}.`;
            const name = p.name.length > 12 ? p.name.substring(0, 11) + '…' : p.name;
            return `<div class="rank-row ${pointsRank === 0 ? 'leader' : ''}"><span class="rank-pos">${medal}</span><span class="rank-name">${name}</span><span class="rank-val">${p.total} pts</span></div>`;
        }).join('');

        // Build holes won ranking (most wins) with tie handling
        let holesRank = 0;
        let prevHoles = null;
        const holesHtml = rankings.by_holes_won.map((p, i) => {
            if (p.rounds_won !== prevHoles) {
                holesRank = i;
                prevHoles = p.rounds_won;
            }
            // No medal for 0 wins
            const medal = p.rounds_won === 0 ? '-' :
                          holesRank === 0 ? '🥇' : holesRank === 1 ? '🥈' : holesRank === 2 ? '🥉' : `${holesRank + 1}.`;
            const name = p.name.length > 12 ? p.name.substring(0, 11) + '…' : p.name;
            return `<div class="rank-row ${holesRank === 0 && p.rounds_won > 0 ? 'leader' : ''}"><span class="rank-pos">${medal}</span><span class="rank-name">${name}</span><span class="rank-val">${p.rounds_won} wins</span></div>`;
        }).join('');

        // If double victory, show banner above the left panel (standings)
        if (isDoubleVictory) {
            const victoryBanner = document.createElement('div');
            victoryBanner.id = 'double-victory-banner';
            victoryBanner.className = 'double-victory';
            victoryBanner.textContent = `DOUBLE VICTORY! ${pointsLeader.name}`;
            const standingsPanel = document.getElementById('standings-panel');
            if (standingsPanel) {
                standingsPanel.insertBefore(victoryBanner, standingsPanel.firstChild);
            }
        }

        announcement.innerHTML = `
            <h3>${title}</h3>
            <div class="rankings-columns">
                <div class="ranking-section">
                    <h4>Points (Low Wins)</h4>
                    ${pointsHtml}
                </div>
                <div class="ranking-section">
                    <h4>Holes Won</h4>
                    ${holesHtml}
                </div>
            </div>
        `;

        // Insert before the scoreboard
        this.scoreboard.insertBefore(announcement, this.scoreboard.firstChild);
    }

    showFinalResultsModal(rankings, scores) {
        // Hide side panels
        const standingsPanel = document.getElementById('standings-panel');
        const scoreboard = document.getElementById('scoreboard');
        if (standingsPanel) standingsPanel.classList.add('hidden');
        if (scoreboard) scoreboard.classList.add('hidden');

        // Remove existing modal if any
        const existing = document.getElementById('final-results-modal');
        if (existing) existing.remove();

        // Determine winners
        const pointsLeader = rankings.by_points[0];
        const holesLeader = rankings.by_holes_won[0];
        const isDoubleVictory = pointsLeader && holesLeader &&
            pointsLeader.name === holesLeader.name &&
            holesLeader.rounds_won > 0;

        // Build points ranking
        let pointsRank = 0;
        let prevPoints = null;
        const pointsHtml = rankings.by_points.map((p, i) => {
            if (p.total !== prevPoints) {
                pointsRank = i;
                prevPoints = p.total;
            }
            const medal = pointsRank === 0 ? '🥇' : pointsRank === 1 ? '🥈' : pointsRank === 2 ? '🥉' : `${pointsRank + 1}.`;
            return `<div class="final-rank-row ${pointsRank === 0 ? 'winner' : ''}"><span class="rank-pos">${medal}</span><span class="rank-name">${p.name}</span><span class="rank-val">${p.total} pts</span></div>`;
        }).join('');

        // Build holes ranking
        let holesRank = 0;
        let prevHoles = null;
        const holesHtml = rankings.by_holes_won.map((p, i) => {
            if (p.rounds_won !== prevHoles) {
                holesRank = i;
                prevHoles = p.rounds_won;
            }
            const medal = p.rounds_won === 0 ? '-' :
                          holesRank === 0 ? '🥇' : holesRank === 1 ? '🥈' : holesRank === 2 ? '🥉' : `${holesRank + 1}.`;
            return `<div class="final-rank-row ${holesRank === 0 && p.rounds_won > 0 ? 'winner' : ''}"><span class="rank-pos">${medal}</span><span class="rank-name">${p.name}</span><span class="rank-val">${p.rounds_won} wins</span></div>`;
        }).join('');

        // Build share text
        const shareText = this.buildShareText(rankings, isDoubleVictory);

        // Create modal
        const modal = document.createElement('div');
        modal.id = 'final-results-modal';
        modal.className = 'final-results-modal';
        modal.innerHTML = `
            <div class="final-results-content">
                <h2>🏌️ Final Results</h2>
                ${isDoubleVictory ? `<div class="double-victory-banner">🏆 DOUBLE VICTORY: ${pointsLeader.name} 🏆</div>` : ''}
                <div class="final-rankings">
                    <div class="final-ranking-section">
                        <h3>By Points (Low Wins)</h3>
                        ${pointsHtml}
                    </div>
                    <div class="final-ranking-section">
                        <h3>By Holes Won</h3>
                        ${holesHtml}
                    </div>
                </div>
                <div class="final-actions">
                    <button class="btn btn-primary" id="share-results-btn">📋 Copy Results</button>
                    <button class="btn btn-secondary" id="close-results-btn">New Game</button>
                </div>
            </div>
        `;

        document.body.appendChild(modal);

        // Bind button events
        document.getElementById('share-results-btn').addEventListener('click', () => {
            navigator.clipboard.writeText(shareText).then(() => {
                const btn = document.getElementById('share-results-btn');
                btn.textContent = '✓ Copied!';
                const copyDelay = window.TIMING?.feedback?.copyConfirm || 2000;
                setTimeout(() => btn.textContent = '📋 Copy Results', copyDelay);
            });
        });

        document.getElementById('close-results-btn').addEventListener('click', () => {
            modal.remove();
            this.leaveRoom();
        });
    }

    buildShareText(rankings, isDoubleVictory) {
        let text = '🏌️ Golf Card Game Results\n';
        text += '═══════════════════════\n\n';

        if (isDoubleVictory) {
            text += `🏆 DOUBLE VICTORY: ${rankings.by_points[0].name}!\n\n`;
        }

        text += '📊 By Points (Low Wins):\n';
        rankings.by_points.forEach((p, i) => {
            const medal = i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : `${i + 1}.`;
            text += `${medal} ${p.name}: ${p.total} pts\n`;
        });

        text += '\n⛳ By Holes Won:\n';
        rankings.by_holes_won.forEach((p, i) => {
            const medal = p.rounds_won === 0 ? '-' : i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : `${i + 1}.`;
            text += `${medal} ${p.name}: ${p.rounds_won} wins\n`;
        });

        text += '\nPlayed at golf.game';
        return text;
    }
}

// Initialize game when page loads
document.addEventListener('DOMContentLoaded', () => {
    window.game = new GolfGame();
    window.auth = new AuthManager(window.game);
});


// ===========================================
// AUTH MANAGER
// ===========================================

class AuthManager {
    constructor(game) {
        this.game = game;
        this.token = localStorage.getItem('authToken');
        this.user = JSON.parse(localStorage.getItem('authUser') || 'null');

        this.initElements();
        this.bindEvents();
        this.updateUI();
    }

    initElements() {
        this.authBar = document.getElementById('auth-bar');
        this.authUsername = document.getElementById('auth-username');
        this.logoutBtn = document.getElementById('auth-logout-btn');
        this.authButtons = document.getElementById('auth-buttons');
        this.loginBtn = document.getElementById('login-btn');
        this.signupBtn = document.getElementById('signup-btn');
        this.modal = document.getElementById('auth-modal');
        this.modalClose = document.getElementById('auth-modal-close');
        this.loginFormContainer = document.getElementById('login-form-container');
        this.loginForm = document.getElementById('login-form');
        this.loginUsername = document.getElementById('login-username');
        this.loginPassword = document.getElementById('login-password');
        this.loginError = document.getElementById('login-error');
        this.signupFormContainer = document.getElementById('signup-form-container');
        this.signupForm = document.getElementById('signup-form');
        this.signupUsername = document.getElementById('signup-username');
        this.signupEmail = document.getElementById('signup-email');
        this.signupPassword = document.getElementById('signup-password');
        this.signupError = document.getElementById('signup-error');
        this.showSignupLink = document.getElementById('show-signup');
        this.showLoginLink = document.getElementById('show-login');
    }

    bindEvents() {
        this.loginBtn?.addEventListener('click', () => this.showModal('login'));
        this.signupBtn?.addEventListener('click', () => this.showModal('signup'));
        this.modalClose?.addEventListener('click', () => this.hideModal());
        this.modal?.addEventListener('click', (e) => {
            if (e.target === this.modal) this.hideModal();
        });
        this.showSignupLink?.addEventListener('click', (e) => {
            e.preventDefault();
            this.showForm('signup');
        });
        this.showLoginLink?.addEventListener('click', (e) => {
            e.preventDefault();
            this.showForm('login');
        });
        this.loginForm?.addEventListener('submit', (e) => this.handleLogin(e));
        this.signupForm?.addEventListener('submit', (e) => this.handleSignup(e));
        this.logoutBtn?.addEventListener('click', () => this.logout());
    }

    showModal(form = 'login') {
        this.modal.classList.remove('hidden');
        this.showForm(form);
        this.clearErrors();
    }

    hideModal() {
        this.modal.classList.add('hidden');
        this.clearForms();
    }

    showForm(form) {
        if (form === 'login') {
            this.loginFormContainer.classList.remove('hidden');
            this.signupFormContainer.classList.add('hidden');
            this.loginUsername.focus();
        } else {
            this.loginFormContainer.classList.add('hidden');
            this.signupFormContainer.classList.remove('hidden');
            this.signupUsername.focus();
        }
    }

    clearForms() {
        this.loginForm.reset();
        this.signupForm.reset();
        this.clearErrors();
    }

    clearErrors() {
        this.loginError.textContent = '';
        this.signupError.textContent = '';
    }

    async handleLogin(e) {
        e.preventDefault();
        this.clearErrors();

        const username = this.loginUsername.value.trim();
        const password = this.loginPassword.value;

        try {
            const response = await fetch('/api/auth/login', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ username, password }),
            });

            const data = await response.json();

            if (!response.ok) {
                this.loginError.textContent = data.detail || 'Login failed';
                return;
            }

            this.setAuth(data.token, data.user);
            this.hideModal();

            if (data.user.username && this.game.playerNameInput) {
                this.game.playerNameInput.value = data.user.username;
            }
        } catch (err) {
            this.loginError.textContent = 'Connection error';
        }
    }

    async handleSignup(e) {
        e.preventDefault();
        this.clearErrors();

        const username = this.signupUsername.value.trim();
        const email = this.signupEmail.value.trim() || null;
        const password = this.signupPassword.value;

        try {
            const response = await fetch('/api/auth/register', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ username, email, password }),
            });

            const data = await response.json();

            if (!response.ok) {
                this.signupError.textContent = data.detail || 'Signup failed';
                return;
            }

            this.setAuth(data.token, data.user);
            this.hideModal();

            if (data.user.username && this.game.playerNameInput) {
                this.game.playerNameInput.value = data.user.username;
            }
        } catch (err) {
            this.signupError.textContent = 'Connection error';
        }
    }

    setAuth(token, user) {
        this.token = token;
        this.user = user;
        localStorage.setItem('authToken', token);
        localStorage.setItem('authUser', JSON.stringify(user));
        this.updateUI();
    }

    logout() {
        this.token = null;
        this.user = null;
        localStorage.removeItem('authToken');
        localStorage.removeItem('authUser');
        this.updateUI();
    }

    updateUI() {
        if (this.user) {
            this.authBar?.classList.remove('hidden');
            this.authButtons?.classList.add('hidden');
            if (this.authUsername) {
                this.authUsername.textContent = this.user.username;
            }
            if (this.game.playerNameInput && !this.game.playerNameInput.value) {
                this.game.playerNameInput.value = this.user.username;
            }
        } else {
            this.authBar?.classList.add('hidden');
            this.authButtons?.classList.remove('hidden');
        }
    }
}
