import {
  createCssVariablesTheme,
  createHighlighter,
  isSpecialLang
} from "shiki";
import { loadShikiEngine } from "#shiki-engine";
let _cssVariablesTheme;
const cssVariablesTheme = () => _cssVariablesTheme ?? (_cssVariablesTheme = createCssVariablesTheme({
  variablePrefix: "--astro-code-"
}));
const cachedHighlighters = /* @__PURE__ */ new Map();
function clearShikiHighlighterCache() {
  cachedHighlighters.clear();
}
function createShikiHighlighter(options) {
  const key = getCacheKey(options);
  let highlighterPromise = cachedHighlighters.get(key);
  if (!highlighterPromise) {
    highlighterPromise = createShikiHighlighterInternal(options);
    cachedHighlighters.set(key, highlighterPromise);
  }
  return ensureLanguagesLoaded(highlighterPromise, options?.langs);
}
function getCacheKey(options) {
  const keyCache = [];
  const { theme, themes, langAlias } = options ?? {};
  if (theme) {
    keyCache.push(theme);
  }
  if (themes) {
    keyCache.push(Object.entries(themes).sort());
  }
  if (langAlias) {
    keyCache.push(Object.entries(langAlias).sort());
  }
  return keyCache.length > 0 ? JSON.stringify(keyCache) : "";
}
async function ensureLanguagesLoaded(promise, langs) {
  const highlighter = await promise;
  if (!langs) {
    return highlighter;
  }
  const loadedLanguages = highlighter.getLoadedLanguages();
  for (const lang of langs) {
    if (typeof lang === "string" && (isSpecialLang(lang) || loadedLanguages.includes(lang))) {
      continue;
    }
    await highlighter.loadLanguage(lang);
  }
  return highlighter;
}
let shikiEngine = void 0;
async function createShikiHighlighterInternal({
  langs = [],
  theme = "github-dark",
  themes = {},
  langAlias = {}
} = {}) {
  theme = theme === "css-variables" ? cssVariablesTheme() : theme;
  if (shikiEngine === void 0) {
    shikiEngine = await loadShikiEngine();
  }
  const highlighter = await createHighlighter({
    langs: ["plaintext", ...langs],
    langAlias,
    themes: Object.values(themes).length ? Object.values(themes) : [theme],
    engine: shikiEngine
  });
  async function highlight(code, lang = "plaintext", options, to) {
    const resolvedLang = langAlias[lang] ?? lang;
    const loadedLanguages = highlighter.getLoadedLanguages();
    if (!isSpecialLang(lang) && !loadedLanguages.includes(resolvedLang)) {
      try {
        await highlighter.loadLanguage(resolvedLang);
      } catch (_err) {
        const langStr = lang === resolvedLang ? `"${lang}"` : `"${lang}" (aliased to "${resolvedLang}")`;
        console.warn(`[Shiki] The language ${langStr} doesn't exist, falling back to "plaintext".`);
        lang = "plaintext";
      }
    }
    code = code.replace(/(?:\r\n|\r|\n)$/, "");
    const themeOptions = Object.values(themes).length ? { themes } : { theme };
    const inline = options?.inline ?? false;
    return highlighter[to === "html" ? "codeToHtml" : "codeToHast"](code, {
      ...themeOptions,
      defaultColor: options.defaultColor,
      lang,
      // NOTE: while we can spread `options.attributes` here so that Shiki can auto-serialize this as rendered
      // attributes on the top-level tag, it's not clear whether it is fine to pass all attributes as meta, as
      // they're technically neither meta nor parsed from Shiki's `parseMetaString` API.
      meta: options?.meta ? { __raw: options?.meta } : void 0,
      transformers: [
        {
          pre(node) {
            if (inline) {
              node.tagName = "code";
            }
            const {
              class: attributesClass,
              style: attributesStyle,
              ...rest
            } = options?.attributes ?? {};
            Object.assign(node.properties, rest);
            const classValue = (normalizePropAsString(node.properties.class) ?? "") + (attributesClass ? ` ${attributesClass}` : "");
            const styleValue = (normalizePropAsString(node.properties.style) ?? "") + (attributesStyle ? `; ${attributesStyle}` : "");
            node.properties.class = classValue.replace(/shiki/g, "astro-code");
            node.properties.dataLanguage = lang;
            if (options.wrap === false || options.wrap === void 0) {
              node.properties.style = styleValue + "; overflow-x: auto;";
            } else if (options.wrap === true) {
              node.properties.style = styleValue + "; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;";
            }
          },
          line(node) {
            if (resolvedLang === "diff") {
              const innerSpanNode = node.children[0];
              const innerSpanTextNode = innerSpanNode?.type === "element" && innerSpanNode.children?.[0];
              if (innerSpanTextNode && innerSpanTextNode.type === "text") {
                const start = innerSpanTextNode.value[0];
                if (start === "+" || start === "-") {
                  innerSpanTextNode.value = innerSpanTextNode.value.slice(1);
                  innerSpanNode.children.unshift({
                    type: "element",
                    tagName: "span",
                    properties: { style: "user-select: none;" },
                    children: [{ type: "text", value: start }]
                  });
                }
              }
            }
          },
          code(node) {
            if (inline) {
              return node.children[0];
            }
          }
        },
        ...options.transformers ?? []
      ]
    });
  }
  return {
    codeToHast(code, lang, options = {}) {
      return highlight(code, lang, options, "hast");
    },
    codeToHtml(code, lang, options = {}) {
      return highlight(code, lang, options, "html");
    },
    loadLanguage(...newLangs) {
      return highlighter.loadLanguage(...newLangs);
    },
    getLoadedLanguages() {
      return highlighter.getLoadedLanguages();
    }
  };
}
function normalizePropAsString(value) {
  return Array.isArray(value) ? value.join(" ") : value;
}
export {
  clearShikiHighlighterCache,
  createShikiHighlighter
};
