import { createTransformerFactory } from '@shikijs/twoslash/core';
import { removeTwoslashNotations } from 'twoslash';
import { createTwoslasher } from 'twoslash-vue';
import { rendererRich } from '@shikijs/twoslash';
export { defaultHoverInfoProcessor } from '@shikijs/twoslash';
import { fromMarkdown } from 'mdast-util-from-markdown';
import { gfmFromMarkdown } from 'mdast-util-gfm';
import { toHast, defaultHandlers } from 'mdast-util-to-hast';

function rendererFloatingVue(options = {}) {
  const {
    classCopyIgnore = "vp-copy-ignore",
    classFloatingPanel = "twoslash-floating",
    classCode = "vp-code",
    classMarkdown = "vp-doc",
    floatingVueTheme = "twoslash",
    floatingVueThemeQuery = "twoslash-query",
    floatingVueThemeCompletion = "twoslash-completion"
  } = options.floatingVue || {};
  const {
    errorRendering = "line"
  } = options;
  const hoverBasicProps = {
    "class": "twoslash-hover",
    "popper-class": ["shiki", classFloatingPanel, classCopyIgnore, classCode].join(" "),
    "theme": floatingVueTheme
  };
  const rich = rendererRich({
    classExtra: classCopyIgnore,
    ...options,
    renderMarkdown,
    renderMarkdownInline,
    hast: {
      hoverToken: {
        tagName: "v-menu",
        properties: hoverBasicProps
      },
      hoverCompose: compose,
      queryToken: {
        tagName: "v-menu",
        properties: {
          ...hoverBasicProps,
          ":shown": "true",
          "theme": floatingVueThemeQuery
        }
      },
      queryCompose: compose,
      popupDocs: {
        class: `twoslash-popup-docs ${classMarkdown}`
      },
      popupDocsTags: {
        class: `twoslash-popup-docs twoslash-popup-docs-tags ${classMarkdown}`
      },
      popupError: {
        class: `twoslash-popup-error ${classMarkdown}`
      },
      errorToken: errorRendering === "line" ? undefined : {
        tagName: "v-menu",
        properties: {
          ...hoverBasicProps,
          class: "twoslash-error twoslash-error-hover"
        }
      },
      errorCompose: compose,
      completionCompose({ popup, cursor }) {
        return [
          {
            type: "element",
            tagName: "v-menu",
            properties: {
              "popper-class": ["shiki twoslash-completion", classCopyIgnore, classFloatingPanel],
              "theme": floatingVueThemeCompletion,
              ":shown": "true"
            },
            children: [
              cursor,
              {
                type: "element",
                tagName: "template",
                properties: {
                  "v-slot:popper": "{}"
                },
                content: {
                  type: "root",
                  children: [vPre(popup)]
                }
              }
            ]
          }
        ];
      }
    }
  });
  return rich;
}
function vPre(el) {
  if (el.type === "element") {
    el.properties = el.properties || {};
    el.properties["v-pre"] = "";
  }
  return el;
}
function renderMarkdown(md) {
  const mdast = fromMarkdown(
    md.replace(/\{@link ([^}]*)\}/g, "$1"),
    // replace jsdoc links
    { mdastExtensions: [gfmFromMarkdown()] }
  );
  return toHast(
    mdast,
    {
      handlers: {
        code: (state, node) => {
          const lang = node.lang || "";
          if (lang) {
            return {
              type: "element",
              tagName: "code",
              properties: {},
              children: this.codeToHast(
                node.value,
                {
                  ...this.options,
                  transformers: [],
                  lang,
                  structure: node.value.trim().includes("\n") ? "classic" : "inline"
                }
              ).children
            };
          }
          return defaultHandlers.code(state, node);
        }
      }
    }
  ).children;
}
function renderMarkdownInline(md, context) {
  if (context === "tag:param")
    md = md.replace(/^([\w$-]+)/, "`$1` ");
  const children = renderMarkdown.call(this, md);
  if (children.length === 1 && children[0].type === "element" && children[0].tagName === "p")
    return children[0].children;
  return children;
}
function compose(parts) {
  return [
    {
      type: "element",
      tagName: "span",
      properties: {},
      children: [parts.token]
    },
    {
      type: "element",
      tagName: "template",
      properties: {
        "v-slot:popper": "{}"
      },
      content: {
        type: "root",
        children: [vPre(parts.popup)]
      },
      children: []
    }
  ];
}

function transformerTwoslash(options = {}) {
  const {
    explicitTrigger = true,
    typesCache
  } = options;
  const onError = (error, code) => {
    const isCI = typeof process !== "undefined" && process?.env?.CI;
    const isDev = typeof process !== "undefined" && process?.env?.NODE_ENV === "development";
    const shouldThrow = (options.throws || isCI || !isDev) && options.throws !== false;
    console.error(`

--------
Twoslash error in code:
--------
${code.split(/\n/g).slice(0, 15).join("\n").trim()}
--------
`);
    if (shouldThrow)
      throw error;
    else
      console.error(error);
    removeTwoslashNotations(code);
  };
  const defaultTwoslasher = createTwoslasher(options.twoslashOptions);
  let twoslasher = defaultTwoslasher;
  if (typesCache) {
    twoslasher = (code, extension, options2) => {
      const cached = typesCache.read(code);
      if (cached)
        return cached;
      const twoslashResult = defaultTwoslasher(code, extension, options2);
      typesCache.write(code, twoslashResult);
      return twoslashResult;
    };
    twoslasher.getCacheMap = defaultTwoslasher.getCacheMap;
  }
  const twoslash = createTransformerFactory(twoslasher)({
    langs: ["ts", "tsx", "js", "jsx", "json", "vue"],
    renderer: rendererFloatingVue(options),
    onTwoslashError: onError,
    onShikiError: onError,
    ...options,
    explicitTrigger
  });
  const trigger = explicitTrigger instanceof RegExp ? explicitTrigger : /\btwoslash\b/;
  typesCache?.init?.();
  return {
    ...twoslash,
    name: "@shikijs/vitepress-twoslash",
    preprocess(code, options2) {
      const cleanup = options2.transformers?.find((i) => i.name === "vitepress:clean-up");
      if (cleanup)
        options2.transformers?.splice(options2.transformers.indexOf(cleanup), 1);
      if (!explicitTrigger || options2.meta?.__raw?.match(trigger)) {
        const vPre = options2.transformers?.find((i) => i.name === "vitepress:v-pre");
        if (vPre)
          options2.transformers?.splice(options2.transformers.indexOf(vPre), 1);
      }
      return twoslash.preprocess.call(this, code, options2);
    },
    postprocess(html) {
      if (this.meta.twoslash)
        return html.replace(/\{/g, "&#123;");
    }
  };
}

export { rendererFloatingVue, transformerTwoslash };
