var MemoryRouter = ReactRouterDOM.MemoryRouter;
var Routes       = ReactRouterDOM.Routes;
var Route        = ReactRouterDOM.Route;
var createRoot   = ReactDOM.createRoot;
var _useEffect_App  = React.useEffect;
var _useRef_App     = React.useRef;

// Route → page title map for GA4 virtual page views
var ROUTE_TITLES = {
    '/':                    'Home',
    '/analysis':            'Scan',
    '/scans':               'Scan History',
    '/reports':             'Reports',
    '/location-details':    'Location Details',
    '/faq':                 'FAQ',
    '/miss-mould':          'Miss Mould',
    '/weather':             'Weather',
    '/air-quality':         'Air Quality',
    '/edu':                 'Mould Edu',
    '/about':               'About Us',
    '/privacy':             'Privacy & Terms',
    '/specialists':         'Find a Professional',
};

function _getPageTitle(pathname) {
    if (ROUTE_TITLES[pathname]) return ROUTE_TITLES[pathname];
    if (pathname.indexOf('/specialists/') === 0 && pathname.split('/').length === 3) return 'Specialist Profile';
    if (pathname.indexOf('/specialists/') === 0 && pathname.indexOf('/contact') !== -1) return 'Contact Professional';
    return pathname;
}

// Inner component that has access to MemoryRouter location
var RouteTracker = function () {
    var location = ReactRouterDOM.useLocation();
    var navigate = ReactRouterDOM.useNavigate();
    var prevPath = _useRef_App(null);
    // Browser-back bridge. MemoryRouter deliberately keeps routes off the URL (PWA
    // shell), which also made the browser/Android Back button a no-op — never built,
    // not a regression (confirmed 2026-08-08). Every genuine in-app navigation pushes
    // one opaque entry onto window.history; popstate translates Back into router
    // navigate(-1). expectingPop stops the resulting location change from pushing a
    // fresh entry (which would trap the user in a loop). Forward button intentionally
    // unsupported (single-direction bridge — the honest minimum for launch).
    var expectingPop = _useRef_App(false);

    _useEffect_App(function () {
        var onPop = function () {
            expectingPop.current = true;
            navigate(-1);
        };
        window.addEventListener('popstate', onPop);
        return function () { window.removeEventListener('popstate', onPop); };
    }, []);

    _useEffect_App(function () {
        var path = location.pathname;
        // Only fire on genuine route changes
        if (path === prevPath.current) return;
        var isFirst = prevPath.current === null;
        prevPath.current = path;
        if (expectingPop.current) {
            expectingPop.current = false;
        } else if (!isFirst) {
            window.history.pushState({ md: 1 }, '');
        }
        AnalyticsService.pageView(path, _getPageTitle(path));
    }, [location.pathname]);

    return null;
};

var AppRoutes = function () {
    var store = useScanStore();

    if (store.loading) {
        return (
            <div className="max-w-md mx-auto h-[100dvh] flex flex-col items-center justify-center gap-4">
                <div className="w-12 h-12 rounded-full border-4 border-forest/20 border-t-forest animate-spin"></div>
                <p className="text-sm text-muted font-medium">Loading your data...</p>
            </div>
        );
    }

    return (
        <Routes>
            <Route path="/"                          element={<HomePage />} />
            <Route path="/scans"                     element={<ScansPage />} />
            <Route path="/reports"                   element={<ReportsPage />} />
            <Route path="/analysis"                  element={<AnalysisPage />} />
            <Route path="/location-details"          element={<LocationDetails />} />
            <Route path="/scan/:id"                  element={<ScanDetails />} />
            <Route path="/faq"                       element={<MouldFAQ />} />
            {/* Miss Mould unmounts on navigation, which is how its microphone, AudioContext
                and audio buffers get released — see the unmount effect in MissMouldChat. */}
            <Route path="/miss-mould"                element={<MissMouldChat />} />
            <Route path="/weather"                   element={<WeatherPage />} />
            <Route path="/air-quality"               element={<AirQualityPage />} />
            <Route path="/edu"                       element={<MouldEdu />} />
            <Route path="/about"                     element={<AboutPage />} />
            <Route path="/privacy"                   element={<PrivacyPolicyPage />} />
            <Route path="/specialists"               element={<SpecialistDirectoryPage />} />
            <Route path="/specialists/:id"           element={<SpecialistProfilePage />} />
            <Route path="/specialists/:id/contact"   element={<ContactSpecialistPage />} />
        </Routes>
    );
};

