import React, { useEffect, useState, useRef, useCallback } from 'react';
import Icon from '../editor-shared/Icon';
import { STORYS_addres, USER_addres } from '../api/API';
import { MIXAMO_SKINS } from '../engine/PlayerController';

const getToken = () => {
    try {
        return (
            localStorage.getItem('player_jwt') ||
            localStorage.getItem('Authorization') ||
            ''
        );
    } catch { return ''; }
};

/**
 * GameMenu — Roblox-style центральная карточка меню в игре.
 *
 * Полная копия Godot/exe-меню (см. rublox-native/godot/scripts/ui/GameHud.gd:1304+):
 *   • Карточка 880×620, тёмный полупрозрачный фон, скруглённые углы
 *   • Верхний таб-бар: 5 вкладок (Участники / Настройки / Захваты / Жалоба / Помощь)
 *   • Контент-зона (меняется по таб'у)
 *   • Нижний 3-кнопочный ряд: L Покинуть / R Возродиться / Esc Продолжить
 *
 * Не модалка-pause: игра продолжается. ESC закрывает.
 *
 * props:
 *   visible, onClose, onExit, onRespawn
 *   gameId, gameTitle  — для участников и жалобы
 *   onTakeScreenshot   — F12 (опционально)
 */

const HUD = {
    cardBg: 'rgba(15, 18, 35, 0.88)',
    border: 'rgba(255, 255, 255, 0.10)',
    text: '#f1f5fb',
    textDim: 'rgba(241, 245, 251, 0.55)',
    textMuted: 'rgba(241, 245, 251, 0.62)',
    accent: '#3357ff',
    accentAlt: '#22d97a',
    danger: '#ff6f7a',
    rowBg: 'rgba(255, 255, 255, 0.04)',
    rowBgHover: 'rgba(255, 255, 255, 0.08)',
    font: '"Inter", system-ui, -apple-system, sans-serif',
};

// В десктоп-приложении (Electron) пункт «Полноэкранный режим» не нужен —
// окно и так на весь экран. preload выставляет window.__RUBLOX_DESKTOP__.
const IS_DESKTOP_APP = typeof window !== 'undefined' && !!window.__RUBLOX_DESKTOP__;

const TABS = [
    { id: 'people',   icon: 'users',    title: 'Участники' },
    { id: 'settings', icon: 'settings', title: 'Настройки' },
    { id: 'captures', icon: 'camera',   title: 'Захваты' },
    { id: 'report',   icon: 'flag',     title: 'Жалоба' },
    { id: 'help',     icon: 'info',     title: 'Помощь' },
];

export default function GameMenu({
    visible,
    onClose,
    onExit,
    onRespawn,
    gameId,
    gameTitle,
    mySkin,        // мой текущий скин (skin_bacon-hair, и т.п.) — берём из KubikonPlayer
    sceneRef,      // ref на BabylonScene для применения настроек в реальном времени
}) {
    const [activeTab, setActiveTab] = useState('people');

    // Закрытие меню:
    //   • НЕ fullscreen — Esc (классика)
    //   • Fullscreen    — Tab (Esc отдаётся браузеру для выхода из FS)
    // Регистрируем в capture-фазе чтобы не конфликтовать с pointer-lock.
    useEffect(() => {
        if (!visible) return;
        const onKey = (e) => {
            const isFs = !!(typeof document !== 'undefined' && document.fullscreenElement);
            if (e.key === 'Escape' && !isFs) {
                e.stopPropagation();
                onClose();
                return;
            }
            if ((e.key === 'Tab' || e.code === 'Tab') && isFs) {
                e.preventDefault();
                e.stopPropagation();
                onClose();
                return;
            }
            // L/R hotkeys как в Godot
            if (e.key === 'l' || e.key === 'L') onExit?.();
            if (e.key === 'r' || e.key === 'R') {
                onRespawn?.();
                onClose();
            }
        };
        window.addEventListener('keydown', onKey, true);
        return () => window.removeEventListener('keydown', onKey, true);
    }, [visible, onClose, onExit, onRespawn]);

    if (!visible) return null;

    return (
        <div
            data-rublox-game-menu
            style={{
                position: 'fixed',
                inset: 0,
                zIndex: 4000,
                background: 'rgba(0, 0, 0, 0.55)',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                fontFamily: HUD.font,
                color: HUD.text,
                animation: 'rmFadeIn 180ms ease',
            }}
            onClick={(e) => {
                // Клик по фону (dim) — закрыть. По карточке — не закрывать.
                if (e.target === e.currentTarget) onClose();
            }}
        >
            <style>{KEYFRAMES}</style>

            <div
                style={{
                    width: 880,
                    height: 620,
                    maxWidth: 'calc(100vw - 32px)',
                    maxHeight: 'calc(100vh - 32px)',
                    background: HUD.cardBg,
                    border: `1px solid ${HUD.border}`,
                    borderRadius: 14,
                    boxShadow: '0 16px 48px rgba(0, 0, 0, 0.6)',
                    display: 'flex',
                    flexDirection: 'column',
                    overflow: 'hidden',
                    animation: 'rmSlideUp 220ms cubic-bezier(0.34, 1.56, 0.64, 1)',
                }}
            >
                {/* === Таб-бар === */}
                <TabBar activeTab={activeTab} onTab={setActiveTab} />

                {/* === Контент === */}
                <div
                    style={{
                        flex: 1,
                        minHeight: 0,
                        padding: '12px 16px',
                        overflow: 'hidden',
                        display: 'flex',
                        flexDirection: 'column',
                    }}
                >
                    {activeTab === 'people'   && <TabPeople gameId={gameId} mySkin={mySkin} />}
                    {activeTab === 'settings' && <TabSettings sceneRef={sceneRef} />}
                    {activeTab === 'captures' && <TabCaptures />}
                    {activeTab === 'report'   && <TabReport gameId={gameId} gameTitle={gameTitle} />}
                    {activeTab === 'help'     && <TabHelp />}
                </div>

                {/* === Нижний ряд кнопок === */}
                <BottomBar
                    onExit={onExit}
                    onRespawn={() => { onRespawn?.(); onClose(); }}
                    onResume={onClose}
                />
            </div>
        </div>
    );
}

// ════════════════════════════════════════════════════════════════════
//  TabBar — верхний ряд из 5 вкладок с индикатором активной
// ════════════════════════════════════════════════════════════════════
function TabBar({ activeTab, onTab }) {
    return (
        <div
            style={{
                position: 'relative',
                height: 56,
                display: 'flex',
                borderBottom: `1px solid ${HUD.border}`,
                padding: '0 16px',
            }}
        >
            {TABS.map((tab) => {
                const active = tab.id === activeTab;
                return (
                    <button
                        key={tab.id}
                        onClick={() => onTab(tab.id)}
                        style={{
                            flex: 1,
                            position: 'relative',
                            background: 'transparent',
                            border: 'none',
                            cursor: 'pointer',
                            color: active ? HUD.text : HUD.textMuted,
                            fontWeight: 700,
                            fontSize: 15,
                            fontFamily: HUD.font,
                            display: 'flex',
                            alignItems: 'center',
                            justifyContent: 'center',
                            gap: 8,
                            transition: 'color 150ms ease',
                        }}
                        onMouseEnter={(e) => { if (!active) e.currentTarget.style.color = HUD.text; }}
                        onMouseLeave={(e) => { if (!active) e.currentTarget.style.color = HUD.textMuted; }}
                    >
                        <Icon name={tab.icon} size={16} />
                        {tab.title}
                        {/* Индикатор активной вкладки — белая полоска 92% ширины */}
                        {active && (
                            <span
                                style={{
                                    position: 'absolute',
                                    bottom: -1,
                                    left: '4%',
                                    width: '92%',
                                    height: 3,
                                    background: HUD.text,
                                    borderRadius: 2,
                                }}
                            />
                        )}
                    </button>
                );
            })}
        </div>
    );
}

