// ⚠ Sentry MUST be imported before React (SDK requirement)
import './sentry'
import { Sentry } from './sentry'
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter, useLocation } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import App from './App'
import { AuthProvider, useAuth } from './contexts/AuthContext'
import { ModuleProvider } from './contexts/ModuleContext'
import { TmaProvider } from './contexts/TmaContext'
import { isInTelegramWebApp } from './utils/telegramWebApp'
import LoginPage from './pages/LoginPage'
import { initTheme } from './utils/theme'
import LoadingIcon from './components/LoadingIcon'
import { startWebVitals } from './utils/webVitals'
import { setupSupportContext } from './utils/supportContext'
import { installAutoDrain } from './utils/supportOutbox'
import { installNumberInputWheelGuard } from './utils/numberInputWheelGuard'
import { _createTicketDirect } from './api/support'
import { API_BASE } from './api/core'

// Build id — меняется, чтобы главный бандл index-*.js получил НОВЫЙ content-hash
// и index.html ссылался на свежий URL. Лечит CF-poison, когда предыдущий
// /assets/index-<hash>.js отравился закэшированной SPA-заглушкой во время гонки
// деплоя (белый экран, главный бандл lazyWithRetry не покрывает). См.
// [[reference_cf_pages_deploy_churn_verify]] / HARD GATE 4.
window.__BUILD_ID__ = '20260716-cf-heal'

// Hydrate global error/network buffers for the support widget so the user
// never has to "open DevTools and paste console" (CLAUDE.md §12 forbids that
// instruction-shift). Passive — no fetch interception, just window listeners.
setupSupportContext()

// PR-8: drain the offline support-ticket outbox when the network returns.
// Runs once on boot if already online; re-runs on `online` event.
installAutoDrain(_createTicketDirect)

// Колесо мыши больше не подкручивает введённые суммы в числовых полях.
installNumberInputWheelGuard()
import './styles/index.css'
import './styles/excellence.css'
import './styles/pwa.css'
import './styles/utilities.css'
import './styles/system-costs.css'
import './styles/ceo-dashboard.css'
import './styles/requisition-detail.css'
import './styles/vacancy-detail.css'
import './components/analytics/analytics.css'
import './components/layout/layout-dropdowns.css'
import './styles/glass-overhaul.css'
import './styles/page-template.css'
import './styles/pipeline-board.css'
import './styles/entity-picker.css'

// ── PWA Service Worker registration ──
import { registerSW } from 'virtual:pwa-register'
import { applyPwaUpdate, handleSwTookControl, wasUpdateJustApplied } from './utils/pwaUpdate'

// User-controlled update prompt (override 2026-05-08).
//
// История: раньше при `onNeedRefresh` мы через 800мс делали `updateSW(true)` →
// reload страницы. То же при `controllerchange`. На активной AI-сессии (диалог-
// тренажёр, AI-match, генерация в AI Studio) это:
//   1. рвало диалог посередине, теряло прогресс;
//   2. сжигало квоту Gemini — пользователь начинал заново и платный AI-вызов
//      выполнялся повторно.
//
// Новое поведение: SW тихо качает свежий бандл в фоне. На клиенте показываем
// маленькую плашку «Доступно обновление» с кнопкой «Обновить» — apply только
// по клику пользователя. Идемпотентно (повторные триггеры не плодят плашек).
const UPDATE_TOAST_ID = 'sw-update-toast'
// FIX 2026-06-07: флаг «пользователь уже закрыл тост ×». При закрытии ×
// тост удаляется из DOM, но SW позже может взять управление и onNeedReload
// вызовет showUpdateAvailableToast снова (document.getElementById → null → показ).
// Флаг предотвращает повторный автоматический показ после явного закрытия пользователем.
// Флаг живёт только в памяти вкладки (не localStorage) — при reload сбрасывается,
// что правильно: после reload это другая сессия.
let _toastDismissedByUser = false

// FIX 2026-06-08 (баг «кнопка „Обновить“ не реагирует»): если страница загружена
// СРАЗУ после клика «Обновить» (ядерный applyPwaUpdate поставил sessionStorage-метку
// перед reload), подавляем повторный тост на это окно жизни вкладки. Без этого при
// открытой второй вкладке (держит старый SW) свежезагруженная страница тут же снова
// уходит в waiting → onNeedRefresh/onNeedReload → тот же тост возвращается, и клик
// выглядит как no-op (перезагрузка была, но «Доступно обновление» снова на экране).
// Проверяем РОВНО один раз на загрузку (метка одноразовая); дальше легитимные апдейты
// (новый деплой через минуты работы) снова покажут тост. HARD GATE 5 не затронут.
const _updateJustApplied = wasUpdateJustApplied()