var App = function () {
    return (
        <ErrorBoundary>
            <ClerkProvider>
                <ScanProvider>
                    <MemoryRouter>
                        <RouteTracker />
                        <AppRoutes />
                        {/* BottomNav is mounted ONCE here, as a sibling of the
                            routes, and must stay that way.

                            It used to be rendered by Layout — and all 15 page
                            components render their own Layout — so every
                            navigation unmounted the whole nav and rebuilt it,
                            including the frosted-glass island's
                            backdrop-filter surface and five ligature icon
                            spans. Measured: 0 of 4 anchors survived a tab tap.
                            Re-rasterising a backdrop-filter layer from scratch
                            on every tap is the bottom-nav flicker.

                            The nav is position:fixed, so it never needed to
                            live inside the page subtree. It reads the active
                            route from useLocation and hides itself where
                            required, which is why no page needed editing. */}
                        <BottomNav />
                    </MemoryRouter>
                </ScanProvider>
            </ClerkProvider>
        </ErrorBoundary>
    );
};

var root = createRoot(document.getElementById('root'));

/**
 * Boot sequence — AI engine pre-loading via AIEngineAdapter.
 *
 * Reads AI_ENGINE flag value and pre-loads models accordingly:
 *   'local'  → LocalCNNService.load()              (full on-device pipeline)
 *   'hybrid' → LocalCNNEnrichmentService.loadForEnrichment()  (enrichment only)
 *   others   → no pre-load needed (cloud APIs)
 *
 * window.__aiEngineStatus is set after boot for diagnostic use:
 *   { active: 'local_cnn' | 'hybrid' | 'cloud_ai' | 'nyckel' | 'none', reason: string }
 */
function _logEngineStatus(active, reason) {
    window.__aiEngineStatus = { active: active, reason: reason };
    console.info('[AI Engine] Active engine:', active, '|', reason);
}

/**
 * Injects a script and resolves when it has run. Rejects rather than hanging if
 * the CDN is unreachable, so a failed on-device load degrades to the cloud
 * verdict instead of sitting on the splash screen.
 */
function _loadScript(src) {
    return new Promise(function (resolve, reject) {
        var s = document.createElement('script');
        s.src = src;
        s.async = false;              // preserve tf-before-mobilenet ordering
        s.onload = function () { resolve(); };
        s.onerror = function () { reject(new Error('Failed to load ' + src)); };
        document.head.appendChild(s);
    });
}

/**
 * Fetches the on-device ML runtime, once, and only for the engines that use it.
 *
 * TensorFlow.js is ~1.4 MB of parsed JavaScript. It used to load unconditionally
 * from index.html, which meant every visitor paid for it even under
 * aws_lambda_only — the shipped default, where no model ever runs. MobileNet
 * depends on the tf global, so the two are sequenced rather than parallel.
 */
var _onDeviceRuntimePromise = null;
function _loadOnDeviceRuntime(onProgress) {
    if (_onDeviceRuntimePromise) return _onDeviceRuntimePromise;
    if (typeof window.tf !== 'undefined' && typeof window.mobilenet !== 'undefined') {
        _onDeviceRuntimePromise = Promise.resolve();
        return _onDeviceRuntimePromise;
    }
    if (onProgress) onProgress('Loading on-device AI runtime…');
    _onDeviceRuntimePromise = _loadScript('https://cdn.jsdelivr.net/npm/@tensorflow/tfjs')
        .then(function () { return _loadScript('https://cdn.jsdelivr.net/npm/@tensorflow-models/mobilenet'); });
    return _onDeviceRuntimePromise;
}

