"use client";
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
import { useDeepCompareMemo } from "@react-hookz/web";
import escapeStringRegexp from "escape-string-regexp";
import { forwardRef, useContext, createContext, useSyncExternalStore } from "react";
import flattenChildren from "react-keyed-flatten-children";
import { ExperimentalButton } from "./Button.js";
import { ExperimentalCopyButton } from "./CopyButton.js";
import { ExperimentalIcon } from "./Icon.js";
import { ExperimentalTabSet } from "./TabSet.js";
import { useControlled } from "./hooks/useControlled.js";
import { Eye } from "../icons/Eye.js";
import { EyeClosed } from "../icons/EyeClosed.js";
import { FileCode } from "../icons/FileCode.js";
import { FilePy } from "../icons/FilePy.js";
import { FileRs } from "../icons/FileRs.js";
import { FileSql } from "../icons/FileSql.js";
import { FileVue } from "../icons/FileVue.js";
import { FileTsx } from "../icons/FileTsx.js";
import { FileTs } from "../icons/FileTs.js";
import { FileJsx } from "../icons/FileJsx.js";
import { FileJs } from "../icons/FileJs.js";
import { FileCss } from "../icons/FileCss.js";
import { FileMd } from "../icons/FileMd.js";
import { FileHtml } from "../icons/FileHtml.js";
import { Terminal } from "../icons/Terminal.js";
import { classNames } from "../utils/classNames.js";
const FontStyle = {
  Italic: 1,
  Bold: 2,
  Underline: 4
};
const THEME = "night-owl";
const LANGUAGES = [
  "text",
  "shellscript",
  "shell",
  "bash",
  "sh",
  "zsh",
  "html",
  "markdown",
  "md",
  "css",
  "javascript",
  "js",
  "jsx",
  "typescript",
  "ts",
  "tsx",
  "vue",
  "json",
  "yaml",
  "yml",
  "toml",
  "graphql",
  "gql",
  "sql",
  "php",
  "java",
  "ruby",
  "rust",
  "rs",
  "python",
  "py",
  "go",
  "swift",
  "solidity",
  "mermaid"
];
const ExperimentalCodeBlockWrapper = forwardRef(
  ({ className, children, ...props }, ref) => {
    return (
      // TODO: Would be nice if `rounded-8` and `bg-white/4` were on the outermost wrapper in `ExperimentalCodeBlock.Tabs` (as unimportant)
      /* @__PURE__ */ jsx("figure", { ref, className: classNames(["rounded-8 bg-white/4", className]), ...props, children })
    );
  }
);
ExperimentalCodeBlockWrapper.displayName = "ExperimentalCodeBlockWrapper";
const ExperimentalCodeBlockHeaderContext = createContext(null);
const ExperimentalCodeBlockHeader = forwardRef(
  ({ buttons, className, children, ...props }, ref) => {
    return /* @__PURE__ */ jsx(ExperimentalCodeBlockHeaderContext.Provider, { value: {}, children: /* @__PURE__ */ jsxs(
      "figcaption",
      {
        ref,
        className: classNames(["flex h-10 gap-4 rounded-t-[inherit] bg-white/2 pe-1 ps-2", className]),
        ...props,
        children: [
          /* @__PURE__ */ jsx("div", { className: "absolute inset-0 border-b border-white/4" }),
          /* @__PURE__ */ jsx("div", { className: "flex items-center", children }),
          buttons ? /* @__PURE__ */ jsx("div", { className: "ms-auto flex shrink-0 items-center gap-4.5 pe-3", children: buttons }) : null
        ]
      }
    ) });
  }
);
ExperimentalCodeBlockHeader.displayName = "ExperimentalCodeBlockHeader";
const ExperimentalCodeBlockRoot = forwardRef(
  ({
    language = "text",
    icon: passedIcon,
    label,
    highlight: passedHighlight,
    secret: passedSecret,
    masked: controlledMasked,
    onMaskedChange,
    copy: passedCopy,
    wrap: passedWrap,
    diff: passedDiff,
    lineNumbers: passedLineNumbers,
    rows: passedRows,
    className,
    children,
    ...props
  }, ref) => {
    const tabsContext = useContext(ExperimentalCodeBlockTabsContext);
    const hasAncestorTabs = tabsContext !== null;
    const hasAncestorHeader = useContext(ExperimentalCodeBlockHeaderContext) !== null;
    const tabsProps = hasAncestorTabs ? tabsContext.props : null;
    const highlighter = useSyncExternalStore(
      subscribeToCurrentHighlighterChanges,
      () => currentHighlighter,
      () => currentHighlighter
    );
    const icon = passedIcon !== void 0 ? passedIcon : getDefaultIcon(language);
    const highlights = [passedHighlight ?? (tabsProps == null ? void 0 : tabsProps.highlight)].flat().filter((value) => Boolean(value));
    const secrets = [passedSecret ?? (tabsProps == null ? void 0 : tabsProps.secret)].flat().filter((value) => Boolean(value));
    const [masked, setMasked] = useControlled(controlledMasked ?? (tabsProps == null ? void 0 : tabsProps.masked), true, (masked2) => {
      var _a;
      onMaskedChange == null ? void 0 : onMaskedChange(masked2);
      (_a = tabsProps == null ? void 0 : tabsProps.onMaskedChange) == null ? void 0 : _a.call(tabsProps, masked2);
    });
    const copy = passedCopy ?? (tabsProps == null ? void 0 : tabsProps.copy) ?? true;
    const wrap = passedWrap ?? (tabsProps == null ? void 0 : tabsProps.wrap) ?? false;
    const diff = passedDiff ?? (tabsProps == null ? void 0 : tabsProps.diff) ?? false;
    const lineNumbers = passedLineNumbers ?? (tabsProps == null ? void 0 : tabsProps.lineNumbers) ?? true;
    const rows = passedRows ?? (tabsProps == null ? void 0 : tabsProps.rows) ?? 0;
    const code = Array.isArray(children) ? children.join("") : String(children);
    const lines = useDeepCompareMemo(() => {
      const { tokens } = highlighter.codeToTokens(code, {
        theme: THEME,
        lang: LANGUAGES.includes(language) ? language : "text"
      });
      return tokens.map((lineTokens) => {
        const lineContent = lineTokens.map((token) => token.content).join("");
        let lineStyle = null;
        if (diff) {
          lineStyle = lineContent.startsWith("+") ? "added" : lineContent.startsWith("-") ? "deleted" : null;
        }
        if (!lineContent) {
          return { chunks: [], style: lineStyle };
        }
        if (highlights.length === 0 && secrets.length === 0) {
          return {
            chunks: lineTokens.map((token) => ({ ...token, highlighted: false, secret: false })),
            style: lineStyle
          };
        }
        const highlightRanges = highlights.length > 0 ? findRanges(lineContent, highlights) : [];
        const secretRanges = secrets.length > 0 ? findRanges(lineContent, secrets) : [];
        const lineChunks = [];
        let currentTokenIndex = 0;
        let currentToken = lineTokens[currentTokenIndex];
        let currentChunk = {
          ...currentToken,
          content: "",
          highlighted: isInRange(0, highlightRanges),
          secret: isInRange(0, secretRanges)
        };
        let currentOffset = currentChunk.offset;
        for (let characterIndex = 0; characterIndex < lineContent.length; characterIndex++) {
          const character = lineContent[characterIndex];
          const highlighted = isInRange(characterIndex, highlightRanges);
          const secret = isInRange(characterIndex, secretRanges);
          const moveToNextToken = currentOffset >= currentToken.offset + currentToken.content.length;
          if (moveToNextToken || highlighted !== currentChunk.highlighted || secret !== currentChunk.secret) {
            lineChunks.push(currentChunk);
            if (moveToNextToken) {
              currentTokenIndex++;
              currentToken = lineTokens[currentTokenIndex];
            }
            currentChunk = { ...currentToken, content: character, highlighted, secret };
          } else {
            currentChunk.content += character;
          }
          currentOffset++;
        }
        lineChunks.push(currentChunk);
        return {
          chunks: lineChunks,
          style: lineStyle
        };
      });
    }, [highlighter, code, language, highlights, secrets, diff]);
    const buttons = /* @__PURE__ */ jsxs(Fragment, { children: [
      secrets.length > 0 ? /* @__PURE__ */ jsx(ExperimentalButton, { variant: "naked", size: "small", onClick: () => setMasked(!masked), children: masked ? /* @__PURE__ */ jsx(Eye, { alt: "Reveal" }) : /* @__PURE__ */ jsx(EyeClosed, { alt: "Hide" }) }) : null,
      copy ? /* @__PURE__ */ jsx(ExperimentalCopyButton, { variant: "naked", size: "small", value: typeof copy === "string" ? copy : code }) : null
    ] });
    if (hasAncestorHeader) {
      return buttons;
    }
    const Wrapper = hasAncestorTabs ? "div" : ExperimentalCodeBlockWrapper;
    const headerContent = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 ps-2", children: [
      icon ? /* @__PURE__ */ jsx(ExperimentalIcon, { alt: "", size: 3.5, children: icon }) : null,
      label ? /* @__PURE__ */ jsx("span", { className: "text-p14 truncate text-white/64", children: label }) : null
    ] });
    return /* @__PURE__ */ jsxs(
      Wrapper,
      {
        ref,
        "data-wrap": wrap || void 0,
        "data-line-numbers": lineNumbers || void 0,
        className: classNames(["group/code-block gds-code-block", className]),
        ...props,
        children: [
          !hasAncestorTabs ? /* @__PURE__ */ jsx(ExperimentalCodeBlockHeader, { buttons, children: headerContent }) : null,
          hasAncestorTabs && label ? /* @__PURE__ */ jsxs("div", { className: "flex h-10 px-2", children: [
            /* @__PURE__ */ jsx("div", { className: "absolute inset-0 border-b border-white/4" }),
            headerContent
          ] }) : null,
          /* @__PURE__ */ jsx(
            "pre",
            {
              dir: "ltr",
              className: "overflow-auto [--padding:theme(spacing.4)]",
              style: { maxHeight: rows && rows > 0 ? `calc(${rows}lh + 2 * var(--padding))` : void 0 },
              children: /* @__PURE__ */ jsx(
                "code",
                {
                  className: `
              text-m14 grid min-w-max ${/* Ensure the right padding is preserved when there is a horizontal scroll */
                  ""}
              gap-x-3 py-[var(--padding)]
              group-data-[wrap]/code-block:min-w-0
              group-data-[line-numbers]/code-block:grid-cols-[min-content_1fr]
              group-data-[wrap]/code-block:whitespace-pre-wrap
              group-data-[wrap]/code-block:break-words
            `,
                  children: lines.flatMap((line, lineIndex) => {
                    return /* @__PURE__ */ jsxs(
                      "span",
                      {
                        "data-style": line.style,
                        className: classNames([
                          "col-span-full grid min-h-[1lh] grid-cols-subgrid items-baseline px-[var(--padding)]",
                          line.style ? `data-[style=added]:bg-green/16
                      data-[style=deleted]:bg-red/8
                      data-[style=added]:text-green
                      data-[style=deleted]:text-red` : null
                        ]),
                        children: [
                          /* @__PURE__ */ jsx(
                            "span",
                            {
                              "aria-hidden": "true",
                              className: `
                      text-m12 hidden select-none text-end text-white/32
                      group-data-[line-numbers]/code-block:block
                    `,
                              children: lineIndex + 1
                            }
                          ),
                          /* @__PURE__ */ jsx("span", { children: line.chunks.map((chunk, chunkIndex) => {
                            const fontStyle = chunk.fontStyle;
                            const chunkClasses = [
                              fontStyle === FontStyle.Bold ? "font-bold" : null,
                              fontStyle === FontStyle.Italic ? "italic" : null,
                              fontStyle === FontStyle.Underline ? "underline" : null
                            ];
                            const chunkStyle = {
                              color: chunk.color,
                              backgroundColor: chunk.bgColor
                            };
                            let chunkContent = chunk.content;
                            if (chunk.secret && masked) {
                              chunkContent = "•".repeat(chunkContent.length);
                            }
                            if ((line.style === "added" || line.style === "deleted") && chunkIndex === 0) {
                              chunkContent = chunkContent.replace(
                                /^([+-])\s+/,
                                (match, plusOrMinus) => (plusOrMinus === "-" ? "−" : plusOrMinus) + " ".repeat(match.length - 1)
                              );
                            }
                            if (highlights.length > 0) {
                              return /* @__PURE__ */ jsx(
                                "span",
                                {
                                  "data-highlighted": chunk.highlighted || void 0,
                                  className: classNames([
                                    ...chunkClasses,
                                    chunk.highlighted ? `bg-yellow/16 py-1 font-bold
                                  not-[:has(+[data-highlighted])]:-mr-1
                                  not-[[data-highlighted]+*]:-ml-1
                                  not-[:has(+[data-highlighted])]:rounded-r-4
                                  not-[[data-highlighted]+*]:rounded-l-4
                                  not-[:has(+[data-highlighted])]:pr-1
                                  not-[[data-highlighted]+*]:pl-1` : null
                                  ]),
                                  style: chunkStyle,
                                  children: chunkContent
                                },
                                chunkIndex
                              );
                            } else {
                              return /* @__PURE__ */ jsx("span", { className: classNames(chunkClasses), style: chunkStyle, children: chunkContent }, chunkIndex);
                            }
                          }) })
                        ]
                      },
                      lineIndex
                    );
                  })
                }
              )
            }
          )
        ]
      }
    );
  }
);
ExperimentalCodeBlockRoot.displayName = "ExperimentalCodeBlock";
const ExperimentalCodeBlockTabsContext = createContext(null);
const ExperimentalCodeBlockTabs = forwardRef(
  ({
    tabs: passedTabs,
    selectedIndex: controlledSelectedIndex,
    defaultIndex = 0,
    onChange,
    highlight,
    secret,
    masked: controlledMasked,
    onMaskedChange,
    copy,
    wrap,
    diff,
    lineNumbers,
    rows,
    className,
    children,
    ...props
  }, ref) => {
    const tabs = passedTabs.map((tab) => typeof tab === "string" ? { label: tab } : tab);
    if (tabs.length === 0) {
      throw new Error("ExperimentalCodeBlock.Tabs: At least 1 tab is required");
    }
    const childrenArray = flattenChildren(children);
    if (tabs.length !== childrenArray.length) {
      throw new Error(
        `ExperimentalCodeBlock.Tabs: The number of tabs (${tabs.length}) must match the number of children (${childrenArray.length})`
      );
    }
    const [selectedIndex, setSelectedIndex] = useControlled(controlledSelectedIndex, defaultIndex, onChange);
    const [masked, setMasked] = useControlled(controlledMasked, void 0, onMaskedChange);
    const tabsContext = {
      selectedIndex,
      setSelectedIndex,
      props: {
        highlight,
        secret,
        masked,
        onMaskedChange: setMasked,
        copy,
        wrap,
        diff,
        lineNumbers,
        rows
      }
    };
    return /* @__PURE__ */ jsx(
      ExperimentalTabSet,
      {
        ref,
        size: "small",
        selectedIndex,
        onChange: setSelectedIndex,
        className: classNames(["group/code-block-tabs gds-code-block-tabs", className]),
        ...props,
        children: /* @__PURE__ */ jsxs(ExperimentalCodeBlockWrapper, { children: [
          /* @__PURE__ */ jsx(
            ExperimentalCodeBlockHeader,
            {
              buttons: /* @__PURE__ */ jsx(ExperimentalCodeBlockTabsContext.Provider, { value: tabsContext, children: childrenArray.map((child, childIndex) => childIndex === selectedIndex ? child : null) }),
              children: /* @__PURE__ */ jsx(ExperimentalTabSet.Tabs, { className: "h-full +:nested-divider:hidden", children: tabs.map((tab, tabIndex) => /* @__PURE__ */ jsx(
                ExperimentalTabSet.Tab,
                {
                  iconBefore: tab.iconBefore,
                  iconAfter: tab.iconAfter,
                  disabled: tab.disabled,
                  children: tab.label
                },
                tabIndex
              )) })
            }
          ),
          /* @__PURE__ */ jsx(ExperimentalTabSet.Panels, { transition: { animateSize: "clip" }, children: childrenArray.map((child, childIndex) => /* @__PURE__ */ jsx(ExperimentalTabSet.Panel, { children: /* @__PURE__ */ jsx(ExperimentalCodeBlockTabsContext.Provider, { value: tabsContext, children: child }) }, childIndex)) })
        ] })
      }
    );
  }
);
ExperimentalCodeBlockTabs.displayName = "ExperimentalCodeBlock.Tabs";
function getDefaultIcon(language) {
  switch (language) {
    case "shellscript":
    case "shell":
    case "bash":
    case "sh":
    case "zsh":
      return /* @__PURE__ */ jsx(Terminal, { alt: "Terminal" });
    case "html":
      return /* @__PURE__ */ jsx(FileHtml, { alt: "HTML" });
    case "markdown":
    case "md":
      return /* @__PURE__ */ jsx(FileMd, { alt: "Markdown" });
    case "css":
      return /* @__PURE__ */ jsx(FileCss, { alt: "CSS" });
    case "javascript":
    case "js":
      return /* @__PURE__ */ jsx(FileJs, { alt: "JavaScript" });
    case "jsx":
      return /* @__PURE__ */ jsx(FileJsx, { alt: "JSX" });
    case "typescript":
    case "ts":
      return /* @__PURE__ */ jsx(FileTs, { alt: "TypeScript" });
    case "tsx":
      return /* @__PURE__ */ jsx(FileTsx, { alt: "TSX" });
    case "vue":
      return /* @__PURE__ */ jsx(FileVue, { alt: "Vue.js" });
    case "sql":
      return /* @__PURE__ */ jsx(FileSql, { alt: "SQL" });
    case "rust":
    case "rs":
      return /* @__PURE__ */ jsx(FileRs, { alt: "Rust" });
    case "python":
    case "py":
      return /* @__PURE__ */ jsx(FilePy, { alt: "Python" });
    default:
      return /* @__PURE__ */ jsx(FileCode, { alt: "" });
  }
}
let shikiHighlighter;
let shikiHighlighterPromise;
let currentHighlighter = {
  codeToTokens: (code) => {
    let offset = 0;
    return {
      tokens: code.split("\n").map((line) => {
        const lineTokens = [{ offset, content: line }];
        offset += line.length + 1;
        return lineTokens;
      })
    };
  }
};
const currentHighlighterChangeListeners = /* @__PURE__ */ new Set();
function subscribeToCurrentHighlighterChanges(listener) {
  currentHighlighterChangeListeners.add(listener);
  if (!shikiHighlighterPromise) {
    void loadShikiHighlighter();
  }
  return () => currentHighlighterChangeListeners.delete(listener);
}
async function loadShikiHighlighter() {
  if (!shikiHighlighterPromise) {
    const { createHighlighter } = await import("shiki");
    shikiHighlighterPromise = createHighlighter({
      themes: [THEME],
      langs: [...LANGUAGES]
    });
  }
  shikiHighlighter = await shikiHighlighterPromise;
  currentHighlighter = shikiHighlighter;
  currentHighlighterChangeListeners.forEach((listener) => listener());
}
function findRanges(string, substrings) {
  const ranges = [];
  substrings.forEach((substring) => {
    const regex = new RegExp(escapeStringRegexp(substring), "gi");
    let match;
    while ((match = regex.exec(string)) !== null) {
      ranges.push({ start: match.index, end: match.index + match[0].length });
      regex.lastIndex = match.index + 1;
    }
  });
  return ranges;
}
function isInRange(index, ranges) {
  return ranges.some((range) => index >= range.start && index < range.end);
}
export {
  ExperimentalCodeBlockRoot,
  ExperimentalCodeBlockTabs,
  LANGUAGES
};
