"use strict";
import { compact } from '../utils/compact.js';
import { createProps } from '../utils/create-props.js';
import { mapEntries } from '../utils/entries.js';
import { isString, isObject, isFunction } from '../utils/is.js';
import { memo } from '../utils/memo.js';
import { mapObject, walkObject } from '../utils/walk-object.js';
import { cssVar } from './css-var.js';
import { esc } from './esc.js';
import { expandTokenReferences } from './expand-reference.js';
import { mapToJson } from './map-to-json.js';
import { TOKEN_PATH_REGEX, hasReference, expandReferences, getReferences } from './references.js';
import { tokenMiddlewares } from './token-middleware.js';
import { tokenTransforms } from './token-transforms.js';

const isToken = (value) => {
  return isObject(value) && Object.prototype.hasOwnProperty.call(value, "value");
};
function expandBreakpoints(breakpoints) {
  if (!breakpoints) return { breakpoints: {}, sizes: {} };
  return {
    breakpoints: mapObject(breakpoints, (value) => ({ value })),
    sizes: mapEntries(breakpoints, (key, value) => [
      `breakpoint-${key}`,
      { value }
    ])
  };
}
function createTokenDictionary(options) {
  const {
    prefix = "",
    tokens = {},
    semanticTokens = {},
    breakpoints = {}
  } = options;
  const formatTokenName = (path) => path.join(".");
  const formatCssVar = (path, prefix2) => cssVar(path.join("-"), { prefix: prefix2 });
  const allTokens = [];
  const tokenNameMap = /* @__PURE__ */ new Map();
  const conditionMap = /* @__PURE__ */ new Map();
  const cssVarMap = /* @__PURE__ */ new Map();
  const colorPaletteMap = /* @__PURE__ */ new Map();
  const flatMap = /* @__PURE__ */ new Map();
  const byCategory = /* @__PURE__ */ new Map();
  const categoryMap = /* @__PURE__ */ new Map();
  const transforms = /* @__PURE__ */ new Map();
  const middlewares = [];
  function registerToken(token, phase) {
    allTokens.push(token);
    tokenNameMap.set(token.name, token);
    if (phase) {
      transforms.forEach((fn) => {
        if (fn.enforce === phase) transformToken(fn, token);
      });
    }
  }
  const breakpointTokens = expandBreakpoints(breakpoints);
  const computedTokens = compact({
    ...tokens,
    breakpoints: breakpointTokens.breakpoints,
    sizes: {
      ...tokens.sizes,
      ...breakpointTokens.sizes
    }
  });
  function registerTokens() {
    walkObject(
      computedTokens,
      (entry, path) => {
        const isDefault = path.includes("DEFAULT");
        path = filterDefault(path);
        const category = path[0];
        const name = formatTokenName(path);
        const t = isString(entry) ? { value: entry } : entry;
        const token = {
          value: t.value,
          originalValue: t.value,
          name,
          path,
          extensions: {
            condition: "base",
            originalPath: path,
            category,
            prop: formatTokenName(path.slice(1))
          }
        };
        if (isDefault) {
          token.extensions.default = true;
        }
        registerToken(token);
      },
      { stop: isToken }
    );
    walkObject(
      semanticTokens,
      (entry, path) => {
        const isDefault = path.includes("DEFAULT");
        path = filterBaseCondition(filterDefault(path));
        const category = path[0];
        const name = formatTokenName(path);
        const t = isString(entry.value) ? { value: { base: entry.value } } : entry;
        const token = {
          value: t.value.base || "",
          originalValue: t.value.base || "",
          name,
          path,
          extensions: {
            originalPath: path,
            category,
            conditions: t.value,
            condition: "base",
            prop: formatTokenName(path.slice(1))
          }
        };
        if (isDefault) {
          token.extensions.default = true;
        }
        registerToken(token);
      },
      { stop: isToken }
    );
  }
  function getByName(name) {
    return tokenNameMap.get(name);
  }
  function buildConditionMap(token) {
    const { condition } = token.extensions;
    if (!condition) return;
    if (!conditionMap.has(condition)) {
      conditionMap.set(condition, /* @__PURE__ */ new Set());
    }
    conditionMap.get(condition).add(token);
  }
  function buildCategoryMap(token) {
    const { category, prop } = token.extensions;
    if (!category) return;
    if (!categoryMap.has(category)) {
      categoryMap.set(category, /* @__PURE__ */ new Map());
    }
    categoryMap.get(category).set(prop, token);
  }
  function buildCssVars(token) {
    const { condition, negative, virtual, cssVar: cssVar2 } = token.extensions;
    if (negative || virtual || !condition || !cssVar2) return;
    if (!cssVarMap.has(condition)) {
      cssVarMap.set(condition, /* @__PURE__ */ new Map());
    }
    cssVarMap.get(condition).set(cssVar2.var, token.value);
  }
  function buildFlatMap(token) {
    const { category, prop, cssVar: cssVar2, negative } = token.extensions;
    if (!category) return;
    if (!byCategory.has(category)) {
      byCategory.set(category, /* @__PURE__ */ new Map());
    }
    const value = negative ? token.extensions.conditions ? token.originalValue : token.value : cssVar2.ref;
    byCategory.get(category).set(prop, value);
    flatMap.set([category, prop].join("."), value);
  }
  function buildColorPalette(token) {
    const { colorPalette, virtual, default: isDefault } = token.extensions;
    if (!colorPalette || virtual) return;
    colorPalette.roots.forEach((root) => {
      const name = formatTokenName(root);
      if (!colorPaletteMap.has(name)) {
        colorPaletteMap.set(name, /* @__PURE__ */ new Map());
      }
      const virtualPath = replaceRootWithColorPalette(
        [...token.path],
        [...root]
      );
      const virtualName = formatTokenName(virtualPath);
      const virtualToken = getByName(virtualName);
      if (!virtualToken || !virtualToken.extensions.cssVar) return;
      const { var: virtualVar } = virtualToken.extensions.cssVar;
      colorPaletteMap.get(name).set(virtualVar, token.extensions.cssVar.ref);
      if (isDefault && root.length === 1) {
        const colorPaletteName = formatTokenName(["colors", "colorPalette"]);
        const colorPaletteToken = getByName(colorPaletteName);
        if (!colorPaletteToken) return;
        const name2 = formatTokenName(token.path);
        const virtualToken2 = getByName(name2);
        if (!virtualToken2) return;
        const keyPath = colorPalette.keys[0]?.filter(Boolean);
        if (!keyPath.length) return;
        const computedName = formatTokenName(root.concat(keyPath));
        if (!colorPaletteMap.has(computedName)) {
          colorPaletteMap.set(computedName, /* @__PURE__ */ new Map());
        }
        colorPaletteMap.get(computedName).set(
          colorPaletteToken.extensions.cssVar.var,
          virtualToken2.extensions.cssVar.ref
        );
      }
    });
  }
  let byCategoryJson = {};
  function setupViews() {
    allTokens.forEach((token) => {
      buildConditionMap(token);
      buildCategoryMap(token);
      buildCssVars(token);
      buildFlatMap(token);
      buildColorPalette(token);
    });
    byCategoryJson = mapToJson(byCategory);
  }
  const colorMix = (value, tokenFn) => {
    if (!value || typeof value !== "string") return { invalid: true, value };
    const [colorPath, rawOpacity] = value.split("/");
    if (!colorPath || !rawOpacity) {
      return { invalid: true, value: colorPath };
    }
    const colorToken = tokenFn(colorPath);
    const opacityToken = getByName(`opacity.${rawOpacity}`)?.value;
    if (!opacityToken && isNaN(Number(rawOpacity))) {
      return { invalid: true, value: colorPath };
    }
    const percent = opacityToken ? Number(opacityToken) * 100 + "%" : `${rawOpacity}%`;
    const color = colorToken ?? colorPath;
    return {
      invalid: false,
      color,
      value: `color-mix(in srgb, ${color} ${percent}, transparent)`
    };
  };
  const getVar = memo((value, fallback) => {
    return flatMap.get(value) ?? fallback;
  });
  const getCategoryValues = memo((category) => {
    return byCategoryJson[category] || null;
  });
  const expandReferenceInValue = memo((value) => {
    return expandTokenReferences(value, (path) => {
      if (!path) return;
      if (path.includes("/")) {
        const mix = colorMix(path, (v) => getVar(v));
        if (mix.invalid) {
          throw new Error("Invalid color mix at " + path + ": " + mix.value);
        }
        return mix.value;
      }
      const resolved = getVar(path);
      if (resolved) return resolved;
      return TOKEN_PATH_REGEX.test(path) ? esc(path) : path;
    });
  });
  const dictionary = {
    prefix,
    allTokens,
    tokenMap: tokenNameMap,
    registerToken,
    getByName,
    formatTokenName,
    formatCssVar,
    flatMap,
    cssVarMap,
    categoryMap,
    colorPaletteMap,
    getVar,
    getCategoryValues,
    expandReferenceInValue
  };
  function registerTransform(...fns) {
    fns.forEach((fn) => {
      transforms.set(fn.name, fn);
    });
  }
  function registerMiddleware(...fns) {
    middlewares.push(...fns);
  }
  function transformToken(transform, token) {
    if (token.extensions.references) return;
    if (isFunction(transform.match) && !transform.match(token)) return;
    const fn = (v) => transform.transform(v, dictionary);
    const transformed = fn(token);
    switch (true) {
      case transform.type === "extensions":
        Object.assign(token.extensions, transformed);
        break;
      case transform.type === "value":
        token.value = transformed;
        break;
      default:
        token[transform.type] = transformed;
        break;
    }
  }
  function applyMiddlewares(enforce) {
    middlewares.forEach((middleware) => {
      if (middleware.enforce === enforce) {
        middleware.transform(dictionary);
      }
    });
  }
  function applyTransforms(enforce) {
    transforms.forEach((transform) => {
      if (transform.enforce === enforce) {
        allTokens.forEach((token) => {
          transformToken(transform, token);
        });
      }
    });
  }
  function addConditionalTokens() {
    allTokens.forEach((token) => {
      const tokens2 = getConditionalTokens(token);
      if (!tokens2 || tokens2.length === 0) return;
      tokens2.forEach((token2) => {
        registerToken(token2);
      });
    });
  }
  function getTokenReferences(value) {
    const refs = getReferences(value);
    const result = [];
    for (let i = 0; i < refs.length; i++) {
      const token = getByName(refs[i]);
      if (token) {
        result.push(token);
      }
    }
    return result;
  }
  function addReferences() {
    allTokens.forEach((token) => {
      if (!hasReference(token.value)) return;
      const references = getTokenReferences(token.value);
      token.extensions.references = references.reduce((acc, ref) => {
        acc[ref.name] = ref;
        return acc;
      }, {});
    });
  }
  function expandTokenReferences$1() {
    allTokens.forEach((token) => {
      expandReferences(token);
    });
  }
  function build() {
    applyMiddlewares("pre");
    applyTransforms("pre");
    addConditionalTokens();
    addReferences();
    expandTokenReferences$1();
    applyMiddlewares("post");
    applyTransforms("post");
    setupViews();
  }
  registerTokens();
  registerTransform(...tokenTransforms);
  registerMiddleware(...tokenMiddlewares);
  build();
  return dictionary;
}
function filterDefault(path) {
  if (path[0] === "DEFAULT") return path;
  return path.filter((item) => item !== "DEFAULT");
}
function filterBaseCondition(path) {
  return path.filter((item) => item !== "base");
}
function getConditionalTokens(token) {
  if (!token.extensions.conditions) return;
  const { conditions } = token.extensions;
  const tokens = [];
  walkObject(conditions, (value, path) => {
    const nextPath = filterBaseCondition(path);
    if (!nextPath.length) return;
    const nextToken = {
      ...token,
      value,
      extensions: {
        ...token.extensions,
        condition: nextPath.join(":")
      }
    };
    tokens.push(nextToken);
  });
  return tokens;
}
function replaceRootWithColorPalette(path, roots) {
  const startIndex = path.findIndex(
    (_, index) => roots.every(
      (rootElement, rootIndex) => path[index + rootIndex] === rootElement
    )
  );
  if (startIndex === -1) {
    return path;
  }
  path.splice(startIndex, roots.length);
  path.splice(startIndex, 0, "colorPalette");
  return path;
}
const tokenCategories = createProps()([
  "aspectRatios",
  "zIndex",
  "opacity",
  "colors",
  "fonts",
  "fontSizes",
  "fontWeights",
  "lineHeights",
  "letterSpacings",
  "sizes",
  "shadows",
  "spacing",
  "radii",
  "cursor",
  "borders",
  "borderWidths",
  "borderStyles",
  "durations",
  "easings",
  "animations",
  "blurs",
  "gradients",
  "breakpoints",
  "assets"
]);

export { createTokenDictionary, tokenCategories };
