import { INITIAL, EncodedTokenMetadata } from '@shikijs/vscode-textmate';

function textmateThemeToMonacoTheme(theme) {
  let rules = "rules" in theme ? theme.rules : undefined;
  if (!rules) {
    rules = [];
    const themeSettings = theme.settings || theme.tokenColors;
    for (const { scope, settings: { foreground, background, fontStyle } } of themeSettings) {
      const scopes = Array.isArray(scope) ? scope : [scope];
      for (const s of scopes) {
        if (s && (foreground || background || fontStyle)) {
          rules.push({
            token: s,
            foreground: normalizeColor(foreground),
            background: normalizeColor(background),
            fontStyle
          });
        }
      }
    }
  }
  const colors = Object.fromEntries(
    Object.entries(theme.colors || {}).map(([key, value]) => [key, `#${normalizeColor(value)}`])
  );
  return {
    base: theme.type === "light" ? "vs" : "vs-dark",
    inherit: false,
    colors,
    rules
  };
}
function shikiToMonaco(highlighter, monaco, options = {}) {
  const themeMap = /* @__PURE__ */ new Map();
  const themeIds = highlighter.getLoadedThemes();
  for (const themeId of themeIds) {
    const tmTheme = highlighter.getTheme(themeId);
    const monacoTheme = textmateThemeToMonacoTheme(tmTheme);
    themeMap.set(themeId, monacoTheme);
    monaco.editor.defineTheme(themeId, monacoTheme);
  }
  const colorMap = [];
  const colorToScopeMap = /* @__PURE__ */ new Map();
  const _setTheme = monaco.editor.setTheme.bind(monaco.editor);
  monaco.editor.setTheme = (themeName) => {
    const ret = highlighter.setTheme(themeName);
    const theme = themeMap.get(themeName);
    colorMap.length = ret.colorMap.length;
    ret.colorMap.forEach((color, i) => {
      colorMap[i] = color;
    });
    colorToScopeMap.clear();
    theme?.rules.forEach((rule) => {
      const c = normalizeColor(rule.foreground);
      if (c && !colorToScopeMap.has(c))
        colorToScopeMap.set(c, rule.token);
    });
    _setTheme(themeName);
  };
  monaco.editor.setTheme(themeIds[0]);
  function findScopeByColor(color) {
    return colorToScopeMap.get(color);
  }
  const {
    tokenizeMaxLineLength = 2e4,
    tokenizeTimeLimit = 500
  } = options;
  const monacoLanguageIds = new Set(monaco.languages.getLanguages().map((l) => l.id));
  for (const lang of highlighter.getLoadedLanguages()) {
    if (monacoLanguageIds.has(lang)) {
      monaco.languages.setTokensProvider(lang, {
        getInitialState() {
          return new TokenizerState(INITIAL);
        },
        tokenize(line, state) {
          if (line.length >= tokenizeMaxLineLength) {
            return {
              endState: state,
              tokens: [{ startIndex: 0, scopes: "" }]
            };
          }
          const grammar = highlighter.getLanguage(lang);
          const result = grammar.tokenizeLine2(line, state.ruleStack, tokenizeTimeLimit);
          if (result.stoppedEarly)
            console.warn(`Time limit reached when tokenizing line: ${line.substring(0, 100)}`);
          const tokensLength = result.tokens.length / 2;
          const tokens = [];
          for (let j = 0; j < tokensLength; j++) {
            const startIndex = result.tokens[2 * j];
            const metadata = result.tokens[2 * j + 1];
            const color = normalizeColor(colorMap[EncodedTokenMetadata.getForeground(metadata)] || "");
            const scope = findScopeByColor(color) || "";
            tokens.push({ startIndex, scopes: scope });
          }
          return { endState: new TokenizerState(result.ruleStack), tokens };
        }
      });
    }
  }
}
class TokenizerState {
  constructor(_ruleStack) {
    this._ruleStack = _ruleStack;
  }
  get ruleStack() {
    return this._ruleStack;
  }
  clone() {
    return new TokenizerState(this._ruleStack);
  }
  equals(other) {
    if (!other || !(other instanceof TokenizerState) || other !== this || other._ruleStack !== this._ruleStack) {
      return false;
    }
    return true;
  }
}
function normalizeColor(color) {
  if (!color)
    return color;
  color = (color.charCodeAt(0) === 35 ? color.slice(1) : color).toLowerCase();
  if (color.length === 3 || color.length === 4)
    color = color.split("").map((c) => c + c).join("");
  return color;
}

export { shikiToMonaco, textmateThemeToMonacoTheme };