function showUpdateAvailableToast(applyUpdate) {
    if (_updateJustApplied) return
    if (_toastDismissedByUser) return
    if (document.getElementById(UPDATE_TOAST_ID)) return
    const toast = document.createElement('div')
    toast.id = UPDATE_TOAST_ID
    toast.setAttribute('role', 'status')
    toast.style.cssText = `
        position:fixed;bottom:24px;right:24px;z-index:99999;
        background:#1c1c1e;color:#f5f5f7;padding:10px 14px;border-radius:12px;
        box-shadow:0 8px 32px rgba(0,0,0,0.4);display:flex;align-items:center;gap:10px;
        font-family:Inter,system-ui,sans-serif;font-size:13px;border:1px solid var(--color-accent)40;
        max-width:380px;
    `
    toast.innerHTML = `
        <span style="flex:1">↻ Доступно обновление приложения</span>
        <button id="${UPDATE_TOAST_ID}-apply" style="
            padding:6px 14px;border-radius:8px;border:none;cursor:pointer;
            background:var(--color-accent);color:white;font-weight:600;font-size:12px;
        ">Обновить</button>
        <button id="${UPDATE_TOAST_ID}-close" aria-label="Скрыть" style="
            background:transparent;border:none;color:var(--color-text-muted);cursor:pointer;
            font-size:18px;line-height:1;padding:0 4px;
        ">×</button>
    `
    document.body.appendChild(toast)
    document.getElementById(`${UPDATE_TOAST_ID}-apply`)?.addEventListener('click', () => {
        try { applyUpdate() } catch (e) { console.warn('[PWA] applyUpdate failed', e) }
    })
    document.getElementById(`${UPDATE_TOAST_ID}-close`)?.addEventListener('click', () => {
        _toastDismissedByUser = true
        toast.remove()
    })
}

// onStuck: если та же плашка «Обновить» не гаснет и пользователь жмёт снова и
// снова (новый SW так и не взял управление — вторая вкладка держит старый SW,
// Ядерный вариант: unregister SW + clear caches + reload. Всегда работает,
// не зависит от reg.waiting / onNeedReload (детали в pwaUpdate.js).
const applyUpdateOnClick = () => applyPwaUpdate()

const checkServiceWorkerUpdate = (registration) => {
    try {
        const maybePromise = registration?.update?.()
        if (maybePromise?.catch) {
            maybePromise.catch((error) => {
                console.debug('[PWA] SW update check skipped:', error?.message || error)
            })
        }
    } catch (error) {
        console.debug('[PWA] SW update check skipped:', error?.message || error)
    }
}

const updateSW = registerSW({
    onNeedRefresh() {
        // Новый бандл доступен. Показываем кнопку — reload ТОЛЬКО по клику.
        // Никакого auto-apply: активная AI-сессия (диалог-тренажёр, ai-match,
        // ai-studio) не должна обрываться сама.
        showUpdateAvailableToast(applyUpdateOnClick)
    },
    onNeedReload() {
        // Новый SW взял управление. ВАЖНО: наличие этого колбэка отключает
        // встроенный авто-reload vite-plugin-pwa (register.js зовёт onNeedReload
        // ВМЕСТО window.location.reload()). Перезагружаем вкладку ТОЛЬКО если
        // пользователь нажал «Обновить» именно здесь; иначе (апдейт применён в
        // другой вкладке / self-claim) НЕ трогаем страницу — показываем тост.
        // Запрос пользователя 2026-06-01: никакого принудительного reload без кнопки.
        if (!handleSwTookControl()) showUpdateAvailableToast(applyUpdateOnClick)
    },
    onOfflineReady() {
        console.info('[PWA] ✓ App ready to work offline')
    },
    onRegisteredSW(swUrl, registration) {
        // Никакого auto-reload на boot. Только периодическая проверка апдейта —
        // когда найдётся новый SW, сработает onNeedRefresh и покажет кнопку.
        if (registration) {
            checkServiceWorkerUpdate(registration) // immediate check on registration
            setInterval(() => {
                checkServiceWorkerUpdate(registration)
            }, 5 * 60 * 1000)
        }
    },
    onRegisterError(error) {
        console.warn('[PWA] SW registration failed:', error)
    },
})

