import type { Monaco, OnMount } from "@monaco-editor/react";
import { createStore } from "idb-keyval";
import trim from "lodash.trim";
import { FC, useEffect } from "react";
import { compress } from "wasm-gzip";
import { useGlobal, useLocal } from "web-utils";
import { ParsedScope } from "../../../../../../../srv/ws/sync/editor/parser/parse-js";
import { jscript } from "../../../../../utils/script/jscript";
import { jsMount } from "../../../../../utils/script/mount";
import { monacoTypings } from "../../../../../utils/script/typings";
import { Loading } from "../../../../../utils/ui/loading";
import { getActiveMeta } from "../../../logic/active/get-meta";
import { EDGlobal, IMeta, active } from "../../../logic/ed-global";
import { edMonacoDefaultVal } from "./default-val";
import { declareScope } from "./scope/scope";
// @ts-ignore
import { constrainedEditor } from "constrained-editor-plugin/dist/esm/constrainedEditor";
import { addScope } from "./scope/add-scope";
import { FNCompDef } from "../../../../../utils/types/meta-fn";

const scriptEdit = {
  timeout: null as any,
};

const IMPORT_SEPARATOR = "//SCRIPT//";

const encode = new TextEncoder();
export type MonacoEditor = Parameters<OnMount>[0];
export const EdScriptMonaco: FC<{}> = () => {
  const p = useGlobal(EDGlobal, "EDITOR");
  const local = useLocal({
    editor: null as null | MonacoEditor,
    monaco: null as null | Monaco,
    changeTimeout: 0 as any,
    init: false,
    value: "",
    historyOpen: false,
    mode: "",
    imports: "",
    active_id: "",
    idbstore: createStore(`prasi-page-${p.page.cur.id}`, "script-history"),
  });

  const Editor = jscript.editor;
  if (!Editor) return <Loading backdrop={false} note="loading-monaco" />;

  let meta: IMeta | null = p.page.meta[active.item_id];
  if (active.comp_id) {
    if (p.comp.list[active.comp_id]) {
      meta = p.comp.list[active.comp_id].meta[active.item_id];
    } else meta = null;
  }

  let val = "";
  useEffect(() => {
    return () => {
      p.script.do_edit = async () => {};
    };
  }, []);

  useEffect(() => {
    clearTimeout(scriptEdit.timeout);
    (async () => {
      const editor = local.editor;
      const monaco = local.monaco;

      if (monaco && editor) {
        const type = p.ui.popup.script.type;
        if (
          local.mode !== p.ui.popup.script.mode ||
          local.active_id !== active.item_id
        ) {
          local.init = false;
          local.mode = p.ui.popup.script.mode;
          local.active_id = active.item_id;
        }

        if (!local.init) {
          monaco.editor.getModels().forEach((model) => {
            if (
              model.uri.toString().startsWith("inmemory://model") ||
              model.uri.toString().startsWith("file://")
            ) {
              model.dispose();
            }
          });

          if (meta) {
            if (type === "item") {
              if (!meta.item.adv) {
                meta.item.adv = {};
              }

              if (!meta.item.adv[p.ui.popup.script.mode]) {
                meta.item.adv[p.ui.popup.script.mode] = val;
              }
            }
          }

          if (p.ui.popup.script.mode === "js") {
            const w = window as any;
            const types: any = {};
            for (const prop_name of p.global_prop) {
              if (prop_name === "_types") continue;
              types[prop_name] = "any";
            }

            if (w._types) {
              for (const [k, v] of Object.entries(w._types)) {
                types[k] = v;
              }
            }

            let component = { id: "", props: {} as Record<string, FNCompDef> };
            if (meta?.item.component?.id && meta.item.component.props) {
              component.id = meta.item.component.id;
              component.props = meta.item.component.props;
            }
            if (meta?.parent?.comp_id && meta.parent.instance_id) {
              const comp_meta = p.page.meta[meta.parent.instance_id];

              if (comp_meta && comp_meta.item.component?.id) {
                component.id = comp_meta.item.component.id;
                component.props = comp_meta.item.component.props;
              }
            }

            if (meta) {
              switch (type) {
                case "prop-master":
                  {
                    const nmodel = monaco.editor.createModel(
                      val,
                      "typescript",
                      monaco.Uri.parse("file:///active.tsx")
                    );
                    editor.setModel(nmodel);
                  }
                  break;
                case "prop-instance":
                  {
                    const nmodel = monaco.editor.createModel(
                      val,
                      "typescript",
                      monaco.Uri.parse("file:///active.tsx")
                    );
                    editor.setModel(nmodel);

                    if (component.id) {
                      const prop_name = p.ui.popup.script.prop_name;
                      const prop = component.props[prop_name];
                      if (typeof prop.typings === "string") {
                        const typings_src = prop.typings.substring(
                          `const typings = `.length
                        );
                        const typings_fn = new Function(
                          `return ${typings_src}`
                        );
                        const typings = typings_fn();
                        for (const [k, v] of Object.entries(typings)) {
                          if (typeof v === "string") {
                            types[k] = v;
                          }
                        }
                      }
                    }
                  }
                  break;
                case "item":
                  {
                    const { exports, imports } = declareScope(p, meta, monaco);
                    const im = imports[active.item_id];
                    const im_src = [];

                    if (im) {
                      for (const [var_name, item] of Object.entries(im)) {
                        im_src.push(
                          `import { ${var_name} } from "./${item.id}_${var_name}_${item.type}";`
                        );
                      }
                    }
                    for (const [k, v] of Object.entries(exports)) {
                      addScope(p, monaco, `file:///${k}`, v);
                    }
                    let active_src =
                      im_src.length > 0
                        ? `${im_src.join("\n")}\n${IMPORT_SEPARATOR}\n${val}`
                        : val;
                    const model = monaco.editor.createModel(
                      active_src,
                      "typescript",
                      monaco.Uri.parse("file:///active.tsx")
                    );
                    editor.setModel(model);
                    editor.trigger("fold", "editor.foldAllMarkerRegions", {});
                    if (active_src) {
                      const end_hide = active_src
                        .split(IMPORT_SEPARATOR)[0]
                        .split("\n").length;
                      const range = new monaco.Range(1, 1, end_hide, 1);
                      (editor as any).setHiddenAreas([range]);
                    }

                    if (component.id && meta.jsx_prop?.name) {
                      const prop_name = meta.jsx_prop.name;
                      const prop = component.props[prop_name];
                      if (typeof prop.typings === "string") {
                        const typings_src = prop.typings.substring(
                          `const typings = `.length
                        );
                        const typings_fn = new Function(
                          `return ${typings_src}`
                        );
                        const typings = typings_fn();
                        for (const [k, v] of Object.entries(typings)) {
                          if (typeof v === "string") {
                            types[k] = v;
                          }
                        }
                      }
                    }
                  }
                  break;
              }
            }

            await monacoTypings(
              {
                site_dts: p.site_dts,
                script: {
                  siteTypes: p.script.site_types,
                },
                site: p.site.config,
              },
              monaco,
              {
                types,
                values: {},
              }
            );
            await jsMount(editor, monaco, p);
          } else {
            const model = monaco.editor.createModel(
              val,
              { css: "scss", js: "typescript", html: "html" }[
                p.ui.popup.script.mode
              ],
              monaco.Uri.parse(`inmemory://model/1`)
            );
            editor.setModel(model);
          }

          local.init = true;
          local.value = val;
          local.render();
        }
      }
    })();
  }, [active.item_id, local.monaco, local.editor, p.ui.popup.script.mode]);

  if (!meta) return <Loading backdrop={false} note="meta-not-found" />;

  const item = meta.item;
  const adv = meta.mitem?.get("adv")?.toJSON() || {};
  item.adv = adv;

  const doEdit = async (newval: string, all?: boolean) => {
    if (local.editor) {
      const prettier = jscript.prettier.standalone;
      const prettier_ts = jscript.prettier.ts;
      const prettier_estree = jscript.prettier.estree;

      if (prettier && prettier_estree && prettier_ts) {
        let curval = local.editor
          ?.getValue()
          .replace(/\{\s*children\s*\}/gi, newval);

        if (curval.includes(IMPORT_SEPARATOR)) {
          curval = curval.split(IMPORT_SEPARATOR + "\n").pop() || "";
        }

        const text = trim(
          await prettier.format(all ? newval : curval, {
            parser: "typescript",
            plugins: [prettier_ts, prettier_estree],
          }),
          "; \n"
        );

        let final_src = text;
        if (local.imports) {
          final_src = `${local.imports}\n${IMPORT_SEPARATOR}\n${text}`;
        }
        local.editor.executeEdits(null, [
          {
            range: {
              startLineNumber: 0,
              startColumn: 0,
              endColumn: Number.MAX_SAFE_INTEGER,
              endLineNumber: Number.MAX_SAFE_INTEGER,
            },
            text: final_src,
          },
        ]);
      }
    }
  };
  p.script.do_edit = doEdit;

  let mitem = meta.mitem;

  if (!mitem) {
    active.item_id = "";
    return <div>no mitem</div>;
  } else if (item.type === "item" && item.component?.id) {
    if (!mitem) {
      active.item_id = "";
      return <div>no mitem</div>;
    }
  }

  val = edMonacoDefaultVal(p, adv, mitem);
  return (
    <Editor
      loading={<Loading backdrop={false} note="monaco-loading" />}
      options={{
        minimap: { enabled: false },
        wordWrap: "wordWrapColumn",
        autoClosingBrackets: "always",
        autoIndent: "full",
        formatOnPaste: true,
        formatOnType: true,
        tabSize: 2,
        useTabStops: true,
      }}
      language={
        { css: "scss", js: "typescript", html: "html" }[p.ui.popup.script.mode]
      }
      onChange={(value) => {
        const stype = p.ui.popup.script.type;
        p.ui.popup.script.typings.status = "loading";
        p.ui.popup.script.wb_render();

        if ((value || "").includes(IMPORT_SEPARATOR)) {
          const valparts = (value || "").split(IMPORT_SEPARATOR);
          if (valparts.length > 1) local.value = valparts[1];
          if (
            stype === "prop-instance" &&
            local.value.includes(
              `export const ${p.ui.popup.script.prop_name} = `
            )
          ) {
            local.value = local.value.substring(
              `export const ${p.ui.popup.script.prop_name} = `.length
            );
          }
        } else {
          local.value = value || "";
        }
        local.render();
        clearTimeout(scriptEdit.timeout);
        const applyChanges = async () => {
          const value = local.value;
          const meta = getActiveMeta(p);
          const mode = p.ui.popup.script.mode;
          if (meta && meta.mitem) {
            let arg = {} as any;
            if (active.comp_id) {
              arg.comp_id = active.comp_id;
            } else {
              arg.page_id = p.page.cur.id;
            }
            let scope: boolean | ParsedScope = false;
            p.ui.popup.script.typings.status = "ok";
            p.ui.popup.script.typings.err_msg = "";
            if (stype === "prop-master") {
              p.sync.code.edit({
                type: "prop-master",
                prop_kind: p.ui.popup.script.prop_kind,
                prop_name: p.ui.popup.script.prop_name,
                value: compress(encode.encode(value || "")),
                ...arg,
              });
            } else if (stype === "prop-instance") {
              const code_result = await p.sync.code.edit({
                type: "prop-instance",
                mode: mode,
                prop_name: p.ui.popup.script.prop_name,
                item_id: active.item_id,
                value: compress(encode.encode(value || "")),
                ...arg,
              });
              if (typeof code_result === "string") {
                p.ui.popup.script.typings.status = "error";
                p.ui.popup.script.typings.err_msg = code_result;
              } else if (typeof code_result === "object") {
                scope = code_result;
              }
            } else {
              const code_result = await p.sync.code.edit({
                type: "adv",
                mode: mode,
                item_id: active.item_id,
                value: compress(encode.encode(value || "")),
                ...arg,
              });

              if (typeof code_result === "string") {
                p.ui.popup.script.typings.status = "error";
                p.ui.popup.script.typings.err_msg = code_result;
              } else if (typeof code_result === "object") {
                scope = code_result;
              }
            }
            if (typeof scope === "object") {
              meta.item.script = scope;
            }
            p.ui.popup.script.wb_render();
          }
        };
        p.ui.popup.script.on_close = () => {
          clearTimeout(scriptEdit.timeout);
          applyChanges();
          p.ui.popup.script.on_close = () => {};
        };
        scriptEdit.timeout = setTimeout(applyChanges, 1000);
      }}
      onMount={async (editor, monaco) => {
        local.monaco = monaco;
        local.editor = editor;
        local.render();
        editor.focus();
        setTimeout(() => {
          editor.focus();
        }, 300);
      }}
    />
  );
};
