import { createApp, defineAsyncComponent, defineComponent, h } from 'vue';
import { createPinia } from 'pinia';
import i18next from 'i18next';
import i18nFetchBackend from 'i18next-fetch-backend';
import I18NextVue from 'i18next-vue';
import { getQueryClient } from 'network-layer/plugin/getQueryClient';
import { initializeCSR } from 'network-layer/plugin/initializerCSR';
import { isEnvironmentProd } from 'shared-library/utils/detectEnvironment';
import { mark, measure } from 'shared-library/utils/performanceMarks';
import AxiosPlugin, { updateURLs } from 'shared-services/Axios/AxiosPlugin';
import $axios from 'shared-services/Axios/AxiosService';
import { initConsentDefaults } from 'shared-services/CookieConsentService';
import EventBusPlugin from 'shared-services/EventBus/EventBusPlugin';
import { isLangCodePTBR } from 'shared-services/LocaleUtils';
import RenderService from 'shared-services/RenderService';
import QRouterPlugin from 'shared-services/Router/QRouterPlugin';
import { useServerDataStore } from 'shared-services/serverData';
import {
  register,
  start as startThirdPartyScheduler,
} from './ThirdPartyScriptScheduler';
import UserActionsService from 'shared-services/User/UserActionsService';
import UserPlugin from 'shared-services/User/UserPlugin';
import useCurrentUserStore from 'shared-services/User/currentUser';
import useUserStore from 'shared-services/User/userStore';
import UtilitiesPlugin from 'shared-services/UtilitiesPlugin';
import useRootStore from 'shared-services/rootStore';
import clickOutsidePlugin from 'sphagetti/plugins/v-click-outside';
import getCurrentDeviceProperties from 'utilities/DeviceConfigUtils';
import { substituteLocalizedKeyword } from 'utilities/i18nUtils';
import App from './App.vue';
import { resolveCurrentUser, seedCurrentUserQueryCache } from './earlyCurrentUser';

import getRouter from './router';
import { warmSettingsRouteChunk } from './utils/warmRouteChunks';

import type { ICommonStoreProps, TPlatform } from 'analytics-client/types';
import type { IMetaData } from 'shared-domains/types/metaData';
import type UserDomain from 'shared-services/User/UserService';

const FA = defineAsyncComponent(() => import('shared-components/FA.vue'));
const VImage = defineAsyncComponent(() => import('sphagetti/components/VImage.vue'));

document.title = 'Wayground';

mark('admin:bootstrap:start');

const app = createApp(App);

function getFontAwesomeClass(icon: unknown) {
  if (Array.isArray(icon)) {
    return `${icon[0]} fa-${icon[1]}`;
  }
  if (typeof icon === 'string') {
    return icon;
  }
  if (icon && typeof icon === 'object' && 'prefix' in icon && 'iconName' in icon) {
    const { prefix, iconName } = icon as { prefix: string; iconName: string };
    return `${prefix} fa-${iconName}`;
  }
  return '';
}

// setup pinia store
const piniaStore = createPinia();
app.use(piniaStore);

// setup router
const router = getRouter(app);
window.hack_router = router || window.hack_router;

// Sentry is deferred to idle tier to avoid 800ms+ script eval on the critical path.
// Errors before init are buffered and replayed.
const earlyErrors: Array<{ error: unknown; context?: Record<string, unknown> }> = [];
function captureException(error: unknown, context?: Record<string, unknown>) {
  earlyErrors.push({ error, context });
}