// ════════════════════════════════════════════════════════════════════
//  BottomBar — 3 кнопки: L Покинуть / R Возродиться / Esc(Tab) Продолжить
// ════════════════════════════════════════════════════════════════════
function BottomBar({ onExit, onRespawn, onResume }) {
    // В fullscreen Esc отдан браузеру (выход из FS) — меню закрывается
    // на Tab. В обычном режиме — Esc.
    const [resumeKey, setResumeKey] = useState(
        typeof document !== 'undefined' && document.fullscreenElement ? 'Tab' : 'Esc'
    );
    useEffect(() => {
        const onFsChange = () => {
            setResumeKey(document.fullscreenElement ? 'Tab' : 'Esc');
        };
        document.addEventListener('fullscreenchange', onFsChange);
        return () => document.removeEventListener('fullscreenchange', onFsChange);
    }, []);
    return (
        <div
            style={{
                height: 64,
                borderTop: `1px solid ${HUD.border}`,
                padding: '12px 16px',
                display: 'flex',
                gap: 12,
            }}
        >
            <ActionBtn hotkey="L" label="Покинуть" onClick={onExit} variant="ghost" />
            <ActionBtn hotkey="R" label="Возродиться" onClick={onRespawn} variant="ghost" />
            <ActionBtn hotkey={resumeKey} label="Продолжить" onClick={onResume} variant="primary" />
        </div>
    );
}

function ActionBtn({ hotkey, label, onClick, variant = 'ghost' }) {
    const [hover, setHover] = useState(false);
    const primary = variant === 'primary';
    return (
        <button
            onClick={onClick}
            onMouseEnter={() => setHover(true)}
            onMouseLeave={() => setHover(false)}
            style={{
                flex: 1,
                height: 40,
                background: primary
                    ? (hover ? '#4868ff' : HUD.accent)
                    : (hover ? HUD.rowBgHover : HUD.rowBg),
                border: primary ? 'none' : `1px solid ${HUD.border}`,
                borderRadius: 10,
                color: HUD.text,
                fontWeight: 700,
                fontSize: 14,
                fontFamily: HUD.font,
                cursor: 'pointer',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                gap: 10,
                transition: 'background 150ms ease',
            }}
        >
            <span
                style={{
                    padding: '2px 8px',
                    fontSize: 11,
                    fontWeight: 800,
                    background: 'rgba(255, 255, 255, 0.18)',
                    borderRadius: 6,
                    letterSpacing: 0.5,
                }}
            >
                {hotkey}
            </span>
            {label}
        </button>
    );
}

// ════════════════════════════════════════════════════════════════════
//  TAB: УЧАСТНИКИ — сетка карточек игроков
// ════════════════════════════════════════════════════════════════════
function TabPeople({ gameId, mySkin }) {
    const [players, setPlayers] = useState(null);
    const [err, setErr] = useState(null);
    // Состояния друзей. Узнаём один раз при открытии таба:
    //   friendIds — уже друзья (status='accepted')
    //   pendingOutIds — я отправил запрос (status='pending', from=me)
    //   pendingInIds  — мне отправили запрос (status='pending', to=me)
    const [friendIds, setFriendIds] = useState(new Set());
    const [pendingOutIds, setPendingOutIds] = useState(new Set());
    // pendingInIds пока не используем визуально (TODO: показать «принять»)
    // const [pendingInIds, setPendingInIds] = useState(new Set());

    useEffect(() => {
        if (!gameId) {
            setErr('Проект не определён');
            return;
        }
        let cancelled = false;
        (async () => {
            try {
                const token = getToken();
                const res = await fetch(
                    `${STORYS_addres}/kubikon3d/projects/${gameId}/players`,
                    {
                        headers: token ? { Authorization: token } : {},
                    },
                );
                if (cancelled) return;
                if (!res.ok) {
                    // Если эндпоинта нет (404) — показываем хотя бы себя.
                    setPlayers([]);
                    return;
                }
                const data = await res.json();
                setPlayers(Array.isArray(data?.players) ? data.players : []);
            } catch (e) {
                if (!cancelled) setErr('Не удалось загрузить участников');
            }
        })();
        return () => { cancelled = true; };
    }, [gameId]);

    // Грузим список друзей + запросы (требуют JWT).
    useEffect(() => {
        const token = getToken();
        if (!token) return;
        let cancelled = false;
        (async () => {
            try {
                const [fr, reqs] = await Promise.all([
                    fetch(`${USER_addres}/api/v1/users/friends`, {
                        headers: { Authorization: token },
                    }).then(r => r.ok ? r.json() : null),
                    fetch(`${USER_addres}/api/v1/users/friends/requests`, {
                        headers: { Authorization: token },
                    }).then(r => r.ok ? r.json() : null),
                ]);
                if (cancelled) return;
                if (fr?.friends) {
                    setFriendIds(new Set(fr.friends.map(f => Number(f.user_id))));
                }
                if (reqs) {
                    const out = (reqs.outgoing || []).map(r => Number(r.to_user_id || r.user_id));
                    setPendingOutIds(new Set(out));
                }
            } catch (e) {
                // тихо игнорим — кнопки покажутся в дефолтном состоянии
            }
        })();
        return () => { cancelled = true; };
    }, []);

    const handleSendRequest = useCallback(async (toUid) => {
        const token = getToken();
        if (!token) return false;
        try {
            const res = await fetch(`${USER_addres}/api/v1/users/friends/request`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: token,
                },
                body: JSON.stringify({ to_user_id: toUid }),
            });
            if (res.ok) {
                const data = await res.json().catch(() => null);
                if (data?.auto_accepted) {
                    // Встречный запрос — мы стали друзьями
                    setFriendIds(prev => new Set(prev).add(toUid));
                } else {
                    setPendingOutIds(prev => new Set(prev).add(toUid));
                }
                return true;
            }
            // 400 already_requested / already_friends — синхронизируем UI
            const err = await res.json().catch(() => null);
            if (err?.error === 'already_requested') {
                setPendingOutIds(prev => new Set(prev).add(toUid));
            } else if (err?.error === 'already_friends') {
                setFriendIds(prev => new Set(prev).add(toUid));
            }
            return false;
        } catch (e) {
            return false;
        }
    }, []);

    // Добавляем самого себя в начало списка
    const me = getMyProfile();
    const combined = (() => {
        if (!players) return null;
        if (!me) return players;
        const myIdx = players.findIndex(p => Number(p.user_id) === Number(me.id));
        if (myIdx >= 0) {
            // Бэк пока не отдаёт skin в /players. Подмешиваем свой mySkin
            // в свою же карточку из API.
            const myFromApi = { ...players[myIdx] };
            if (!myFromApi.skin && (mySkin || me.skin)) {
                myFromApi.skin = mySkin || me.skin;
            }
            return [myFromApi, ...players.filter((_, i) => i !== myIdx)];
        }
        return [{
            user_id: me.id,
            username: me.username || 'Я',
            photo: me.photo || '',
            photo_thumb_b64: me.photo_thumb_b64 || '',
            // mySkin приходит из KubikonPlayer (skinFolderRef.current).
            // Это РЕАЛЬНЫЙ выбранный скин, в отличие от пустого me.skin.
            skin: mySkin || me.skin || '',
        }, ...players];
    })();

    return (
        <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }}>
            {/* Шапка */}
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, height: 38 }}>
                <span style={{ fontWeight: 800, fontSize: 16, color: HUD.text }}>На сервере</span>
                {combined && (
                    <span style={{ color: HUD.accentAlt, fontWeight: 800, fontSize: 12 }}>
                        ● {combined.length}
                    </span>
                )}
            </div>

            {/* Сетка */}
            <div
                style={{
                    flex: 1,
                    overflowY: 'auto',
                    marginTop: 8,
                    paddingRight: 6,
                }}
                className="rm-scroll"
            >
                {err && (
                    <div style={{ color: HUD.danger, textAlign: 'center', padding: 40 }}>
                        {err}
                    </div>
                )}
                {!err && !combined && (
                    <div style={{ color: HUD.textMuted, textAlign: 'center', padding: 40 }}>
                        Загрузка участников...
                    </div>
                )}
                {combined && combined.length === 0 && (
                    <div style={{ color: HUD.textMuted, textAlign: 'center', padding: 40 }}>
                        На сервере пока никого нет
                    </div>
                )}
                {combined && combined.length > 0 && (
                    <div
                        style={{
                            display: 'grid',
                            gridTemplateColumns: 'repeat(5, 1fr)',
                            gap: 12,
                        }}
                    >
                        {combined.map((p, idx) => {
                            const uid = Number(p.user_id || 0);
                            const isFriend = friendIds.has(uid);
                            const isPending = pendingOutIds.has(uid);
                            return (
                                <PlayerCard
                                    key={`${uid || idx}-${idx}`}
                                    player={p}
                                    isMe={me && uid === Number(me.id)}
                                    isFriend={isFriend}
                                    isPending={isPending}
                                    onAddFriend={() => handleSendRequest(uid)}
                                />
                            );
                        })}
                    </div>
                )}
            </div>
        </div>
    );
}