// Circuit-breaker для reload-штормов.
//
// До 2026-05-08 отдельный per-source счётчик жил в `controllerchange` (TTL +
// 3-в-2-минуты gate). Теперь auto-reload из controllerchange убран — его
// счётчики стали мёртвыми. Глобальный `META_RELOAD_KEY` ниже всё ещё
// активен и срабатывает на ЛЮБОЙ reload-источник (в первую очередь
// `lazyWithRetry` chunk-404), вызывая `tripCircuitBreaker` для ручного
// recovery. lazyWithRetry имеет свой локальный счётчик в [utils/lazyWithRetry.js].
async function tripCircuitBreaker(reason) {
    try { Sentry.captureMessage('[PWA] reload circuit breaker tripped', { level: 'error', extra: { reason } }) } catch { /* noop */ }
    try {
        if ('serviceWorker' in navigator) {
            const regs = await navigator.serviceWorker.getRegistrations()
            await Promise.all(regs.map(r => r.unregister()))
        }
        if ('caches' in window) {
            const keys = await caches.keys()
            await Promise.all(keys.map(k => caches.delete(k)))
        }
    } catch { /* best effort */ }
    document.body.innerHTML = `
        <div style="
            position:fixed;inset:0;display:flex;flex-direction:column;align-items:center;
            justify-content:center;gap:16px;background:#1c1c1e;color:#f5f5f7;padding:24px;
            font-family:Inter,system-ui,sans-serif;text-align:center;
        ">
            <svg width="52" height="52" viewBox="0 0 24 24" fill="none" stroke="var(--color-warning)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>
            <div style="font-size:18px;font-weight:700">Не удалось обновить приложение автоматически</div>
            <div style="font-size:14px;color:var(--color-text-muted);max-width:520px;line-height:1.5">
                Мы несколько раз попытались подтянуть свежую версию, но браузерный кэш
                продолжает возвращать старый файл. Нажмите кнопку ниже — она очистит
                все локальные кэши и заново откроет приложение.
            </div>
            <button id="onboardix-hard-reset" style="
                padding:10px 24px;border-radius:8px;border:none;cursor:pointer;
                background:var(--color-accent);color:#fff;font-weight:600;font-size:14px;
            ">Сбросить кэш и перезайти</button>
            <div style="font-size:11px;color:#48484a;margin-top:12px">
                Если кнопка не помогает — закройте вкладку и откройте заново (Ctrl+Shift+R).
            </div>
        </div>
    `
    document.getElementById('onboardix-hard-reset')?.addEventListener('click', () => {
        try {
            sessionStorage.clear()
            // Сбрасываем только кэш/синх-мусор, СОХРАНЯЯ: сессию (токен/профиль),
            // пользовательские настройки навигации (закреплённые/недавние/свёрнутые
            // секции левого меню), тему и version-флаги SW/heal. Раньше тут был
            // голый localStorage.clear() с возвратом только токена → "сброс кэша"
            // (а) стирал закреплённые/недавние пункты меню и (б) сносил флаг
            // build-<sha>, из-за чего index.html снова запускал одноразовый SW-nuke
            // → пользователя опять кидало в reload-цикл. Чистим всё остальное
            // (onboardix_cache_*, sync queue, module configs и пр.).
            const KEEP_PREFIXES = [
                'onboardix_token', 'onboardix_original_token', 'onboardix_user',
                'onboardix_nav_', 'onboardix_theme', 'onboardix_heal_version', 'build-',
            ]
            const preserved = {}
            for (const k of Object.keys(localStorage)) {
                if (KEEP_PREFIXES.some((p) => k === p || k.startsWith(p))) {
                    preserved[k] = localStorage.getItem(k)
                }
            }
            localStorage.clear()
            for (const k in preserved) {
                if (preserved[k] != null) localStorage.setItem(k, preserved[k])
            }
        } catch { /* noop */ }
        // Replace (or add) _fresh param — URL остаётся чистым если кнопку жмут повторно.
        const url = new URL(window.location.href)
        url.searchParams.delete('_fresh')
        url.searchParams.append('_fresh', String(Date.now()))
        window.location.href = url.toString()
    })
}