register(async () => {
  const [{ init: SentryInit, browserTracingIntegration, captureException: sentryCaptureException }, { isSentryEnabled }, sentryConfig] = await Promise.all([
    import('@sentry/vue'),
    import('utilities/EnvUtils'),
    import('utilities/sentryConfig').then(m => m.default),
  ]);

  if (!isSentryEnabled()) {return;}

  SentryInit({
    app,
    dsn: 'https://54ecec4aa96ff7a37f92e832ce0e66d5@o984659.ingest.us.sentry.io/4511252872757248',
    tracesSampleRate: 0.05,
    sampleRate: 1,
    ignoreErrors: sentryConfig.ignoredErrors,
    beforeBreadcrumb: sentryConfig.beforeBreadcrumb,
    maxBreadcrumbs: 30,
    integrations: [
      browserTracingIntegration({
        router,
        enableHTTPTimings: false,
      }),
    ],
    denyUrls: sentryConfig.denyUrls,
  });

  // Replay buffered errors
  for (const { error, context } of earlyErrors) {
    sentryCaptureException(error, context as any);
  }
  earlyErrors.length = 0;
}, 'route');


i18next.use(i18nFetchBackend);

app.use(I18NextVue, { i18next });

app.use(EventBusPlugin);
app.use(AxiosPlugin);
app.use(UtilitiesPlugin);

app.component(
  'InjectedFontAwesomeIcon',
  defineComponent({
    props: {
      icon: {
        type: [Array, Object, String],
        required: true,
      },
    },
    setup(props) {
      return () => h('i', { class: getFontAwesomeClass(props.icon) });
    },
  }),
);
app.provide('InjectedFontAwesomeDefinitions', { far: {}, fas: {}, fab: {} });

app.component('FA', FA);
app.component('VImage', VImage);

initConsentDefaults();

// Add VueQueryPlugin to the app and pass in the queryClient
initializeCSR(app);

// Defer FloatingVue — tooltips are not needed until user interaction
register(() => {
  import('floating-vue').then(({ default: FloatingVuePlugin }) => {
    app.use(FloatingVuePlugin, {
      themes: {
        'tooltip': { delay: { show: 300, hide: 0 } },
        'error-tooltip-sm': { triggers: ['hover'] },
        'error-tooltip': { triggers: ['hover'] },
        'info-tooltip': { triggers: ['hover'], delay: { show: 300, hide: 0 } },
        'mystic-peak-tooltip': { triggers: ['hover'] },
        'info-popper': { triggers: ['hover'], delay: { show: 300, hide: 0 } },
        'warn-popper': { triggers: ['hover'] },
        'danger-popper': { triggers: ['hover'] },
      },
    });
  });
}, 'idle');

app.directive('click-outside', clickOutsidePlugin);

function resolveLocaleContext(
  getCurrentLanguageToUse: (userLang?: string) => string,
  userLang?: string,
  requestCountry?: string,
) {
  let locale = getCurrentLanguageToUse(userLang);

  if (!userLang && requestCountry === 'TW') {
    locale = window.location.href.includes('lang=en') ? 'en' : 'zh-TW';
  }

  const i18nextLocale = isLangCodePTBR(locale) ? 'pt-BR' : locale;
  return { locale, i18nextLocale };
}

function loadDayJSLocale(locale: string) {
  import('shared-domains/config/DayjsConfig').then(({ default: SupportedLocaleForDayJS }) => {
    if (SupportedLocaleForDayJS[locale as keyof typeof SupportedLocaleForDayJS]) {
      SupportedLocaleForDayJS[locale as keyof typeof SupportedLocaleForDayJS]().then(() => {
        import('dayjs').then(({ default: dayjs }) => dayjs.locale(locale));
      });
    } else {
      import('dayjs').then(({ default: dayjs }) => dayjs.locale('en'));
    }
  });
}