/**
 * FriendButton — кнопка «добавить в друзья» на карточке участника.
 *
 * Состояния:
 *   • default — синий +, при hover ярче и scale 1.08
 *   • pending — иконка часов, серый/тёмный фон, не реагирует на повторный клик
 *   • (друг — кнопка не рисуется вообще, см. PlayerCard)
 */
function FriendButton({ pending, onClick }) {
    const [hover, setHover] = useState(false);
    const [sending, setSending] = useState(false);
    const disabled = pending || sending;
    const handleClick = async (e) => {
        e.stopPropagation();
        if (disabled || !onClick) return;
        setSending(true);
        try { await onClick(); } finally { setSending(false); }
    };
    // Цвета по состоянию
    const bg = pending
        ? 'rgba(60, 65, 80, 0.92)'
        : (hover ? 'rgba(51, 87, 255, 0.95)' : 'rgba(51, 87, 255, 0.85)');
    const iconColor = pending ? 'rgba(255, 255, 255, 0.70)' : '#ffffff';
    const title = pending ? 'Запрос отправлен' : 'Добавить в друзья';
    return (
        <button
            title={title}
            disabled={disabled}
            onClick={handleClick}
            onMouseEnter={() => setHover(true)}
            onMouseLeave={() => setHover(false)}
            style={{
                position: 'absolute',
                bottom: 8,
                right: 8,
                width: 36,
                height: 36,
                borderRadius: '50%',
                background: bg,
                border: pending
                    ? '1px solid rgba(255, 255, 255, 0.12)'
                    : '1px solid rgba(80, 120, 255, 0.55)',
                color: iconColor,
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                cursor: disabled ? 'default' : 'pointer',
                boxShadow: pending
                    ? '0 2px 6px rgba(0, 0, 0, 0.45)'
                    : (hover
                        ? '0 4px 14px rgba(51, 87, 255, 0.55)'
                        : '0 2px 8px rgba(0, 0, 0, 0.5)'),
                transform: (hover && !pending) ? 'scale(1.08)' : 'scale(1)',
                transition: 'background 150ms ease, transform 150ms ease, box-shadow 150ms ease',
                padding: 0,
            }}
        >
            {pending ? (
                /* Иконка «часы» — заявка отправлена */
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                     strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
                    <circle cx="12" cy="12" r="9" />
                    <path d="M12 7v5l3 2" />
                </svg>
            ) : (
                /* Плюс */
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                     strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round">
                    <path d="M12 5v14M5 12h14" />
                </svg>
            )}
        </button>
    );
}

function PlayerCard({ player, isMe, isFriend, isPending, onAddFriend }) {
    const username = String(player.username || '?');
    const color = colorForUser(Number(player.user_id || 0), username);

    // Аватар: 1) skin PNG (картинка персонажа) — главный
    //         2) photo_thumb_b64 (аватар майнкрафтии, fallback)
    //         3) photo URL (старое поле — fallback)
    //         4) буква-инициал
    //
    // 2026-06-14: Mixamo-скины (skin_y-bot, skin_x-bot и т.д.) лежат на
    // rublox-site в /character-assets/skins/<slug>.png. Legacy R15-скины
    // (skin_bacon-hair, skin_sigma-labubu) — в /kubikon-assets/characters/<slug>/avatar.png.
    // Mixamo-набор детектим по тому что в slug нет дефиса с known-legacy-словами.
    // Известные LEGACY R15-скины (бекон, импостер, сигма-лабубу и пр.):
    // их PNG лежит в /kubikon-assets/characters/<slug>/avatar.png.
    // ВСЁ ОСТАЛЬНОЕ что начинается на 'skin_' — это Mixamo
    // (с 2026-06-11 палитра заменена на 80 Mixamo-персонажей).
    const LEGACY_SKINS = new Set([
        'skin_bacon-hair', 'skin_sigma-labubu', 'skin_sparks-roblox',
        'skin_imposter', 'skin_cop', 'skin_baby',
        'skin_pizza', 'skin_burger', 'skin_taco',
    ]);
    let avatarUrl = null;
    let isSkin = false;
    if (player.skin && typeof player.skin === 'string') {
        // ВАЛИДАЦИЯ: legacy R15-скины (bacon-hair и пр.) больше не существуют.
        // Если скин НЕ в наборе валидных Mixamo (80 шт) — показываем аватар
        // дефолтного skin_y-bot (как и в самой игре 3D-модель валидируется).
        let skinId = player.skin;
        if (!MIXAMO_SKINS.has(skinId) && !skinId.startsWith('customskin:')) {
            skinId = 'skin_y-bot';
        }
        const base = (typeof window !== 'undefined'
            && window.location.hostname === 'localhost')
            ? 'http://localhost:3000'
            : 'https://rublox.pro';
        avatarUrl = `${base}/character-assets/skins/${skinId}.png?v=20260614`;
        isSkin = true;
    } else if (player.photo_thumb_b64) {
        avatarUrl = player.photo_thumb_b64.startsWith('data:')
            ? player.photo_thumb_b64
            : `data:image/jpeg;base64,${player.photo_thumb_b64}`;
    } else if (player.photo && typeof player.photo === 'string') {
        // Если photo относительный — резолвим через API_BASE (текущий origin
        // на проде, vite-proxy в dev).
        const env = (typeof import.meta !== 'undefined' && import.meta.env) || {};
        const apiBase = env.VITE_API_BASE
            || (typeof window !== 'undefined' ? window.location.origin : '');
        avatarUrl = player.photo.startsWith('http')
            ? player.photo
            : `${apiBase}${player.photo.startsWith('/') ? '' : '/'}${player.photo}`;
    }

    return (
        <div
            style={{
                width: '100%',
                height: 210,
                background: HUD.rowBg,
                border: `1px solid ${HUD.border}`,
                borderRadius: 12,
                overflow: 'hidden',
                position: 'relative',
                display: 'flex',
                flexDirection: 'column',
                boxShadow: '0 2px 6px rgba(0, 0, 0, 0.35)',
            }}
        >
            {/* Аватар-блок */}
            <div
                style={{
                    height: 152,
                    background: darken(color, 0.45),
                    position: 'relative',
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                    overflow: 'hidden',
                }}
            >
                {avatarUrl ? (
                    <img
                        src={avatarUrl}
                        alt={username}
                        draggable={false}
                        onError={(e) => { e.currentTarget.style.display = 'none'; }}
                        style={{
                            width: '100%',
                            height: '100%',
                            // Скин — это персонаж в полный рост, нужно показать
                            // целиком (contain). Аватарка майнкрафтии — обычно
                            // квадратное фото, тоже хорошо смотрится в contain.
                            objectFit: isSkin ? 'contain' : 'cover',
                            userSelect: 'none',
                        }}
                    />
                ) : (
                    <span
                        style={{
                            fontSize: 64,
                            fontWeight: 900,
                            color: 'rgba(255, 255, 255, 0.95)',
                            textShadow: '0 2px 12px rgba(0, 0, 0, 0.4)',
                            letterSpacing: -1,
                        }}
                    >
                        {username.slice(0, 1).toUpperCase()}
                    </span>
                )}
                {/* Лёгкая виньетка снизу */}
                <div
                    style={{
                        position: 'absolute',
                        left: 0,
                        right: 0,
                        bottom: 0,
                        height: '40%',
                        background: 'linear-gradient(to bottom, transparent, rgba(0,0,0,0.4))',
                    }}
                />
                {/* Кнопка «добавить в друзья» — НЕ показываем:
                    1) на своей карточке (isMe)
                    2) для гостей без user_id
                    3) если уже в друзьях (isFriend) */}
                {!isMe && Number(player.user_id) > 0 && !isFriend && (
                    <FriendButton
                        pending={isPending}
                        onClick={onAddFriend}
                    />
                )}
            </div>
            {/* Футер: имя + ник */}
            <div style={{ padding: '8px 10px', flex: 1, minWidth: 0 }}>
                <div
                    style={{
                        fontWeight: 800,
                        fontSize: 14,
                        color: HUD.text,
                        whiteSpace: 'nowrap',
                        overflow: 'hidden',
                        textOverflow: 'ellipsis',
                    }}
                >
                    {username}
                </div>
                <div
                    style={{
                        fontSize: 11,
                        color: HUD.textDim,
                        marginTop: 2,
                        whiteSpace: 'nowrap',
                        overflow: 'hidden',
                        textOverflow: 'ellipsis',
                    }}
                >
                    @{username.toLowerCase()}
                </div>
            </div>
        </div>
    );
}

