import '@/helpers/additionalScripts/stylesInjectorRuntime';
import './config/public-path';
import { createRoot } from 'react-dom/client';
import '@/lib/i18n';

import { Logger, logger } from '@/lib/api/logger';
import { initPostMessage } from '@/lib/api/postMessageAPI';
import { Socket } from '@/lib/api/Socket';
import { SportsbookSocketManager } from '@/lib/api/SportsbookSocketManager';
import { mobx } from '@/lib/mobx';
import { ensureSpriteInjected } from '@/helpers/additionalScripts/spriteInjector';
import stylesInjector from '@/helpers/additionalScripts/stylesInjector';
import { eventListenersHandler } from '@/helpers/eventListenersHandler';
import { importGoogleFont } from '@/helpers/importGoogleFont';
import { isShadowMode } from '@/config';
import { defaultFontURL } from '@/config/constants';

import { App } from './app/App';
import iconsUrl from '@/assets/images/sprites/sprite-icons.svg';

declare global {
  interface Window {
    Sportsbook?: {
      destroy: () => void;
      shadowRoot: ShadowRoot;
      appContainer: HTMLElement;
    };
    __SB_app?: {
      mount: () => void;
      unmount: () => void;
      ready: Promise<void>;
    };
  }
}
eventListenersHandler();
// ---- Module-level setup (runs once per bundle load) -----------------------
//
// Anything that mutates global state non-idempotently or registers a global
// listener lives at module top level so it runs exactly once, regardless of
// how many destroy/init cycles the partner page goes through.

Logger.init();

// `initPostMessage` adds `window.addEventListener('message', listener)`.
// Calling it twice would double-register. Top-level only.
initPostMessage();

// Radix UI uses `document.getElementById` to verify accessibility elements
// (e.g. `Dialog.Title`), but those elements live inside the shadow root and
// are invisible to the light-DOM lookup. Patching the prototype once here
// makes all Radix checks work correctly across destroy/init cycles — the
// patch reads the current shadow root from `window.Turbopredict` on every
// call, so it stays correct after a fresh `init()` swaps shadow roots.
if (isShadowMode) {
  const _origGetById = Document.prototype.getElementById;
  Document.prototype.getElementById = function (id) {
    const shadowRoot = window.Sportsbook?.shadowRoot;
    return _origGetById.call(this, id) ?? shadowRoot?.getElementById(id) ?? null;
  };
}

// ---- Per-mount lifecycle (runs every `Turbopredict.init()`) ----------------

let currentRoot: Root | null = null;

/**
 * Resolves the DOM node that React should mount into.
 *
 * In shadow mode the bootstrap layer (mainInjector) creates the shadow root
 * and the app container before calling `mount()`. In non-shadow dev mode we
 * fall back to creating an `#app-container` ourselves.
 */
function getMountTarget(): HTMLElement {
  // `#sportsbook-app` is the single host element created by the external bootstrap layer.
  const hostElement = document.getElementById('sportsbook-app');

  if (!hostElement) {
    throw new Error('Sportsbook host element "#sportsbook-app" not found.');
  }

  // Reuse the app mount node when bootstrap code has already prepared it.
  // Fall back to creating it only for non-shadow scenarios.
  let appContainer: HTMLElement | null = window.Sportsbook?.appContainer || null;

  if (!appContainer) {
    appContainer = document.createElement('div');
    appContainer.id = 'app-container';
    hostElement.append(appContainer);
  }

  // In non-shadow mode the app behaves like a regular DOM app, so sprite injection targets `document`.
  if (!isShadowMode) {
    ensureSpriteInjected(document, iconsUrl).catch((error) => {
      logger.error('Error injecting sprite into document:', error);
    });
    return appContainer;
  }

  // In shadow mode we expect the bootstrap layer to create and expose the shadow root before React starts.
  const shadowRoot: ShadowRoot | null = window.Sportsbook?.shadowRoot || hostElement.shadowRoot;

  if (!shadowRoot) {
    throw new Error('Sportsbook shadowRoot was not created before app bootstrap.');
  }

  if (isShadowMode) {
    appContainer.classList.add('_shadow-root-mode');
  }

  mobx.ui.setAppRoot(shadowRoot);

  importGoogleFont({ googleFontUrl: defaultFontURL, isInstallFont: true }).catch((error) => {
    logger.error('Error loading default font:', error);
  });

  ensureSpriteInjected(shadowRoot, iconsUrl).catch((error) => {
    logger.error('Error injecting sprite into shadow root:', error);
  });

  return appContainer;
}

function mount(): void {
  if (currentRoot) {
    logger.warn('Sportsbook.mount called while already mounted; unmounting first');
    unmount();
  }

  try {
    // CSS layer definitions live inside the shadow tree, so they must be
    // injected for every fresh shadow root the bootstrap creates.
    stylesInjector({ type: 'inject-layers' });

    // Reset socket-readiness gate before the new tree renders. The flag is
    // module-singleton state on `mobx.ui` and would otherwise carry over from
    // the previous cycle as `true`, letting `Layout` render `<Outlet />`
    // immediately and components fire RPCs against a not-yet-open WebSocket
    // (where `SocketEmitter.send` silently drops them). `Socket.initSocket`
    // re-sets it to `true` once the new connection's `connected` handshake
    // resolves.
    mobx.ui.setSocketConfigIsLoaded(false);

    // Reset theme applying before mount to apply theme again
    mobx.ui.isSettledCoreTheme = false;

    const mountTarget = getMountTarget();
    const root = createRoot(mountTarget);
    currentRoot = root;

    root.render(<App />);
  } catch (error) {
    logger.error('Error during Sportsbook App rendering', {
      error: error instanceof Error ? error : new Error(String(error)),
    });
    if (window.Sportsbook) {
      window.Sportsbook.destroy();
      Socket.closeSocket();
    }
  }
}

function unmount(): void {
  if (!currentRoot) {
    return;
  }
  try {
    // Close the socket synchronously (WS instance is owned by a singleton that
    // survives destroy → init cycles). Without this, the next AuthResolver
    // mount may see `getIsWSReady() === true` against a stale, half-closed
    // connection and short-circuit `initSocket` instead of reconnecting.
    Socket.closeSocket();

    // Mark re-init so AuthResolver's bootstrap effect goes through the
    // `closeSocketAndReInit` + `updateBalance` path on the next mount.
    mobx.auth.setIsReInit(true);
    mobx.store.clear();

    SportsbookSocketManager.reset();

    currentRoot.unmount();
  } catch (error) {
    logger.error('Error during Sportsbook App unmount', {
      error: error instanceof Error ? error : new Error(String(error)),
    });
  }
  currentRoot = null;
}

if (!isShadowMode) {
  mount();
}

window.__SB_app = {
  mount,
  ready: Promise.resolve(),
  unmount,
};
