import type { ReactElement } from "react";
import { flushSync } from "react-dom";
import { createRoot } from "react-dom/client";
import { RenderContext } from "../../components/renderContext";
import { Localisation } from "../../plugins/localisation";
import type { TAppRenderModes } from "../../types";

const getDefaultModifier = (str = "") => str.replaceAll(/<template data-comment="">([^"]*)<\/template>/g, "\n$1");

type TOptions = Partial<{
  before: string;
  after: string;
  getWithModifier: (str: string) => string;
  mode: Exclude<TAppRenderModes, "SSR">;
}>;

/**
 * Получает статичную разметку из React-компонента.
 * Используется в браузерной среде, если вам необходимо получить результат рендера React на клиенте в виде HTML. DOM-дерево должно быть доступно.
 * Функция дожидается загрузки плагина `Localisation`.
 * @param element{ReactElement} компонент
 * @param options{Object=} параметры получения
 * @param options.before{String=} строка, добавляемая до компонента
 * @param options.after{String=} строка, добавляемая после компонента
 * @param options.getWithModifier{Function=} функция, модифицирующая HTML
 * @returns {Promise<string>} Возвращает Promise со строкой HTML, сформированной из переданного React-элемента.
 * @example
 * import { getStaticMarkupForClient } from "@delement/ui/utils/react";
 *
 * const html = await getStaticMarkupForClient(<div>Hello</div>);
 */
const getStaticMarkupForClient = async (element: ReactElement, options?: TOptions): Promise<string> => {

  if (typeof window === "undefined") {
    throw new Error(`[getStaticMarkupForClient] Function was called on server, use "getStaticMarkupForServer" instead`);
  }

  const {
    before = "",
    after = "",
    getWithModifier = getDefaultModifier,
    mode = "CSR",
  } = (options || {});

  if (!element) {
    return Promise.resolve("");
  }

  const div = document.createElement("div");
  const root = createRoot(div);

  const getLayout = () => {
    try {
      flushSync(() => root.render(<RenderContext mode={mode} isSSR={false}>{element}</RenderContext>));
      return getWithModifier(`${before}${div.innerHTML}${after}`);
    } finally {
      root.unmount();
    }
  };

  return new Promise((resolve) => {
    const resolver = () => {
      const layout = getLayout();
      resolve(layout);
    };
    if (Localisation.instance) { // localization are ready
      resolver();
    } else { // wait for localization
      Localisation.create()
        .finally(() => {
          resolver();
        });
    }
  });
};

export {
  getStaticMarkupForClient,
};