// ════════════════════════════════════════════════════════════════════
//  TAB: НАСТРОЙКИ
// ════════════════════════════════════════════════════════════════════
function TabSettings({ sceneRef }) {
    const [settings, setSettingsState] = useState(() => loadSettings());

    // === Применение настроек к engine ===
    // Все вызовы безопасны: optional chaining + try/catch.

    const applyVolume = useCallback((vol) => {
        // vol 0..10. Babylon Audio engine использует 0..1.
        try {
            const audio = sceneRef?.current?.audioManager;
            const v = vol / 10;
            if (audio?.setMasterVolume) audio.setMasterVolume(v);
            // Глобальный fallback — установить мастер-громкость Babylon
            const scene = sceneRef?.current?.scene;
            const engine = scene?.getEngine?.();
            if (engine?.audioEngine) engine.audioEngine.setGlobalVolume?.(v);
        } catch (e) { /* ignore */ }
    }, [sceneRef]);

    const applyQuality = useCallback((q) => {
        // q 1..10. Меняем hardwareScaling (плотность пикселей рендера) и
        // качество теней.
        //
        // ВАЖНО про тени:
        //   • 'soft' — PCF 1024-2048 (мягкие, плавные края) — то что выглядит
        //              как «гладкие тени» в exe. Дефолт почти везде.
        //   • 'hard' — резкие пиксельные тени без фильтрации (быстро, уродливо).
        //              Используем только для q<=2.
        //   • 'off'  — без теней.
        // Раньше на q=5..7 ставился 'hard' — выглядело пиксельно (баг 2026-05-27).
        try {
            const scene = sceneRef?.current?.scene;
            const engine = scene?.getEngine?.();
            if (engine?.setHardwareScalingLevel) {
                const lvl = q >= 10 ? 1.0
                    : q >= 8 ? 1.1
                    : q >= 6 ? 1.25
                    : q >= 4 ? 1.5
                    : q >= 2 ? 2.0
                    : 2.5;
                engine.setHardwareScalingLevel(lvl);
            }
            const bs = sceneRef?.current;
            if (bs?.setShadowQuality) {
                const shadow = q >= 3 ? 'soft' : q >= 2 ? 'hard' : 'off';
                bs.setShadowQuality(shadow);
            }
        } catch (e) { /* ignore */ }
    }, [sceneRef]);

    const applyMaxFps = useCallback((fps) => {
        // 0 = без лимита (vsync auto). Babylon ограничивает через RAF — нативный
        // лимит ставится через engine.targetFps (если есть свойство).
        try {
            const engine = sceneRef?.current?.scene?.getEngine?.();
            if (!engine) return;
            if (fps <= 0) {
                // Снимаем лимит
                if ('targetFps' in engine) delete engine.targetFps;
                engine.runRenderLoop && engine._targetFps !== undefined && (engine._targetFps = 0);
            } else {
                engine._targetFps = fps;
            }
        } catch (e) { /* ignore */ }
    }, [sceneRef]);

    const applyShowFps = useCallback((on) => {
        // Создаём/удаляем простой div-overlay с FPS из engine.
        const existing = document.getElementById('rublox-fps-overlay');
        if (!on) {
            existing?.remove();
            if (window.__rubloxFpsRaf) {
                cancelAnimationFrame(window.__rubloxFpsRaf);
                window.__rubloxFpsRaf = null;
            }
            return;
        }
        if (existing) return;
        const el = document.createElement('div');
        el.id = 'rublox-fps-overlay';
        el.style.cssText = [
            'position: fixed', 'top: 8px', 'right: 12px',
            'z-index: 5000',
            'background: rgba(15, 19, 28, 0.85)',
            'color: #22d97a',
            'padding: 4px 10px',
            'border-radius: 8px',
            'font: 700 14px Consolas, "Roboto Mono", monospace',
            'pointer-events: none',
            'border: 1px solid rgba(255,255,255,0.08)',
        ].join(';');
        document.body.appendChild(el);
        const loop = () => {
            try {
                const engine = sceneRef?.current?.scene?.getEngine?.();
                const fps = engine?.getFps?.() || 0;
                el.textContent = `${Math.round(fps)} FPS`;
            } catch {}
            window.__rubloxFpsRaf = requestAnimationFrame(loop);
        };
        window.__rubloxFpsRaf = requestAnimationFrame(loop);
    }, [sceneRef]);

    const applyMouseSens = useCallback((v) => {
        // 1..10 → MOUSE_SENSITIVITY 0.0010 .. 0.0050 (дефолт 0.0025).
        try {
            const player = sceneRef?.current?.player;
            if (!player) return;
            const base = 0.0025;
            // 5 = base, 10 = ×2, 1 = ×0.4 (линейно)
            const k = v <= 5 ? (0.4 + 0.12 * (v - 1)) : (1.0 + 0.20 * (v - 5));
            player.MOUSE_SENSITIVITY = base * k;
        } catch (e) { /* ignore */ }
    }, [sceneRef]);

    const applyInvertCamera = useCallback((on) => {
        // Базовая реализация: меняем знак pitch-вклада через флаг на player.
        // PlayerController читает _invertCamera в onMouseMove (если реализовано),
        // иначе настройка сохранится и применится при следующей версии engine.
        try {
            const player = sceneRef?.current?.player;
            if (player) player._invertCamera = !!on;
        } catch (e) { /* ignore */ }
    }, [sceneRef]);

    const applyCameraMode = useCallback((mode) => {
        // 'third' | 'first'. Меняем поле + дёргаем _applyCameraMode чтобы
        // PlayerController скрыл/показал модель игрока (в 1st-person иначе
        // голова рендерится изнутри и закрывает обзор).
        try {
            const player = sceneRef?.current?.player;
            if (!player) return;
            player._cameraMode = mode;
            if (typeof player._applyCameraMode === 'function') {
                player._applyCameraMode();
            } else {
                // Fallback: ручное скрытие/показ модели
                const visible = mode !== 'first';
                for (const m of (player._modelMeshes || [])) {
                    try { m.setEnabled(visible); } catch {}
                }
            }
        } catch (e) { /* ignore */ }
    }, [sceneRef]);

    // === При открытии меню — применить все сохранённые настройки ===
    useEffect(() => {
        applyVolume(settings.volume);
        applyQuality(settings.quality);
        applyMaxFps(settings.maxFps);
        applyShowFps(settings.showFps);
        applyMouseSens(settings.mouseSens);
        applyInvertCamera(settings.invertCamera);
        applyCameraMode(settings.cameraMode);
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    const update = useCallback((patch, applier) => {
        setSettingsState(prev => {
            const next = { ...prev, ...patch };
            saveSettings(next);
            applySettingsToWindow(next);
            // Применяем сразу к engine
            if (applier) {
                const k = Object.keys(patch)[0];
                applier(next[k]);
            }
            return next;
        });
    }, []);

    return (
        <div style={{ flex: 1, overflowY: 'auto', paddingRight: 6 }} className="rm-scroll">
            <SettingsSection title="Звук" first />
            <SliderRow
                label="Громкость" hint="Звуковые эффекты и музыка"
                min={0} max={10}
                value={settings.volume}
                onChange={(v) => update({ volume: v }, applyVolume)}
            />

            <SettingsSection title="Экран" />
            {!IS_DESKTOP_APP && (
            <ArrowsRow
                label="Полноэкранный режим"
                hint="Развернуть игру на весь экран"
                options={['Выкл', 'Вкл']}
                index={settings.fullscreen ? 1 : 0}
                onChange={(i) => {
                    update({ fullscreen: i === 1 });
                    try {
                        if (i === 1 && document.documentElement.requestFullscreen) {
                            document.documentElement.requestFullscreen();
                        } else if (i === 0 && document.exitFullscreen) {
                            document.exitFullscreen();
                        }
                    } catch (e) { /* ignore */ }
                }}
            />
            )}
            <SliderRow
                label="Качество графики"
                hint="Разрешение рендера и тени"
                min={1} max={10}
                value={settings.quality}
                onChange={(v) => update({ quality: v }, applyQuality)}
            />
            <DropdownRow
                label="Макс. FPS"
                hint="Лимит частоты кадров"
                options={['30', '60 (стандарт)', '75', '120', '144', 'Без лимита']}
                value={settings.maxFpsLabel}
                onChange={(label) => {
                    const map = { '30': 30, '60 (стандарт)': 60, '75': 75, '120': 120, '144': 144, 'Без лимита': 0 };
                    const fps = map[label] ?? 60;
                    update({ maxFpsLabel: label, maxFps: fps });
                    applyMaxFps(fps);
                }}
            />
            <ArrowsRow
                label="Показывать FPS"
                hint="Счётчик кадров в углу экрана"
                options={['Выкл', 'Вкл']}
                index={settings.showFps ? 1 : 0}
                onChange={(i) => update({ showFps: i === 1 }, applyShowFps)}
            />

            <SettingsSection title="Камера и управление" />
            <SliderRow
                label="Чувствительность мыши"
                hint="Скорость поворота камеры"
                min={1} max={10}
                value={settings.mouseSens}
                onChange={(v) => update({ mouseSens: v }, applyMouseSens)}
            />
            <ArrowsRow
                label="Инвертировать камеру"
                hint="Вверх/вниз становится наоборот"
                options={['Нет', 'Да']}
                index={settings.invertCamera ? 1 : 0}
                onChange={(i) => update({ invertCamera: i === 1 }, applyInvertCamera)}
            />
            <ArrowsRow
                label="Режим камеры"
                hint="Применяется сразу"
                options={['От 3-го лица', 'От 1-го лица']}
                index={settings.cameraMode === 'first' ? 1 : 0}
                onChange={(i) => {
                    const mode = i === 1 ? 'first' : 'third';
                    update({ cameraMode: mode });
                    applyCameraMode(mode);
                }}
            />
        </div>
    );
}

function SettingsSection({ title, first }) {
    return (
        <div
            style={{
                fontWeight: 800,
                fontSize: 13,
                color: HUD.text,
                textTransform: 'uppercase',
                letterSpacing: 1,
                paddingTop: first ? 4 : 16,
                paddingBottom: 8,
                opacity: 0.75,
            }}
        >
            {title}
        </div>
    );
}

function SettingsRowBase({ children }) {
    return (
        <div
            style={{
                display: 'flex',
                alignItems: 'center',
                padding: '10px 0',
                borderBottom: `1px solid ${HUD.border}`,
                gap: 16,
            }}
        >
            {children}
        </div>
    );
}

function SliderRow({ label, hint, min, max, value, onChange }) {
    return (
        <SettingsRowBase>
            <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontWeight: 700, fontSize: 14, color: HUD.text }}>{label}</div>
                {hint && <div style={{ fontSize: 12, color: HUD.textDim, marginTop: 2 }}>{hint}</div>}
            </div>
            <input
                type="range"
                min={min}
                max={max}
                value={value}
                onChange={(e) => onChange(parseInt(e.target.value, 10))}
                style={{ width: 200, accentColor: HUD.accent }}
            />
            <div style={{ width: 28, textAlign: 'right', color: HUD.text, fontWeight: 700, fontSize: 14 }}>
                {value}
            </div>
        </SettingsRowBase>
    );
}