// NB: ручной `controllerchange`-слушатель убран (2026-06-01). Случай «новый SW
// взял управление» теперь обрабатывает vite-колбэк `onNeedReload` выше —
// он перезагружает вкладку ТОЛЬКО при клике в ней, иначе показывает тост.
// Это и убирает принудительный reload без кнопки (в т.ч. claim из другой вкладки).

// ── Meta circuit-breaker: кумулятивный счётчик reload'ов из любых источников.
// Существующие guards (lazyWithRetry, controllerchange, PageErrorBoundary,
// onNeedRefresh) используют разные sessionStorage-ключи и не координируются
// между собой → при перекрёстном срабатывании кумулятивный цикл проходит
// каждому счётчику 1-2 раза, но общее число reload'ов уже 4-6.
//
// Этот наблюдатель считает ТОЛЬКО reload'ы (performance navigation type "reload")
// через localStorage (переживает Safari private tab discard / strict sessionStorage)
// и при превышении порога показывает tripCircuitBreaker UI с кнопкой ручного сброса.
// Не трогает существующие guards — они остаются первым уровнем защиты.
//
// FIX 2026-06-07: счётчик больше не бампается при нормальном открытии страницы
// (navigate/back_forward). Используем performance.getEntriesByType('navigation')[0].type
// — "reload" только при реальном перезапуске вкладки. Это устраняло ложный trip
// при открытии 4+ вкладок за 30 сек.
const META_RELOAD_KEY = 'onboardix_meta_reload'
const META_RELOAD_MAX = 4
const META_RELOAD_WINDOW_MS = 180_000 // 3 мин
const META_RELOAD_COOLDOWN_MS = 30_000 // успех = 30 сек без reload'а

try {
    // Проверяем тип навигации — считаем только реальные reload'ы, не обычные открытия.
    // performance.getEntriesByType('navigation')[0].type: "navigate" | "reload" | "back_forward" | "prerender"
    // Браузер возвращает "reload" только при location.reload() или F5.
    // При отсутствии Navigation Timing API (старые/специфичные браузеры) — тоже не бампаем
    // (лучше пропустить редкий storm, чем ложно зажечь breaker при нормальной работе).
    const navType = (() => {
        try {
            const entries = performance.getEntriesByType('navigation')
            return entries && entries[0] ? entries[0].type : null
        } catch { return null }
    })()
    const isReload = navType === 'reload'

    const raw = localStorage.getItem(META_RELOAD_KEY)
    const state = raw ? JSON.parse(raw) : { count: 0, windowStart: 0, lastReload: 0 }
    const now = Date.now()

    // Успешная загрузка после cooldown — полностью убираем запись, не пишем новую.
    // FIX: раньше писали { count:0, lastReload:now } → при следующей быстрой загрузке
    // lastReload = только что → cooldown не срабатывал → bump начинался снова.
    if (state.lastReload && now - state.lastReload > META_RELOAD_COOLDOWN_MS) {
        localStorage.removeItem(META_RELOAD_KEY)
    } else if (isReload) {
        // Реальный reload (не просто открытие вкладки) — bump счётчик.
        const inWindow = state.windowStart && now - state.windowStart <= META_RELOAD_WINDOW_MS
        const nextState = inWindow
            ? { count: (state.count || 0) + 1, windowStart: state.windowStart, lastReload: now }
            : { count: 1, windowStart: now, lastReload: now }
        localStorage.setItem(META_RELOAD_KEY, JSON.stringify(nextState))

        if (nextState.count >= META_RELOAD_MAX) {
            console.error('[PWA] meta circuit breaker tripped:', nextState.count, 'reloads in 3min window')
            // Сброс, чтобы после ручного recovery пользователь мог нормально работать.
            localStorage.removeItem(META_RELOAD_KEY)
            void tripCircuitBreaker(`meta count=${nextState.count}`)
        }
    }
    // isReload=false (navigate/back_forward/prerender) — не бампаем, ничего не пишем.

    // Сброс через 35 сек если больше reload'ов не происходит (успешный boot).
    window.setTimeout(() => {
        try {
            const cur = JSON.parse(localStorage.getItem(META_RELOAD_KEY) || 'null')
            if (cur && Date.now() - cur.lastReload >= META_RELOAD_COOLDOWN_MS) {
                localStorage.removeItem(META_RELOAD_KEY)
            }
        } catch { /* noop */ }
    }, META_RELOAD_COOLDOWN_MS + 5000)
} catch { /* localStorage disabled */ }

