import { generateAllGlobalStyles } from './bind-styles';
import { getCurrentTheme, updateTheme } from './bind-theme';
import { mountInnerComponent } from './mount-component';
import { renderComponentAsync } from './render-component';
// import { callPageResetEvent } from './page-reset-events';
import { callDomLoadedEvent } from './page-loaded-event';

import { IRequestContextProps, IToClientDelivery } from '../models';
import { getMetaDataObject, getMetaDataTags, getPageTitle } from './bind-meta';
import { initWebEnv } from '../lib/web-env';
import { PageProps, PageResultType } from '../models/page-props';
import { setRenderPageProps } from './export-lupine';
import { isFrontEnd } from '../lib/is-frontend';
import { WebConfig } from '../lib/web-config';
import { bindRequestContext } from './use-request-context';
import { CssProps } from '../jsx';
import { _lupineJs } from './lupine-instance';

const renderTargetPage = async (props: PageProps, renderPartPage: boolean) => {
  if (typeof _lupineJs.router !== 'function') {
    return _lupineJs.router.handleRoute(props.url, props, renderPartPage);
  }
  return await _lupineJs.router(props);
};

// this is called by server side for SSR (server-side-rendering)
const generatePage = async (props: PageProps, toClientDelivery: IToClientDelivery): Promise<PageResultType> => {
  const gThis = globalThis as any;
  if (!gThis.__SSR_ALS_PROPS__) {
    throw new Error('__SSR_ALS_PROPS__ is not defined');
  }
  // Provide isolated tracking for this branch of execution against the shared V8 script Object memory
  // not thread-safe
  // setRenderPageProps(props);

  // can't do bindRequestContext, because it's not thread-safe
  // bindRequestContext(() => toClientDelivery.getRequestContext());
  initWebEnv(toClientDelivery.getWebEnv());
  WebConfig.initFromData(toClientDelivery.getWebSetting());
  // initWebSetting(toClientDelivery.getWebSetting());

  // callPageResetEvent();
  callDomLoadedEvent();

  const jsxNodes = await renderTargetPage(props, false);
  if (!jsxNodes || !jsxNodes.props) {
    return {
      content: `Unexpected url: ${props.url}`,
      title: '',
      metaData: '',
      globalCss: '',
      themeName: getCurrentTheme().themeName,
    };
  }

  await renderComponentAsync(jsxNodes.type, jsxNodes.props);
  const currentTheme = getCurrentTheme();
  const cssText = generateAllGlobalStyles();
  const content = jsxNodes.props._html.join('');

  return {
    content,
    title: getPageTitle(),
    metaData: getMetaDataTags(),
    globalCss: cssText,
    themeName: currentTheme.themeName,
    // metaObject: getMetaDataObject(),
  };
};
_lupineJs.generatePage = generatePage;

const _initSaved = {
  pageInitialized: false,
  appInitialized: false,
};
// this is called in the FE when the document is loaded
// to avoid circular reference, bindLinks can't call initializePage directly
export const initializePage = async (newUrl?: string) => {
  // Default context for Browser (singleton)
  const defaultContext: IRequestContextProps = {
    pageTitle: '',
    metaData: {},
    themeName: '',
    langName: '',
    globalStyles: new Map<string, { topUniqueClassName: string; noTopClassName: boolean; style: CssProps }>(),
    globalStyleIds: new Map<CssProps, string>(),
    coreData: {}, // for core development
    devData: {}, // for secondary development
  };
  bindRequestContext(() => defaultContext);

  const json = document.querySelector('#web-env')?.textContent;
  if (json) {
    initWebEnv(JSON.parse(json));
  } else {
    console.warn('web-env is not defined');
  }
  const json2 = document.querySelector('#web-setting')?.textContent;
  if (json2) {
    WebConfig.initFromData(JSON.parse(json2));
  } else {
    console.warn('web-setting is not defined');
  }

  const currentPageInitialized = _initSaved.pageInitialized;
  _initSaved.pageInitialized = true;
  console.log('initializePage: ', newUrl);
  if (newUrl) {
    window.history.pushState({ urlPath: newUrl }, '', newUrl);
    // prevents browser from storing history with each change:
    // window.history.replaceState({ html: '', pageTitle: newUrl }, '', newUrl);
  }
  const splitUrl = newUrl ? newUrl.split('?') : [];
  const url = splitUrl[0] || document.location.pathname;
  const queryString = splitUrl[1] || document.location.search;

  const props: PageProps = {
    url,
    // urlSections: url.split('/').filter((i) => !!i),
    query: Object.fromEntries(new URLSearchParams(queryString)), // new URLSearchParams(queryString),
    urlParameters: {},
    renderPageFunctions: _lupineJs.renderPageFunctions,
  };

  setRenderPageProps(props);
  // can only call callPageLoadedEvent once after page refreshed
  !currentPageInitialized && callDomLoadedEvent();

  const jsxNodes = await renderTargetPage(props, currentPageInitialized);
  if (jsxNodes === null) return;
  if (!jsxNodes || !jsxNodes.props) {
    document.querySelector('.lupine-root')!.innerHTML = `Error happened or unexpected url: ${url}`;
    return;
  }

  // generateAllGlobalStyles will be updated directly in Browser
  await mountInnerComponent('.lupine-root', jsxNodes);
  updateTheme(getCurrentTheme().themeName);

  // title
  document.title = getPageTitle();
  const metaData = getMetaDataObject();
  // meta data?
};
_lupineJs.initializePage = initializePage;

// this is called in bindRouter to avoid tree shaking
export const initializeApp = () => {
  if (isFrontEnd()) {
    if (_initSaved.appInitialized) return;
    _initSaved.appInitialized = true;

    addEventListener('popstate', (event) => {
      initializePage();
    });
    addEventListener('load', (event) => {
      let redirect = new URLSearchParams(window.location.search).get('redirect');
      if (!redirect) {
        redirect = new URLSearchParams(window.location.hash.substring(1)).get('redirect');
      }
      initializePage(redirect || undefined);
    });
  }

  // for SSR, it exports _lupineJs function for the server to call
  // this should be loaded in a sandbox
  if (typeof globalThis !== 'undefined') {
    const gThis = globalThis as any;
    if (gThis._lupineJs === null) {
      gThis._lupineJs = () => {
        return _lupineJs;
      };
    }
  }
  // if (typeof exports !== 'undefined') {
  //   // ignore esbuild's warnings:
  //   // The CommonJS "exports" variable is treated as a global variable in an ECMAScript module and may not work as expected [commonjs-variable-in-esm]
  //   exports._lupineJs = () => {
  //     return _lupineJs;
  //   };
  // }
};