function ArrowsRow({ label, hint, options, index, onChange }) {
    const handle = (delta) => {
        const newIdx = (index + delta + options.length) % options.length;
        onChange(newIdx);
    };
    return (
        <SettingsRowBase>
            <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontWeight: 700, fontSize: 14, color: HUD.text }}>{label}</div>
                {hint && <div style={{ fontSize: 12, color: HUD.textDim, marginTop: 2 }}>{hint}</div>}
            </div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                <ArrowBtn onClick={() => handle(-1)} dir="left" />
                <div
                    style={{
                        minWidth: 110,
                        textAlign: 'center',
                        fontWeight: 700,
                        fontSize: 14,
                        color: HUD.text,
                    }}
                >
                    {options[index]}
                </div>
                <ArrowBtn onClick={() => handle(+1)} dir="right" />
            </div>
        </SettingsRowBase>
    );
}

function ArrowBtn({ onClick, dir }) {
    const [hover, setHover] = useState(false);
    return (
        <button
            onClick={onClick}
            onMouseEnter={() => setHover(true)}
            onMouseLeave={() => setHover(false)}
            style={{
                width: 28,
                height: 28,
                borderRadius: 8,
                background: hover ? HUD.rowBgHover : HUD.rowBg,
                border: `1px solid ${HUD.border}`,
                color: HUD.text,
                cursor: 'pointer',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                fontSize: 12,
                fontWeight: 800,
            }}
        >
            {dir === 'left' ? '‹' : '›'}
        </button>
    );
}