// ── Self-heal bootstrap ──
// Одноразовая чистка «прилипшего» состояния PWA: у части пользователей
// накопилась очередь SyncManager от прошлых релизов (до d6d59f3), SW-регистрации
// с устаревшим precache и cache storage — это проявляется как бесконечное
// «Ожидание синхронизации» / «Сервер недоступен» / зависший спиннер "Загрузка...".
// Чинится автоматически при первом заходе после этого деплоя. Токен и профиль
// пользователя СОХРАНЯЕМ — логин не слетает.
// 2026-07-03: bump — часть браузеров накопила stale SW/precache после серии
// деплоев (после 2026-05-13), из-за чего чанки со сменившимся хэшем не грузятся
// и PageErrorBoundary уходит в цикл «Обновляем приложение…». Bump форсит
// одноразовый снос SW+caches у всех клиентов при следующем заходе.
const HEAL_VERSION = '2026-07-03-01'
const HEAL_KEY = 'onboardix_heal_version'
try {
    if (localStorage.getItem(HEAL_KEY) !== HEAL_VERSION) {
        localStorage.removeItem('onboardix_sync_queue')
        // Чистим возможные устаревшие ключи, но НЕ трогаем токен/пользователя.
        const toKeep = new Set(['onboardix_token', 'onboardix_user', 'onboardix_original_token', HEAL_KEY])
        for (const k of Object.keys(localStorage)) {
            if (k.startsWith('onboardix_cache_')) localStorage.removeItem(k)
        }
        localStorage.setItem(HEAL_KEY, HEAL_VERSION)

        if ('serviceWorker' in navigator) {
            navigator.serviceWorker.getRegistrations().then(regs => {
                for (const r of regs) r.unregister().catch(() => {})
            }).catch(() => {})
        }
        if ('caches' in window) {
            caches.keys().then(keys => Promise.all(keys.map(k => caches.delete(k)))).catch(() => {})
        }
        console.info('[self-heal] Cleared stale SyncManager queue + SW + CacheStorage')
        void toKeep
    }
} catch { /* storage disabled — ignore */ }

// Apply saved theme before render
initTheme()

// ── Railway warmup ping ──
// Cold containers take 15-25s to respond on first request. Fire a background
// /health ping on app boot so the backend wakes up while the user is still
// logging in, and the first real request finds a warm server. Silent & best-effort.
try {
    // API_BASE already resolves VITE_API_BASE | local-dev | PROD_API_FALLBACK (with warn).
    const root = API_BASE.replace(/\/api\/v1\/?$/, '')
    fetch(`${root}/health`, { method: 'GET', cache: 'no-store' }).catch(() => {})
} catch { /* no-op */ }

// Lightweight runtime UX metrics (sampled) for spotting regressions.
startWebVitals((metric) => {
    if (Math.random() > 0.1) return
    try {
        Sentry.captureMessage(`[WebVitals] ${metric.name}`, {
            level: metric.rating === 'poor' ? 'warning' : 'info',
            tags: {
                metric: metric.name,
                rating: metric.rating,
                path: metric.path,
            },
            extra: {
                value: metric.value,
            },
        })
    } catch {
        // no-op when Sentry is disabled
    }
})

const queryClient = new QueryClient({
    defaultOptions: {
        queries: {
            // 5 мин stale: HR-данные меняются редко, нет смысла рефетчить чаще,
            // даже если query явно вызвали. Сильно глушит «дёрганье» при навигации.
            staleTime: 5 * 60_000,
            gcTime: 15 * 60 * 1000,
            retry: 1,
            // Был true — каждый Cmd+Tab / фокус окна / переключение вкладки
            // запускал массовый refetch всех queries на странице, и при отсутствии
            // placeholderData у конкретного компонента это ощущалось как
            // мини-перезагрузка. Жалоба пользователя 2026-05-10 — вылечено выключением.
            refetchOnWindowFocus: false,
            refetchOnReconnect: true,
            placeholderData: (previousData) => previousData,
        },
    },
})

/** React Router basename: production ERP is mounted at /erp/ (FastAPI). Vite dev is usually /. */
function getRouterBasename() {
    if (typeof window === 'undefined') return ''
    const { pathname } = window.location
    if (pathname === '/erp' || pathname.startsWith('/erp/')) return '/erp'
    return ''
}