async function initI18n(initialLocale: string) {
  const defaultLocale = 'en';
  let locale = initialLocale;
  let localeForI18next = isLangCodePTBR(locale) ? 'pt-BR' : locale;

  i18next.use({
    type: 'postProcessor',
    name: 'LocalizedKeywordSubstitution',
    process: substituteLocalizedKeyword,
  });

  if (locale === defaultLocale) {
    await i18next.init({
      lng: defaultLocale,
      debug: false,
      interpolation: { prefix: '{$', suffix: '}' },
      fallbackLng: defaultLocale,
      resources: { [defaultLocale]: { translation: {} } },
      partialBundledLanguages: true,
      backend: {
        loadPath: `${import.meta.env.VITE_TRANSLATION_SOURCE_FILE_URL}/{$lng}.json`,
        crossDomain: true,
      },
      postProcess: ['pseudo', 'LocalizedKeywordSubstitution'],
      nsSeparator: ':Q:',
    });
    useRootStore().setTranslations({});
    useRootStore().setEnglishMap({});
    return;
  }

  const localeConstantsModule = await import('shared-domains/utils/LocaleConstants');
  const supportedLngs = localeConstantsModule.TranslatedLanguages.map((lang) => {
    if (isLangCodePTBR(lang.code)) {
      return 'pt-BR';
    }
    return lang.code;
  });

  if (i18next.isInitialized) {
    i18next.options.supportedLngs = supportedLngs;

    let loaded = false;
    try {
      await i18next.loadLanguages(localeForI18next);
      loaded = i18next.hasResourceBundle(localeForI18next, 'translation');
    } catch (error) {
      // oxlint-disable-next-line no-console
      console.error('Failed to load translation', error);
    }

    if (!loaded) {
      try {
        await i18next.loadLanguages(localeForI18next);
        loaded = i18next.hasResourceBundle(localeForI18next, 'translation');
      } catch {
        // silent — fallback below
      }
    }

    if (loaded) {
      await i18next.changeLanguage(localeForI18next);
    } else {
      // oxlint-disable-next-line no-console
      console.error(`Translation load failed for ${localeForI18next}, falling back to English`);
      locale = defaultLocale;
      localeForI18next = defaultLocale;
    }
  } else {
    await i18next.init({
      lng: localeForI18next,
      debug: false,
      interpolation: { prefix: '{$', suffix: '}' },
      supportedLngs,
      fallbackLng: defaultLocale,
      resources: { [defaultLocale]: { translation: {} } },
      partialBundledLanguages: true,
      backend: {
        loadPath: `${import.meta.env.VITE_TRANSLATION_SOURCE_FILE_URL}/{$lng}.json`,
        crossDomain: true,
      },
      postProcess: ['pseudo', 'LocalizedKeywordSubstitution'],
      nsSeparator: ':Q:',
    });

    if (!i18next.hasResourceBundle(localeForI18next, 'translation')) {
      let loaded = false;

      try {
        await i18next.loadLanguages(localeForI18next);
        loaded = i18next.hasResourceBundle(localeForI18next, 'translation');
      } catch (error) {
        // oxlint-disable-next-line no-console
        console.error('Failed to load translation', error);
        captureException(error, {
          tags: { translation_loading: true, language: localeForI18next },
          level: 'warning',
        });
      }

      if (!loaded) {
        try {
          await i18next.loadLanguages(localeForI18next);
          loaded = i18next.hasResourceBundle(localeForI18next, 'translation');
        } catch {
          // silent — fallback below
        }
      }

      if (!loaded) {
        // oxlint-disable-next-line no-console
        console.error(`Translation load failed for ${localeForI18next}, falling back to English`);
        captureException(
          new Error(`Failed to load ${localeForI18next}, falling back to English`),
          {
            tags: { translation_loading: true, language: localeForI18next, fallback_triggered: true },
            level: 'error',
          },
        );
        await i18next.changeLanguage(defaultLocale);
        locale = defaultLocale;
        localeForI18next = defaultLocale;
      }
    }
  }

  loadDayJSLocale(locale);

  // Non-blocking: load legacy translation maps
  (async () => {
    const defaultLoc = 'en';
    let translationMap;
    let englishMap: Record<string, any> = {};

    if (locale !== defaultLoc) {
      try {
        translationMap = await import(`../translations/${locale}.json`);
        if (!translationMap) {
          translationMap = await import(`../translations/${defaultLoc}.json`);
        }
        if (locale !== 'en') {
          englishMap = await import(`../translations/${defaultLoc}.json`);
        }
      } catch {
        translationMap = await import(`../translations/${defaultLoc}.json`);
      }
    }

    useRootStore().setTranslations(translationMap?.default ?? {});
    useRootStore().setEnglishMap(englishMap.default ?? {});
  })();
}

