// Build version: 2026-01-27T00:00:00Z
import { createRoot } from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { HelmetProvider } from "react-helmet-async";
import { registerSW } from "virtual:pwa-register";
import App from "./App.tsx";
import "./index.css";
import { logger } from "./lib/logger";
import { resetAppCache } from "./lib/appCache";
import { deferAfterPaint } from "./lib/scheduler";
import { reportClientError } from "./lib/clientErrorLogger";

// Defer non-critical logging until after first paint
deferAfterPaint(() => {
  logger.info('App', 'coldStart', {
    url: window.location.href,
    userAgent: navigator.userAgent.substring(0, 100),
    timestamp: new Date().toISOString(),
  });
});

const rootElement = document.getElementById("root");

if (!rootElement) {
  logger.error('App', 'rootElementMissing');
  throw new Error("Root element not found");
}

/**
 * React Query client
 * - Required for all hooks using @tanstack/react-query (e.g. useTransactions)
 */
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 1,
      refetchOnWindowFocus: false,
    },
  },
});

/**
 * Check if running in Despia native wrapper
 * PWA service workers are not relevant in native iOS/Android wrappers
 */
const isDespiaNative = typeof navigator !== 'undefined' && navigator.userAgent.includes('despia');

// Defer native detection logging
if (isDespiaNative) {
  deferAfterPaint(() => {
    logger.info('Native', 'detected', { wrapper: 'despia' });
  });
}

/**
 * PWA Service Worker Registration
 * 
 * Strategy: Manual update with user notification
 * Skip entirely in Despia native wrapper where service workers may behave unexpectedly
 */
let updateAvailable = false;

// Defer PWA registration until after first paint to reduce TBT
if (!isDespiaNative && 'serviceWorker' in navigator) {
  deferAfterPaint(() => {
    logger.debug('App', 'pwaRegistrationStart');

    const updateSW = registerSW({
      // Called when new content is available and SW is ready
      onNeedRefresh() {
        // Silently update and reload once per session to apply the new version
        const guardKey = 'sbb_pwa_auto_update_attempted';
        if (sessionStorage.getItem(guardKey) === 'true') {
          logger.warn('App', 'pwaAutoUpdateSkipped', { reason: 'already_attempted_this_session' });
          return;
        }

        sessionStorage.setItem(guardKey, 'true');
        updateAvailable = true;
        logger.info('App', 'pwaAutoUpdateAndReload');

        // `updateSW(true)` will activate the new SW and reload the page.
        updateSW(true);
      },

      // Called when app is ready for offline use
      onOfflineReady() {
        logger.info('App', 'pwaOfflineReady');
      },

      // Called if registration fails
      onRegisterError(error) {
        logger.error('App', 'pwaRegistrationError', { error: String(error) });
      },

      // Check for updates on load
      immediate: true,
    });

    // Check for updates periodically (every 5 minutes when online)
    const updateCheckInterval = setInterval(() => {
      if (navigator.onLine && !updateAvailable) {
        updateSW();
      }
    }, 5 * 60 * 1000);

    // Cleanup on page unload (good practice even though this is top-level)
    window.addEventListener('beforeunload', () => {
      logger.debug('App', 'beforeUnload');
      clearInterval(updateCheckInterval);
    }, { once: true });
  });
}

// Global error handler for uncaught errors
window.onerror = (message, source, lineno, colno, error) => {
  logger.error('Error', 'uncaughtError', {
    message: String(message),
    source,
    lineno,
    colno,
    errorName: error?.name,
    errorMessage: error?.message,
  });

  // Proactive error logging to DB
  reportClientError({
    source: 'uncaught',
    errorName: error?.name || 'Error',
    errorMessage: error?.message || String(message),
    stackTrace: error?.stack,
  });

  return false;
};

function isModuleImportFailure(reason: unknown): boolean {
  const msg = String(reason ?? '');
  return (
    msg.includes('Importing a module script failed') ||
    msg.includes('Failed to fetch dynamically imported module')
  );
}

async function recoverFromStaleBundles(trigger: 'unhandledRejection' | 'uncaughtError') {
  const guardKey = 'sbb_module_import_recovery_attempted';
  if (sessionStorage.getItem(guardKey) === 'true') {
    logger.warn('App', 'moduleImportRecoverySkipped', { reason: 'already_attempted_this_session', trigger });
    return;
  }
  sessionStorage.setItem(guardKey, 'true');

  try {
    logger.warn('App', 'moduleImportRecoveryStart', { trigger });
    const { swCount, cacheCount } = await resetAppCache();
    logger.warn('App', 'moduleImportRecoveryCleared', { swCount, cacheCount });
  } catch (e) {
    logger.error('App', 'moduleImportRecoveryFailed', { error: String(e) });
  }

  // Cache-bust reload to avoid reusing a stale HTML/chunk mapping.
  const url = new URL(window.location.href);
  url.searchParams.set('__reload', String(Date.now()));
  window.location.replace(url.toString());
}

// Global handler for unhandled promise rejections
window.onunhandledrejection = (event) => {
  logger.error('Error', 'unhandledRejection', {
    reason: String(event.reason),
  });

  // Proactive error logging to DB
  const reason = event.reason;
  reportClientError({
    source: 'unhandledRejection',
    errorName: reason?.name || 'UnhandledRejection',
    errorMessage: reason?.message || String(reason),
    stackTrace: reason?.stack,
  });

  if (isModuleImportFailure(event.reason)) {
    void recoverFromStaleBundles('unhandledRejection');
  }
};

logger.info('App', 'renderStart');
createRoot(rootElement).render(
  <HelmetProvider>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </HelmetProvider>
);
logger.debug('App', 'renderComplete');