function _preloadEngines() {
    var aiEngine = FeatureFlags.getValue('AI_ENGINE') || 'nyckel';

    var _splashProgress = function (msg) {
        var splashMsg = document.getElementById('splash-msg');
        if (splashMsg) { splashMsg.textContent = msg; }
    };

    if (aiEngine === 'hybrid') {
        console.info('[AI Engine] HYBRID mode — pre-loading Local CNN enrichment models...');
        return _loadOnDeviceRuntime(_splashProgress).then(function () {
            return LocalCNNEnrichmentService.loadForEnrichment(_splashProgress);
        })
        .then(function () {
            _logEngineStatus('hybrid', 'Nyckel + Local CNN enrichment models loaded.');
            return false;
        })
        ['catch'](function (err) {
            console.warn('[AI Engine] Hybrid enrichment pre-load failed — Nyckel will run without enrichment:', err.message);
            return false;
        });
    }

    if (aiEngine === 'local') {
        console.info('[AI Engine] LOCAL mode — pre-loading full Local CNN pipeline...');
        return _loadOnDeviceRuntime(_splashProgress).then(function () {
            return LocalCNNService.load(_splashProgress);
        })
        .then(function () {
            _logEngineStatus('local_cnn', 'On-device TF.js model loaded and warmed up during splash.');
            return true;
        })
        ['catch'](function (err) {
            console.error('[AI Engine] LOCAL_CNN pre-load failed:', err.message);
            return false;
        });
    }

    // aws_lambda_only / aws_vision / nyckel / none — verdict comes from the
    // server, so the on-device runtime is never fetched.
    console.info('[AI Engine] AI_ENGINE=' + aiEngine + '. No model pre-load required; on-device runtime not fetched.');
    return Promise.resolve(false);
}

function _resolveActiveEngine(localCNNLoaded) {
    if (localCNNLoaded) return; // local_cnn already logged in _preloadEngines

    var aiEngine = FeatureFlags.getValue('AI_ENGINE') || 'nyckel';

    // 'cloud' was a placeholder that fell through to Nyckel; Swift removed the case and
    // React's adapter no longer has a CloudStrategy, so a stale flag value now takes the
    // unknown-value path (warn, then Nyckel) — identical behaviour, one less dead branch.
    if (aiEngine === 'aws_vision') {
        _logEngineStatus('aws_vision', 'AI_ENGINE=aws_vision. AWS Vision verdict + Local CNN enrichment active.');
        return;
    }
    if (aiEngine === 'aws_lambda_only') {
        _logEngineStatus('aws_lambda_only', 'AI_ENGINE=aws_lambda_only. AWS Vision verdict only — no on-device models loaded.');
        return;
    }
    if (aiEngine === 'cloud_assembly') {
        _logEngineStatus('cloud_assembly', 'AI_ENGINE=cloud_assembly. Cloud Assembly inference API verdict + attention map + indicative species narrative — no on-device models loaded.');
        return;
    }
    if (aiEngine === 'hybrid') {
        _logEngineStatus('hybrid', 'AI_ENGINE=hybrid. Nyckel verdict + Local CNN enrichment active.');
        return;
    }
    if (aiEngine === 'nyckel') {
        _logEngineStatus('nyckel', 'AI_ENGINE=nyckel. Nyckel API active.');
        return;
    }
    if (aiEngine === 'none') {
        _logEngineStatus('none', 'AI_ENGINE=none. All engines disabled.');
        return;
    }
    _logEngineStatus('none', 'AI_ENGINE value unrecognised: ' + aiEngine);
    console.warn('[AI Engine] Unrecognised AI_ENGINE value:', aiEngine);
}

// Boot: init flags → pre-load engines in parallel with splash → render app
FeatureFlags.init()
    .then(function () {
        return _preloadEngines();
    })
    .then(function (localCNNLoaded) {
        _resolveActiveEngine(localCNNLoaded);
        var activeEngine = (window.__aiEngineStatus && window.__aiEngineStatus.active) || 'unknown';
        AnalyticsService.init({ aiEngine: activeEngine, userType: 'guest' });
        root.render(<App />);
        _dismissSplash();
    })
    ['catch'](function (err) {
        console.error('[Boot] FeatureFlags.init() failed:', err.message, '— rendering with flag defaults.');
        _logEngineStatus('nyckel', 'FeatureFlags failed to load. Defaulting to Nyckel.');
        root.render(<App />);
        _dismissSplash();
    });

/**
 * Tells the splash the app is up. Nothing did this before: the splash ran a
 * fixed 15-second timer and the rendered app sat invisible behind it for the
 * remainder — the load delay beta testers were reporting.
 *
 * Deferred one frame past render() so the first paint has actually happened
 * before the overlay starts fading; otherwise a fast boot can cross-fade into a
 * blank frame. index.html enforces its own 2s minimum, so calling this early is
 * safe.
 */
function _dismissSplash() {
    if (typeof window.__mdHideSplash !== 'function') return;
    requestAnimationFrame(function () {
        requestAnimationFrame(function () { window.__mdHideSplash(); });
    });
}