function removeInitialLoader() {
  const spinner: HTMLElement | null = document.querySelector('#initial-loader-container');

  if (spinner) {
    spinner.style.display = 'none';
    spinner.remove();
  }
}

function setMetaData(metaData?: Partial<IMetaData>) {
  (window as any).metaData = {
    ...metaData,
    googleClientId: isEnvironmentProd()
      ? import.meta.env.VITE_GOOGLE_CLIENT_ID_PROD
      : import.meta.env.VITE_GOOGLE_CLIENT_ID_DEV,
    stripePublishableKey: isEnvironmentProd()
      ? import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY_PROD
      : import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY_DEV,
  };
}

async function hydrateUser() {
  mark('admin:hydrate-user:start');
  const userStore = useUserStore();
  const serverDataStore = useServerDataStore();
  try {
    serverDataStore.localClientInit();
    useRootStore().setDeviceInfoFromClient();
    updateURLs();

    UserActionsService.setGACookies().catch(() => {});

    const [user, geoData] = await Promise.all([
      resolveCurrentUser(() => UserActionsService.getCurrentUser($axios)),
      RenderService.getGeoData(),
    ]);

    seedCurrentUserQueryCache(user, getQueryClient());

    if (user) {
      userStore.setUser(user, true);
      useCurrentUserStore().setInitialUserTags(user.tags ? user.tags : []);
    }

    if (geoData && Object.keys(geoData).length > 0) {
      serverDataStore.setRequestCountry(geoData.requestCountry ?? '');
      serverDataStore.setRegion(geoData.region ?? '');
    }

    setMetaData({
      ...geoData,
      user,
    });

    serverDataStore.setCredentials({
      googleClientId: (window as any).metaData?.googleClientId || '',
      stripePublishableKey: (window as any).metaData?.stripePublishableKey || '',
    });

    app.use(UserPlugin, { user });

    // Deferred: user diff check in idle
    window?.requestIdleCallback?.(() => {
      Promise.all([
        import('shared-services/User/UserServiceShared'),
        import('shared-services/userDiffChecker'),
      ]).then(([{ default: UserService }, { mergeAndCompareUserObjects }]) => {
        UserService.getCoreUserData()
          .then((userCoreData) => {
            mergeAndCompareUserObjects(user, userCoreData, true);
          })
          .catch(() => {});
      });
    });

    return { user, geoData, userStore, serverDataStore };
  } finally {
    mark('admin:hydrate-user:end');
    measure('admin:hydrate-user:measure', 'admin:hydrate-user:start', 'admin:hydrate-user:end');
  }
}