function DropdownRow({ label, hint, options, value, onChange }) {
    return (
        <SettingsRowBase>
            <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontWeight: 700, fontSize: 14, color: HUD.text }}>{label}</div>
                {hint && <div style={{ fontSize: 12, color: HUD.textDim, marginTop: 2 }}>{hint}</div>}
            </div>
            <div style={{ minWidth: 180 }}>
                <CustomSelect value={value} options={options} onChange={onChange} />
            </div>
        </SettingsRowBase>
    );
}

// ════════════════════════════════════════════════════════════════════
//  TAB: ЗАХВАТЫ
// ════════════════════════════════════════════════════════════════════
function TabCaptures() {
    const [shots, setShots] = useState(() => loadShots());

    const takeShot = useCallback(async () => {
        try {
            // Берём канвас Babylon
            const canvas = document.querySelector('canvas');
            if (!canvas) return;
            const dataUrl = canvas.toDataURL('image/png');
            const next = [{ id: Date.now(), dataUrl }, ...shots].slice(0, 30);
            setShots(next);
            saveShots(next);
        } catch (e) {
            console.warn('Screenshot failed:', e);
        }
    }, [shots]);

    const removeShot = useCallback((id) => {
        const next = shots.filter(s => s.id !== id);
        setShots(next);
        saveShots(next);
    }, [shots]);

    const download = useCallback((shot) => {
        const a = document.createElement('a');
        a.href = shot.dataUrl;
        a.download = `rublox-${shot.id}.png`;
        a.click();
    }, []);

    return (
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 12, paddingBottom: 12 }}>
                <div style={{ flex: 1 }}>
                    <div style={{ fontWeight: 800, fontSize: 18, color: HUD.text }}>Захваты</div>
                    <div style={{ fontSize: 12, color: HUD.textDim, marginTop: 2 }}>
                        Снимки экрана из игры. Сохраняются локально в браузере.
                    </div>
                </div>
                <PillBtn onClick={takeShot} variant="primary">Снять сейчас</PillBtn>
            </div>

            {shots.length === 0 ? (
                <div
                    style={{
                        flex: 1,
                        display: 'flex',
                        alignItems: 'center',
                        justifyContent: 'center',
                        color: HUD.textMuted,
                        fontSize: 14,
                        textAlign: 'center',
                    }}
                >
                    Пока нет ни одного снимка.<br />
                    Нажмите «Снять сейчас» чтобы создать.
                </div>
            ) : (
                <div
                    style={{
                        flex: 1,
                        overflowY: 'auto',
                        display: 'grid',
                        gridTemplateColumns: 'repeat(3, 1fr)',
                        gap: 10,
                        paddingRight: 6,
                    }}
                    className="rm-scroll"
                >
                    {shots.map((shot) => (
                        <div
                            key={shot.id}
                            style={{
                                position: 'relative',
                                aspectRatio: '16 / 9',
                                background: '#000',
                                borderRadius: 8,
                                overflow: 'hidden',
                                border: `1px solid ${HUD.border}`,
                            }}
                        >
                            <img
                                src={shot.dataUrl}
                                alt=""
                                style={{ width: '100%', height: '100%', objectFit: 'cover' }}
                            />
                            <div style={{
                                position: 'absolute',
                                left: 0, right: 0, bottom: 0,
                                background: 'linear-gradient(to top, rgba(0,0,0,0.85), transparent)',
                                padding: '20px 8px 8px',
                                display: 'flex',
                                gap: 6,
                            }}>
                                <PillBtn onClick={() => download(shot)} small>Скачать</PillBtn>
                                <PillBtn onClick={() => removeShot(shot.id)} small variant="danger">Удалить</PillBtn>
                            </div>
                        </div>
                    ))}
                </div>
            )}
        </div>
    );
}

// ════════════════════════════════════════════════════════════════════
//  TAB: ЖАЛОБА
// ════════════════════════════════════════════════════════════════════
function TabReport({ gameId, gameTitle }) {
    const [category, setCategory] = useState('Игра / Плеер');
    const [title, setTitle] = useState('');
    const [message, setMessage] = useState('');
    const [status, setStatus] = useState(null);
    const [sending, setSending] = useState(false);

    const send = async () => {
        if (sending) return;
        if (!title.trim() || !message.trim()) {
            setStatus({ text: 'Заполните заголовок и описание', error: true });
            return;
        }
        setSending(true);
        setStatus({ text: 'Отправка...', error: false });
        try {
            const token = getToken();
            // Бэкенд /kubikon3d/reports требует reporter_user_id и target_id и
            // принимает поле text. Раньше TabReport слал {title, message,
            // game_id, game_title} БЕЗ reporter_user_id → бэк отвечал
            // 400 'reporter_user_id required' → жалоба падала. Приводим к
            // формату бэкенда (как нижняя кнопка «Жалоба»).
            const me = getMyProfile();
            if (!me || !me.id) {
                setStatus({ text: 'Войдите, чтобы отправить жалобу.', error: true });
                setSending(false);
                return;
            }
            const res = await fetch(`${STORYS_addres}/kubikon3d/reports`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    ...(token ? { Authorization: token } : {}),
                },
                body: JSON.stringify({
                    reporter_user_id: me.id,
                    target_type: 'project',
                    target_id: gameId || null,
                    category,
                    text: title.trim() + '\n\n' + message.trim()
                        + (gameTitle ? `\n\n(игра: ${gameTitle})` : ''),
                }),
            });
            if (res.ok) {
                setStatus({ text: 'Спасибо! Жалоба принята.', error: false });
                setTitle('');
                setMessage('');
            } else {
                let detail = '';
                try { const j = await res.json(); if (j && j.error) detail = ': ' + j.error; } catch (e) {}
                setStatus({ text: 'Не удалось отправить' + detail + '.', error: true });
            }
        } catch (e) {
            setStatus({ text: 'Сеть недоступна.', error: true });
        } finally {
            setSending(false);
        }
    };

    return (
        <div style={{ flex: 1, overflowY: 'auto', paddingRight: 6 }} className="rm-scroll">
            {/* Контейнер ~85% ширины — поля визуально не растянуты на всю
                карточку, оставляют воздух по бокам. */}
            <div style={{ maxWidth: 640, marginLeft: 'auto', marginRight: 'auto' }}>
            <div style={{ fontWeight: 800, fontSize: 18, color: HUD.text }}>Сообщить о проблеме</div>
            <div style={{ fontSize: 12, color: HUD.textDim, marginTop: 2 }}>
                Опишите что пошло не так. Мы прочитаем все обращения.
            </div>

            <FormLabel>Категория</FormLabel>
            <CustomSelect
                value={category}
                options={['Игра / Плеер', 'Редактор', 'Сайт', 'Другое']}
                onChange={setCategory}
            />

            <FormLabel>Заголовок (коротко)</FormLabel>
            <input
                value={title}
                onChange={(e) => setTitle(e.target.value)}
                maxLength={200}
                placeholder="Например: «Падает FPS на карте Тир»"
                style={{
                    width: '100%',
                    background: HUD.rowBg,
                    color: HUD.text,
                    border: `1px solid ${HUD.border}`,
                    borderRadius: 8,
                    padding: '10px 12px',
                    fontFamily: HUD.font,
                    fontSize: 14,
                    boxSizing: 'border-box',
                }}
            />

            <FormLabel>Подробнее (что именно произошло)</FormLabel>
            <textarea
                value={message}
                onChange={(e) => setMessage(e.target.value)}
                maxLength={2000}
                rows={5}
                placeholder="Что вы делали? Что произошло? Что должно было быть?"
                style={{
                    width: '100%',
                    background: HUD.rowBg,
                    color: HUD.text,
                    border: `1px solid ${HUD.border}`,
                    borderRadius: 8,
                    padding: '10px 12px',
                    boxSizing: 'border-box',
                    fontFamily: HUD.font,
                    fontSize: 14,
                    resize: 'vertical',
                    minHeight: 110,
                }}
            />

            <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginTop: 16 }}>
                {status && (
                    <div
                        style={{
                            flex: 1,
                            color: status.error ? HUD.danger : HUD.accentAlt,
                            fontWeight: 700,
                            fontSize: 13,
                        }}
                    >
                        {status.text}
                    </div>
                )}
                <PillBtn onClick={send} variant="primary" disabled={sending}>
                    {sending ? 'Отправка...' : 'Отправить'}
                </PillBtn>
            </div>
            </div>
        </div>
    );
}

