function getCsrfToken() {
    const match = document.cookie.match(/csrftoken=([^;]+)/)
    return match ? match[1] : '';
}

function initGameKitTooltips() {
    const portal = document.getElementById('id_tooltip_portal');
    const miniPortal = document.getElementById('id_mini_tooltip_portal');
    const gameKit = document.getElementById('id_game_kit');
    if (!portal || !miniPortal || !gameKit) return;

    // Start portals hidden — ensures is_displayed() works correctly in tests
    // that run without CSS (StaticLiveServerTestCase).
    portal.style.display = 'none';
    miniPortal.style.display = 'none';

    let activeToken = null;
    let equipping = false;

    function inRect(x, y, r) {
        return x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;
    }

    function closePortals() {
        portal.classList.remove('active');
        portal.style.display = 'none';
        miniPortal.classList.remove('active');
        miniPortal.style.display = 'none';
        activeToken = null;
    }

    document.addEventListener('mousemove', (e) => {
        if (portal.classList.contains('active') && activeToken) {
            const tokenRect = activeToken.getBoundingClientRect();
            const portalRect = portal.getBoundingClientRect();
            const rects = [tokenRect, portalRect];
            // Include the DON/DOFF button group's actual bounding rect so the
            // portions of those buttons that hang past the portal's left edge
            // (and above its top edge) stay inside the hover-tolerance region.
            // Was previously a hardcoded 24px left expansion which didn't
            // cover top overhang and underestimated wider button labels.
            const equipBtns = portal.querySelector('.tt-equip-btns');
            if (equipBtns) rects.push(equipBtns.getBoundingClientRect());
            if (miniPortal.classList.contains('active')) rects.push(miniPortal.getBoundingClientRect());
            const left   = Math.min(...rects.map(r => r.left));
            const top    = Math.min(...rects.map(r => r.top));
            const right  = Math.max(...rects.map(r => r.right));
            const bottom = Math.max(...rects.map(r => r.bottom));
            if (!inRect(e.clientX, e.clientY, { left, top, right, bottom })) closePortals();
        } else if (!portal.classList.contains('active')) {
            for (const tokenEl of gameKit.querySelectorAll('.token')) {
                if (!tokenEl.querySelector('.tt')) continue;
                if (inRect(e.clientX, e.clientY, tokenEl.getBoundingClientRect())) {
                    showPortals(tokenEl);
                    break;
                }
            }
        }
    });

    // buildMiniContent — text-only status; DON/DOFF buttons live in the main portal.
    // In-use rows append the room name as "In-Use: <name>", capped at 24 chars
    // (overflow → 21 chars + "…"). The room name lives on the token element's
    // data-in-use-room-name (decks) or data-current-room-name (trinkets).
    function _inUseLabel(roomName) {
        const full = 'In-Use: ' + roomName;
        return full.length > 24 ? full.slice(0, 21) + '…' : full;
    }
    function buildMiniContent(token) {
        const deckId = token.dataset.deckId;
        const tokenId = token.dataset.tokenId;
        const equippedId = gameKit.dataset.equippedId || '';
        const equippedDeckId = gameKit.dataset.equippedDeckId || '';
        const inUseDeckIds = new Set((gameKit.dataset.inUseDeckIds || '').split(',').filter(Boolean));
        if (deckId) {
            if (inUseDeckIds.has(deckId)) {
                miniPortal.textContent = _inUseLabel(token.dataset.inUseRoomName || '');
            } else {
                miniPortal.textContent = (equippedDeckId && deckId === equippedDeckId) ? 'Equipped' : 'Not Equipped';
            }
        } else if (tokenId) {
            const currentRoomName = token.dataset.currentRoomName || '';
            if (currentRoomName) {
                miniPortal.textContent = _inUseLabel(currentRoomName);
            } else {
                miniPortal.textContent = (equippedId && tokenId === equippedId) ? 'Equipped' : 'Not Equipped';
            }
        }
    }

    // Update DON/DOFF button pair to reflect new equipped state.
    function _setEquipState(donBtn, doffBtn, isEquipped) {
        if (isEquipped) {
            donBtn.classList.add('btn-disabled');    donBtn.textContent = '×';
            doffBtn.classList.remove('btn-disabled'); doffBtn.textContent = 'DOFF';
        } else {
            donBtn.classList.remove('btn-disabled'); donBtn.textContent = 'DON';
            doffBtn.classList.add('btn-disabled');   doffBtn.textContent = '×';
        }
    }

    // Sync all tokens' .tt DON/DOFF buttons after an equip state change.
    function _syncTokenButtons(kind, newEquippedId) {
        const selector = kind === 'deck' ? '[data-deck-id]' : '[data-token-id]';
        gameKit.querySelectorAll('.token' + selector).forEach(tokenEl => {
            const id = kind === 'deck' ? tokenEl.dataset.deckId : tokenEl.dataset.tokenId;
            const tt = tokenEl.querySelector('.tt');
            if (!tt) return;
            const don = tt.querySelector('.btn-equip');
            const doff = tt.querySelector('.btn-unequip');
            if (!don || !doff) return;
            _setEquipState(don, doff, id === newEquippedId);
        });
    }

    // If the kit bag dialog is open, re-fetch its content (used after DON so the
    // newly-equipped card appears without the user having to close+reopen).
    function _refreshKitDialog() {
        const dialog = document.getElementById('id_kit_bag_dialog');
        const kitBtn = document.getElementById('id_kit_btn');
        if (!dialog || !dialog.hasAttribute('open') || !kitBtn || !kitBtn.dataset.kitUrl) return;
        fetch(kitBtn.dataset.kitUrl, {
            headers: { 'X-Requested-With': 'XMLHttpRequest' },
        })
        .then(r => r.text())
        .then(html => {
            dialog.innerHTML = html;
            dialog.dispatchEvent(new CustomEvent('kit-content-refreshed'));
        });
    }

    // If the kit bag dialog is open, replace the matching card with a placeholder.
    function _syncKitBagDialog(kind, id) {
        const dialog = document.getElementById('id_kit_bag_dialog');
        if (!dialog || !dialog.hasAttribute('open')) return;
        const selector = kind === 'deck'
            ? `.kit-bag-deck[data-deck-id="${id}"]`
            : `.token[data-token-id="${id}"]`;
        const card = dialog.querySelector(selector);
        if (!card) return;
        const placeholder = document.createElement('div');
        placeholder.className = 'kit-bag-placeholder';
        const icon = card.querySelector('i');
        if (icon) placeholder.innerHTML = icon.outerHTML;
        card.parentNode.insertBefore(placeholder, card);
        card.remove();
    }

    function wireDonDoff(token) {
        const donBtn = portal.querySelector('.btn-equip');
        const doffBtn = portal.querySelector('.btn-unequip');
        if (!donBtn || !doffBtn) return;

        donBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            if (donBtn.classList.contains('btn-disabled') || equipping) return;
            equipping = true;
            const tokenId = donBtn.dataset.tokenId;
            const deckId = donBtn.dataset.deckId;
            if (tokenId) {
                gameKit.dataset.equippedId = tokenId;
                fetch(`/gameboard/equip-trinket/${tokenId}/`, {
                    method: 'POST',
                    headers: {'X-CSRFToken': getCsrfToken()},
                }).then(r => {
                    equipping = false;
                    if (r.ok) {
                        _setEquipState(donBtn, doffBtn, true);
                        _syncTokenButtons('trinket', tokenId);
                        buildMiniContent(token);
                        _refreshKitDialog();
                    }
                });
            } else if (deckId) {
                gameKit.dataset.equippedDeckId = deckId;
                fetch(`/gameboard/equip-deck/${deckId}/`, {
                    method: 'POST',
                    headers: {'X-CSRFToken': getCsrfToken()},
                }).then(r => {
                    equipping = false;
                    if (r.ok) {
                        _setEquipState(donBtn, doffBtn, true);
                        _syncTokenButtons('deck', deckId);
                        buildMiniContent(token);
                        _refreshKitDialog();
                    }
                });
            }
        });

        doffBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            if (doffBtn.classList.contains('btn-disabled') || equipping) return;
            equipping = true;
            const tokenId = doffBtn.dataset.tokenId;
            const deckId = doffBtn.dataset.deckId;
            if (tokenId) {
                gameKit.dataset.equippedId = '';
                fetch(`/gameboard/unequip-trinket/${tokenId}/`, {
                    method: 'POST',
                    headers: {'X-CSRFToken': getCsrfToken()},
                }).then(r => {
                    equipping = false;
                    if (r.ok) {
                        _setEquipState(donBtn, doffBtn, false);
                        _syncTokenButtons('trinket', '');
                        buildMiniContent(token);
                        _syncKitBagDialog('token', tokenId);
                    }
                });
            } else if (deckId) {
                gameKit.dataset.equippedDeckId = '';
                fetch(`/gameboard/unequip-deck/${deckId}/`, {
                    method: 'POST',
                    headers: {'X-CSRFToken': getCsrfToken()},
                }).then(r => {
                    equipping = false;
                    if (r.ok) {
                        _setEquipState(donBtn, doffBtn, false);
                        _syncTokenButtons('deck', '');
                        buildMiniContent(token);
                        _syncKitBagDialog('deck', deckId);
                    }
                });
            }
        });
    }

    function showPortals(token) {
        equipping = false;
        activeToken = token;
        const tooltip = token.querySelector('.tt');
        portal.innerHTML = tooltip.innerHTML;
        portal.classList.add('active');
        portal.style.display = 'block';

        const isEquippable = !!(token.dataset.tokenId || token.dataset.deckId);
        let miniHeight = 0;

        if (isEquippable) {
            buildMiniContent(token);
            miniPortal.classList.add('active');
            miniPortal.style.display = 'block';
            miniHeight = miniPortal.offsetHeight + 4;
            wireDonDoff(token);
        } else {
            miniPortal.classList.remove('active');
            miniPortal.style.display = 'none';
        }

        const tokenRect = token.getBoundingClientRect();
        const halfW = portal.offsetWidth / 2;
        const rawLeft = tokenRect.left + tokenRect.width / 2;
        // Extra left clearance — the DON/DOFF button group is absolute-
        // positioned with `left: -1rem` inside the portal and spills further
        // left by its own width. Measure the actual overhang so the clamp
        // keeps the buttons inside the viewport rather than just the portal.
        let leftOverhang = 0;
        const equipBtns = portal.querySelector('.tt-equip-btns');
        if (equipBtns) {
            const portalRect = portal.getBoundingClientRect();
            const btnsRect = equipBtns.getBoundingClientRect();
            leftOverhang = Math.max(0, portalRect.left - btnsRect.left);
        }
        const minLeft = halfW + 8 + leftOverhang;
        const clampedLeft = Math.max(minLeft, Math.min(rawLeft, window.innerWidth - halfW - 8));
        portal.style.left = Math.round(clampedLeft) + 'px';

        // Show above when token is in lower viewport half; below when in upper half
        // (avoids clipping when game-kit tokens sit near the top in landscape mode).
        const tokenCenterY = tokenRect.top + tokenRect.height / 2;
        const showBelow = tokenCenterY < window.innerHeight / 2;
        if (showBelow) {
            portal.style.top = Math.round(tokenRect.bottom) + 'px';
            portal.style.transform = 'translate(-50%, 0.5rem)';
        } else {
            portal.style.top = Math.round(tokenRect.top) + 'px';
            portal.style.transform = `translate(-50%, calc(-100% - 0.5rem - ${miniHeight}px))`;
        }

        if (isEquippable) {
            const mainRect = portal.getBoundingClientRect();
            // Pin the right edge of the mini-portal to the right edge of the main portal.
            // Using `right` (not `left`) means text width changes grow/shrink leftward.
            miniPortal.style.left = '';
            miniPortal.style.right = Math.round(window.innerWidth - mainRect.right) + 'px';
            miniPortal.style.top = (mainRect.bottom + 4) + 'px';
        }
    }

    document.addEventListener('mouseover', (e) => {
        const tokenEl = e.target.closest('#id_game_kit .token');
        if (!tokenEl || !tokenEl.querySelector('.tt')) return;
        if (!portal.classList.contains('active') || activeToken !== tokenEl) {
            showPortals(tokenEl);
        }
    });

    gameKit.querySelectorAll('.token').forEach(tokenEl => {
        if (!tokenEl.querySelector('.tt')) return;
        tokenEl.addEventListener('mouseenter', () => {
            if (!portal.classList.contains('active') || activeToken !== tokenEl) {
                showPortals(tokenEl);
            }
        });
    });
}

// Mousewheel → horizontal scroll on the My Sea applet (mirrors the
// Palette applet's `bindPaletteWheel` in `apps/dashboard/dashboard.js`).
// Vertical wheel events translate to horizontal scroll so multi-card
// spreads (6-slot Celtic Cross / Escape Velocity) can be panned through
// without touchpad gestures or a horizontal scrollbar drag.
function bindMySeaWheel() {
    document.querySelectorAll('.my-sea-scroll').forEach(function (el) {
        el.addEventListener('wheel', function (e) {
            if (e.deltaY === 0) return;
            e.preventDefault();
            el.scrollLeft += e.deltaY;
        }, { passive: false });
    });
}

document.addEventListener('DOMContentLoaded', initGameKitTooltips);
document.addEventListener('DOMContentLoaded', bindMySeaWheel);