async function bootstrap() {
  const appDOM = document.querySelector('#app') as HTMLDivElement;

  // Warm the route chunk while bootstrap is in flight.
  // Fire-and-forget so the chunk downloads in parallel with user hydration.
  warmSettingsRouteChunk();

  // Parallelize: optimistic English i18n + user hydration + locale utils
  const i18nPromise = (async () => {
    mark('admin:i18n:start');
    try {
      await initI18n('en');
    } finally {
      mark('admin:i18n:end');
      measure('admin:i18n:measure', 'admin:i18n:start', 'admin:i18n:end');
    }
  })();

  const [{ getCurrentLanguageToUse }, hydrated] = await Promise.all([
    import('shared-services/LocaleUtils'),
    hydrateUser(),
    i18nPromise,
  ]);

  // If user's actual locale differs from optimistic 'en', correct it now
  const { locale: userLocale, i18nextLocale: userI18nextLocale } = resolveLocaleContext(
    getCurrentLanguageToUse,
    hydrated.user?.preferences?.lang,
    hydrated.serverDataStore.requestCountry,
  );
  if (userI18nextLocale !== 'en') {
    mark('admin:i18n-correction:start');
    await initI18n(userLocale);
    mark('admin:i18n-correction:end');
    measure('admin:i18n-correction:measure', 'admin:i18n-correction:start', 'admin:i18n-correction:end');
  }

  // Mount with user + translations ready
  app.use(QRouterPlugin(router));

  mark('admin:mount:start');
  app.mount(appDOM);
  mark('admin:mount:end');
  measure('admin:mount:measure', 'admin:mount:start', 'admin:mount:end');

  // Start third-party script scheduler
  startThirdPartyScheduler({
    routerReady: router.isReady(),
    idleMaxWaitMs: 6_000,
    analyticsMaxWaitMs: 10_000,
    backgroundMaxWaitMs: 15_000,
    quietMs: 5_000,
    idleQuietMs: 2_000,
    networkQuietMs: 1_000,
  });

  // --- idle tier ---
  register(() => {
    import('./assets/css/katex.css');
    import('./assets/css/global.css');
  }, 'idle');


  // --- late tier ---
  register(async () => {
    const { installGTag, installGTM } = await import('shared-services/gtag');
    installGTag(router, { deferScriptLoad: true });
    installGTM(app, { defer: true });
  }, 'late');

  register(async () => {
    const { default: BrazePlugin } = await import('sphagetti/plugins/BrazePlugin');
    app.use(BrazePlugin, { user: hydrated.user });
  }, 'late');

  register(async () => {
    const { default: setupSentryUserDetails } = await import('sphagetti/plugins/SentryPlugin');
    setupSentryUserDetails();
  }, 'late');

  // --- background tier ---
  register(async () => {
    const { installRybena } = await import('shared-services/RybenaAccessibilityToolBarLoaderService');
    installRybena();
  }, 'background');

  // Analytics — fire-and-forget, not blocking
  import('shared-services/AnalyticsV2').then(
    async ({ AnalyticsV2, attachCommonStoreUpdateListener }) => {
      attachCommonStoreUpdateListener();
      const { default: detectEnvironment } = await import('shared-library/utils/detectEnvironment');
      const commonProps = {
        environment: detectEnvironment(),
        screenType: getCurrentDeviceProperties({}).type,
        locale: userLocale,
        country: hydrated.serverDataStore.requestCountry,
        platform: 'web' as TPlatform,
        featureFlags: {},
        userId: (hydrated.userStore.user as UserDomain)?.id,
        subPlanId:
          hydrated.userStore.userService?.subscription?.planId || hydrated.userStore.userService?.subscription?.id,
        region: hydrated.serverDataStore.region,
        appName: 'user-profile',
      };
      import('shared-services/LtiCommonUtils').then(({ getLTIContext }) => {
        const ltiContext = getLTIContext();
        if (ltiContext) {
          commonProps.screenType = `lti-${ltiContext.platform}`;
        }
      }).catch(() => {});
      AnalyticsV2.init({ commonStoreProps: commonProps as ICommonStoreProps });
    },
  );

  mark('admin:router-ready:start');
  router.isReady().then(async () => {
    mark('admin:router-ready:end');
    measure('admin:router-ready:measure', 'admin:router-ready:start', 'admin:router-ready:end');
    setTimeout(removeInitialLoader);
    import('shared-services/LtiCommonUtils').then(async ({ getLTIContext }) => {
      if (getLTIContext()) {
        const { setupAndSyncLTIContext } = await import('lti/initiate');
        await setupAndSyncLTIContext(app);
      }
    }).catch(() => {});
  });
}

bootstrap();