function FormLabel({ children }) {
    return (
        <div
            style={{
                fontWeight: 700,
                fontSize: 13,
                color: HUD.text,
                marginTop: 16,
                marginBottom: 8,
            }}
        >
            {children}
        </div>
    );
}

/**
 * CustomSelect — кастомный дропдаун (вместо нативного <select>).
 *
 * Нативный <select> в Chrome рендерит опции силами ОС: фон/цвет текста
 * прописаны системной темой. В тёмной теме сайта — белый текст на белом
 * фоне (баг 2026-05-27). Делаем свой через позиционированный popover.
 */
function CustomSelect({ value, options, onChange, fullWidth = true }) {
    const [open, setOpen] = useState(false);
    const ref = useRef(null);
    useEffect(() => {
        if (!open) return;
        const onDoc = (e) => {
            if (ref.current && !ref.current.contains(e.target)) setOpen(false);
        };
        const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
        document.addEventListener('mousedown', onDoc);
        document.addEventListener('keydown', onKey);
        return () => {
            document.removeEventListener('mousedown', onDoc);
            document.removeEventListener('keydown', onKey);
        };
    }, [open]);

    return (
        <div ref={ref} style={{ position: 'relative', width: fullWidth ? '100%' : 'auto' }}>
            <button
                type="button"
                onClick={() => setOpen(o => !o)}
                style={{
                    width: '100%',
                    background: HUD.rowBg,
                    color: HUD.text,
                    border: `1px solid ${HUD.border}`,
                    borderRadius: 8,
                    padding: '10px 36px 10px 12px',
                    fontFamily: HUD.font,
                    fontSize: 14,
                    fontWeight: 700,
                    cursor: 'pointer',
                    textAlign: 'left',
                    position: 'relative',
                    boxSizing: 'border-box',
                }}
            >
                {value}
                <span
                    style={{
                        position: 'absolute',
                        right: 12,
                        top: '50%',
                        transform: `translateY(-50%) rotate(${open ? 180 : 0}deg)`,
                        transition: 'transform 150ms ease',
                        color: HUD.textMuted,
                        fontSize: 10,
                        pointerEvents: 'none',
                    }}
                >▼</span>
            </button>
            {open && (
                <div
                    style={{
                        position: 'absolute',
                        top: 'calc(100% + 4px)',
                        left: 0,
                        right: 0,
                        background: 'rgba(20, 24, 45, 0.98)',
                        border: `1px solid ${HUD.border}`,
                        borderRadius: 8,
                        boxShadow: '0 8px 24px rgba(0, 0, 0, 0.55)',
                        zIndex: 50,
                        overflow: 'hidden',
                        maxHeight: 240,
                        overflowY: 'auto',
                    }}
                    className="rm-scroll"
                >
                    {options.map((o) => {
                        const active = o === value;
                        return (
                            <CustomSelectItem
                                key={o}
                                label={o}
                                active={active}
                                onClick={() => { onChange(o); setOpen(false); }}
                            />
                        );
                    })}
                </div>
            )}
        </div>
    );
}

function CustomSelectItem({ label, active, onClick }) {
    const [hover, setHover] = useState(false);
    return (
        <button
            type="button"
            onClick={onClick}
            onMouseEnter={() => setHover(true)}
            onMouseLeave={() => setHover(false)}
            style={{
                width: '100%',
                background: active
                    ? HUD.accent
                    : (hover ? HUD.rowBgHover : 'transparent'),
                color: HUD.text,
                border: 'none',
                padding: '10px 14px',
                fontFamily: HUD.font,
                fontSize: 14,
                fontWeight: 700,
                cursor: 'pointer',
                textAlign: 'left',
                display: 'block',
            }}
        >
            {label}
        </button>
    );
}

// ════════════════════════════════════════════════════════════════════
//  TAB: ПОМОЩЬ
// ════════════════════════════════════════════════════════════════════
function TabHelp() {
    return (
        <div style={{ flex: 1, overflowY: 'auto', paddingRight: 6 }} className="rm-scroll">
            <div style={{ fontWeight: 800, fontSize: 18, color: HUD.text, marginBottom: 14 }}>
                Управление
            </div>

            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12 }}>
                <HelpSection title="Движение" rows={[
                    ['Вперёд',  'W'],
                    ['Назад',   'S'],
                    ['Влево',   'A'],
                    ['Вправо',  'D'],
                    ['Прыжок',  'Space'],
                    ['Бег',     'Shift'],
                ]} />
                <HelpSection title="Камера и инвентарь" rows={[
                    ['Поворот камеры', 'ПКМ + мышь'],
                    ['Зум',            'Колесо'],
                    ['1-е / 3-е лицо', 'F5'],
                    ['Слоты',          '1 – 9'],
                    ['Стрельба',       'ЛКМ'],
                    ['Перезарядка',    'R'],
                ]} />
                <HelpSection title="Меню" rows={[
                    ['Чат',           'T'],
                    ['Скриншот',      'F12'],
                    ['Меню',          'Esc'],
                    ['Закрыть чат',   'Esc'],
                    ['Курсор',        'Alt + M'],
                ]} />
            </div>

            <div style={{ fontWeight: 800, fontSize: 16, color: HUD.text, marginTop: 24, marginBottom: 10 }}>
                О Рублоксе
            </div>
            <div
                style={{
                    background: HUD.rowBg,
                    border: `1px solid ${HUD.border}`,
                    borderRadius: 8,
                    padding: '14px 16px',
                    color: HUD.textMuted,
                    fontSize: 13,
                    lineHeight: 1.55,
                }}
            >
                Рублокс — платформа 3D-игр от онлайн-школы Майнкрафтия. Если что-то
                не работает — нажмите «Жалоба» в меню и опишите проблему.
                Мы прочитаем все обращения.
                <div style={{ marginTop: 12, display: 'flex', gap: 8 }}>
                    <a
                        href="https://rublox.pro"
                        target="_blank"
                        rel="noreferrer"
                        style={linkBtn}
                    >
                        Сайт Рублокса
                    </a>
                    <a
                        href="https://minecraftia-school.ru"
                        target="_blank"
                        rel="noreferrer"
                        style={linkBtn}
                    >
                        Майнкрафтия
                    </a>
                </div>
            </div>
        </div>
    );
}