/** Fallback UI shown when an unhandled error crashes the app */
function SentryFallback({ error, resetError }) {
    return (
        <div
            style={{
                minHeight: '100vh',
                background: 'var(--color-bg)',
                color: 'var(--color-text)',
                fontFamily: 'Inter, Plus Jakarta Sans, system-ui, sans-serif'
            }}
            className='flex flex-col items-center justify-center gap-16'>
            <div style={{ fontSize: 28 }}>💥</div>
            <h2
                style={{
                    color: '#f87171',
                    margin: 0
                }}
                className='text-3xl'>Произошла непредвиденная ошибка</h2>
            <p
                style={{
                    color: 'var(--color-text-muted)',
                    maxWidth: 420,
                    lineHeight: 1.5,
                    margin: 0
                }}
                className='text-center'>
                Наша команда уже уведомлена. Попробуйте перезагрузить страницу.
            </p>
            <button
                onClick={resetError}
                style={{
                    padding: '10px 24px',
                    background: 'var(--color-accent)',
                    border: 'none'
                }}
                className='color-white radius-8 cursor-pointer text-lg fw-600'>
                ↻ Перезагрузить
            </button>
        </div>
    );
}

function AuthGate() {
    const { user, loading } = useAuth()
    const location = useLocation()
    const isCandidateTestRoute = /^\/(?:erp\/)?test\/[^/]+\/?$/.test(location.pathname)
    const isOfferSigningRoute = /^\/(?:erp\/)?offer-signing\/[^/]+\/?$/.test(location.pathname)
    const isCareersRoute = /^\/(?:erp\/)?careers(?:\/.*)?$/.test(location.pathname)
    const isApiDocsRoute = /^\/(?:erp\/)?api-docs\/?$/.test(location.pathname)
    const isProductRoute = /^\/(?:erp\/)?(?:product|privacy|offer)\/?$/.test(location.pathname)
    // Приглашение по ссылке: человек ещё не имеет пароля, стена логина здесь
    // означала бы «войдите, чтобы задать себе пароль» — тупик.
    const isSetPasswordRoute = /^\/(?:erp\/)?set-password\/?$/.test(location.pathname)
    // Возврат из Telegram: человек здесь ровно потому, что ещё не вошёл —
    // стена логина проглотила бы код обмена и вход не завершился бы никогда.
    const isTelegramCallbackRoute = /^\/(?:erp\/)?auth\/telegram\/callback\/?$/.test(location.pathname)
    // Telegram Mini App: route /tma либо signature от Telegram SDK.
    // В TMA-режиме LoginPage запрещён — auth идёт через initData (Phase 6 PR 6.1).
    const isTmaRoute = /^\/(?:erp\/)?tma(?:\/|$)/.test(location.pathname)
    const isTma = isTmaRoute || isInTelegramWebApp()

    // Кандидатские ссылки (тесты, подписание оффера, вакансии) + публичная справка
    // по REST API — без стены логина ERP.
    if (
        isCandidateTestRoute || isOfferSigningRoute || isCareersRoute || isApiDocsRoute
        || isProductRoute || isSetPasswordRoute || isTelegramCallbackRoute
    ) {
        return <App />
    }

    if (isTma) {
        // TmaProvider обрабатывает initData → /auth/telegram-webapp → JWT.
        // App дальше рендерит /tma маршрут с TmaShell вместо обычного Layout.
        return (
            <TmaProvider>
                <App />
            </TmaProvider>
        )
    }

    if (loading) {
        return (
            <div
                style={{
                    minHeight: '100vh',
                    background: 'var(--color-bg)'
                }}
                className='flex items-center justify-center color-blue text-xl fw-600 gap-8'>
                <LoadingIcon status="PROCESSING" size={20} />Загрузка...
                            </div>
        );
    }

    if (!user) return <LoginPage />

    return <ModuleProvider><App /></ModuleProvider>
}

ReactDOM.createRoot(document.getElementById('root')).render(
    <React.StrictMode>
        <Sentry.ErrorBoundary fallback={SentryFallback} showDialog={false}>
            <QueryClientProvider client={queryClient}>
                <AuthProvider>
                    <BrowserRouter basename={getRouterBasename()}>
                        <AuthGate />
                    </BrowserRouter>
                </AuthProvider>
            </QueryClientProvider>
        </Sentry.ErrorBoundary>
    </React.StrictMode>,
)