function HelpSection({ title, rows }) {
    return (
        <div
            style={{
                background: HUD.rowBg,
                border: `1px solid ${HUD.border}`,
                borderRadius: 8,
                padding: '12px 14px',
            }}
        >
            <div style={{ fontWeight: 800, fontSize: 13, color: HUD.text, marginBottom: 10 }}>
                {title}
            </div>
            {rows.map(([label, key]) => (
                <div
                    key={label}
                    style={{
                        display: 'flex',
                        alignItems: 'center',
                        justifyContent: 'space-between',
                        padding: '6px 0',
                        borderBottom: `1px solid rgba(255, 255, 255, 0.04)`,
                    }}
                >
                    <span style={{ color: HUD.textMuted, fontSize: 12 }}>{label}</span>
                    <span
                        style={{
                            padding: '2px 8px',
                            background: 'rgba(255, 255, 255, 0.10)',
                            border: `1px solid ${HUD.border}`,
                            borderRadius: 6,
                            color: HUD.text,
                            fontWeight: 700,
                            fontSize: 11,
                            fontFamily: 'Consolas, "Roboto Mono", monospace',
                        }}
                    >
                        {key}
                    </span>
                </div>
            ))}
        </div>
    );
}

const linkBtn = {
    padding: '6px 12px',
    background: HUD.rowBg,
    border: `1px solid ${HUD.border}`,
    borderRadius: 6,
    color: HUD.text,
    fontSize: 12,
    fontWeight: 700,
    textDecoration: 'none',
};

// ════════════════════════════════════════════════════════════════════
//  ОБЩИЕ компоненты
// ════════════════════════════════════════════════════════════════════
function PillBtn({ children, onClick, variant = 'ghost', disabled, small }) {
    const [hover, setHover] = useState(false);
    const colors = {
        primary: { bg: HUD.accent, bgHover: '#4868ff', color: '#fff' },
        ghost:   { bg: HUD.rowBg, bgHover: HUD.rowBgHover, color: HUD.text },
        danger:  { bg: 'rgba(255, 111, 122, 0.20)', bgHover: 'rgba(255, 111, 122, 0.35)', color: HUD.danger },
    }[variant] || { bg: HUD.rowBg, bgHover: HUD.rowBgHover, color: HUD.text };

    return (
        <button
            onClick={onClick}
            disabled={disabled}
            onMouseEnter={() => setHover(true)}
            onMouseLeave={() => setHover(false)}
            style={{
                padding: small ? '4px 10px' : '10px 18px',
                background: disabled ? 'rgba(255, 255, 255, 0.05)' : (hover ? colors.bgHover : colors.bg),
                color: disabled ? HUD.textDim : colors.color,
                border: variant === 'primary' ? 'none' : `1px solid ${HUD.border}`,
                borderRadius: 10,
                fontWeight: 700,
                fontSize: small ? 12 : 14,
                fontFamily: HUD.font,
                cursor: disabled ? 'not-allowed' : 'pointer',
                transition: 'background 150ms ease',
            }}
        >
            {children}
        </button>
    );
}

// ════════════════════════════════════════════════════════════════════
//  Утилиты
// ════════════════════════════════════════════════════════════════════

const SETTINGS_KEY = 'rublox_game_settings';
const SHOTS_KEY = 'rublox_game_shots';

const DEFAULT_SETTINGS = {
    volume: 7,
    fullscreen: false,
    quality: 7,
    maxFps: 60,
    maxFpsLabel: '60 (стандарт)',
    showFps: false,
    mouseSens: 5,
    invertCamera: false,
    cameraMode: 'third',
};

function loadSettings() {
    try {
        const raw = localStorage.getItem(SETTINGS_KEY);
        if (!raw) return { ...DEFAULT_SETTINGS };
        return { ...DEFAULT_SETTINGS, ...JSON.parse(raw) };
    } catch {
        return { ...DEFAULT_SETTINGS };
    }
}

function saveSettings(s) {
    try { localStorage.setItem(SETTINGS_KEY, JSON.stringify(s)); } catch {}
}

function applySettingsToWindow(s) {
    // Глобальные настройки — engine может их прочитать.
    try {
        window.__rubloxSettings = s;
    } catch {}
}

function loadShots() {
    try {
        const raw = localStorage.getItem(SHOTS_KEY);
        return raw ? JSON.parse(raw) : [];
    } catch { return []; }
}

function saveShots(shots) {
    try {
        // Ограничиваем 30 снимков чтобы не переполнять localStorage.
        const trimmed = shots.slice(0, 30);
        localStorage.setItem(SHOTS_KEY, JSON.stringify(trimmed));
    } catch (e) {
        // localStorage переполнен — удаляем половину.
        try {
            const trimmed = shots.slice(0, 10);
            localStorage.setItem(SHOTS_KEY, JSON.stringify(trimmed));
        } catch {}
    }
}

function getMyProfile() {
    // rublox-player хранит юзера в JWT (player_jwt), не в localStorage.Profile.
    // Декодируем payload чтобы получить id/username — без проверки подписи,
    // только для отображения self-карточки.
    try {
        const jwt = (
            localStorage.getItem('player_jwt') ||
            localStorage.getItem('Authorization') ||
            ''
        );
        if (jwt) {
            const parts = jwt.split('.');
            if (parts.length >= 2) {
                const b64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
                const json = atob(b64 + '==='.slice((b64.length + 3) % 4));
                const p = JSON.parse(decodeURIComponent(escape(json)));
                const uid = Number(p.id || p.userId || p.user_id || p.sub);
                if (uid) {
                    return {
                        id: uid,
                        username: p.firstName || p.username || p.email?.split('@')[0] || 'Я',
                        photo: '',
                        photo_thumb_b64: '',
                        skin: '',
                    };
                }
            }
        }
    } catch { /* fall through */ }
    // Legacy fallback (на случай если где-то Profile есть)
    try {
        const raw = localStorage.getItem('Profile') || localStorage.getItem('profile');
        if (!raw) return null;
        const p = JSON.parse(raw);
        return {
            id: p.id || p.userId,
            username: p.firstName || p.username || p.email?.split('@')[0],
            photo: p.photo || '',
            photo_thumb_b64: p.photo_thumb_b64 || '',
            skin: p.skin || '',
        };
    } catch { return null; }
}

function colorForUser(uid, name) {
    // Хеш-цвет для аватара. Простой деривация.
    let h = 0;
    const s = String(uid || name || '?');
    for (let i = 0; i < s.length; i++) h = ((h << 5) - h + s.charCodeAt(i)) & 0xffffffff;
    h = Math.abs(h);
    const palette = [
        '#3357ff', '#22d97a', '#ffc857', '#ff6f7a', '#a06aff',
        '#ff8a3d', '#5ec8ff', '#dc6aff', '#7adb6c', '#ffd84a',
    ];
    return palette[h % palette.length];
}

function darken(hex, k) {
    // hex #rrggbb → ослабляем на k (0..1)
    const c = hex.replace('#', '');
    const r = parseInt(c.slice(0, 2), 16);
    const g = parseInt(c.slice(2, 4), 16);
    const b = parseInt(c.slice(4, 6), 16);
    const f = 1 - k;
    return `rgb(${Math.round(r * f)}, ${Math.round(g * f)}, ${Math.round(b * f)})`;
}

const KEYFRAMES = `
@keyframes rmFadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@keyframes rmSlideUp {
    from { opacity: 0; transform: translateY(20px) scale(0.96); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}
.rm-scroll::-webkit-scrollbar {
    width: 8px;
}
.rm-scroll::-webkit-scrollbar-track {
    background: transparent;
}
.rm-scroll::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.12);
    border-radius: 4px;
}
.rm-scroll::-webkit-scrollbar-thumb:hover {
    background: rgba(255, 255, 255, 0.20);
}
`;
