"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.STYLE_PREFIX = void 0;
exports.default = useStyleRegister;
exports.extract = void 0;
exports.normalizeStyle = normalizeStyle;
exports.parseStyle = void 0;
exports.uniqueHash = uniqueHash;
var _hash = _interopRequireDefault(require("@emotion/hash"));
var _dynamicCSS = require("@rc-component/util/lib/Dom/dynamicCSS");
var React = _interopRequireWildcard(require("react"));
var _unitless = _interopRequireDefault(require("@emotion/unitless"));
var _stylis = require("stylis");
var _linters = require("../linters");
var _StyleContext = _interopRequireWildcard(require("../StyleContext"));
var _util = require("../util");
var _cacheMapUtil = require("../util/cacheMapUtil");
var _useGlobalCache = _interopRequireDefault(require("./useGlobalCache"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
// @ts-ignore

const SKIP_CHECK = '_skip_check_';
const MULTI_VALUE = '_multi_value_';
// ============================================================================
// ==                                 Parser                                 ==
// ============================================================================
// Preprocessor style content to browser support one
function normalizeStyle(styleStr, autoPrefix) {
  const serialized = autoPrefix ? (0, _stylis.serialize)((0, _stylis.compile)(styleStr), (0, _stylis.middleware)([_stylis.prefixer, _stylis.stringify])) : (0, _stylis.serialize)((0, _stylis.compile)(styleStr), _stylis.stringify);
  return serialized.replace(/\{%%%\:[^;];}/g, ';');
}
function isCompoundCSSProperty(value) {
  return typeof value === 'object' && value && (SKIP_CHECK in value || MULTI_VALUE in value);
}

// 注入 hash 值
function injectSelectorHash(key, hashId, hashPriority = 'high') {
  if (!hashId) {
    return key;
  }
  const hashSelector = (0, _util.where)({
    hashCls: hashId,
    hashPriority
  });

  // 注入 hashId
  const keys = key.split(',').map(k => {
    const fullPath = k.trim().split(/\s+/);

    // 如果 Selector 第一个是 HTML Element，那我们就插到它的后面。反之，就插到最前面。
    let firstPath = fullPath[0] || '';
    const htmlElement = firstPath.match(/^\w+/)?.[0] || '';
    firstPath = `${htmlElement}${hashSelector}${firstPath.slice(htmlElement.length)}`;
    return [firstPath, ...fullPath.slice(1)].join(' ');
  });
  return keys.join(',');
}
// Parse CSSObject to style content
const parseStyle = (interpolation, config = {}, {
  root,
  injectHash,
  parentSelectors
} = {
  root: true,
  parentSelectors: []
}) => {
  const {
    hashId,
    layer,
    path,
    hashPriority,
    transformers = [],
    linters = []
  } = config;
  let styleStr = '';
  let effectStyle = {};
  function parseKeyframes(keyframes) {
    const animationName = keyframes.getName(hashId);
    if (!effectStyle[animationName]) {
      const [parsedStr] = parseStyle(keyframes.style, config, {
        root: false,
        parentSelectors
      });
      effectStyle[animationName] = `@keyframes ${keyframes.getName(hashId)}${parsedStr}`;
    }
  }
  function flattenList(list, fullList = []) {
    list.forEach(item => {
      if (Array.isArray(item)) {
        flattenList(item, fullList);
      } else if (item) {
        fullList.push(item);
      }
    });
    return fullList;
  }
  const flattenStyleList = flattenList(Array.isArray(interpolation) ? interpolation : [interpolation]);
  flattenStyleList.forEach(originStyle => {
    // Only root level can use raw string
    const style = typeof originStyle === 'string' && !root ? {} : originStyle;
    if (typeof style === 'string') {
      styleStr += `${style}\n`;
    } else if (style._keyframe) {
      // Keyframe
      parseKeyframes(style);
    } else {
      const mergedStyle = transformers.reduce((prev, trans) => trans?.visit?.(prev) || prev, style);

      // Normal CSSObject
      Object.keys(mergedStyle).forEach(key => {
        const value = mergedStyle[key];
        if (typeof value === 'object' && value && (key !== 'animationName' || !value._keyframe) && !isCompoundCSSProperty(value)) {
          let subInjectHash = false;

          // 当成嵌套对象来处理
          let mergedKey = key.trim();
          // Whether treat child as root. In most case it is false.
          let nextRoot = false;

          // 拆分多个选择器
          if ((root || injectHash) && hashId) {
            if (mergedKey.startsWith('@')) {
              // 略过媒体查询，交给子节点继续插入 hashId
              subInjectHash = true;
            } else if (mergedKey === '&') {
              // 抹掉 root selector 上的单个 &
              mergedKey = injectSelectorHash('', hashId, hashPriority);
            } else {
              // 注入 hashId
              mergedKey = injectSelectorHash(key, hashId, hashPriority);
            }
          } else if (root && !hashId && (mergedKey === '&' || mergedKey === '')) {
            // In case of `{ '&': { a: { color: 'red' } } }` or `{ '': { a: { color: 'red' } } }` without hashId,
            // we will get `&{a:{color:red;}}` or `{a:{color:red;}}` string for stylis to compile.
            // But it does not conform to stylis syntax,
            // and finally we will get `{color:red;}` as css, which is wrong.
            // So we need to remove key in root, and treat child `{ a: { color: 'red' } }` as root.
            mergedKey = '';
            nextRoot = true;
          }
          const [parsedStr, childEffectStyle] = parseStyle(value, config, {
            root: nextRoot,
            injectHash: subInjectHash,
            parentSelectors: [...parentSelectors, mergedKey]
          });
          effectStyle = {
            ...effectStyle,
            ...childEffectStyle
          };
          styleStr += `${mergedKey}${parsedStr}`;
        } else {
          function appendStyle(cssKey, cssValue) {
            if (process.env.NODE_ENV !== 'production' && (typeof value !== 'object' || !value?.[SKIP_CHECK])) {
              [_linters.contentQuotesLinter, _linters.hashedAnimationLinter, ...linters].forEach(linter => linter(cssKey, cssValue, {
                path,
                hashId,
                parentSelectors
              }));
            }

            // 如果是样式则直接插入
            const styleName = cssKey.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);

            // Auto suffix with px
            let formatValue = cssValue;
            if (!_unitless.default[cssKey] && typeof formatValue === 'number' && formatValue !== 0) {
              formatValue = `${formatValue}px`;
            }

            // handle animationName & Keyframe value
            if (cssKey === 'animationName' && cssValue?._keyframe) {
              parseKeyframes(cssValue);
              formatValue = cssValue.getName(hashId);
            }
            styleStr += `${styleName}:${formatValue};`;
          }
          const actualValue = value?.value ?? value;
          if (typeof value === 'object' && value?.[MULTI_VALUE] && Array.isArray(actualValue)) {
            actualValue.forEach(item => {
              appendStyle(key, item);
            });
          } else {
            if ((0, _util.isNonNullable)(actualValue)) {
              appendStyle(key, actualValue);
            }
          }
        }
      });
    }
  });
  if (!root) {
    styleStr = `{${styleStr}}`;
  } else if (layer) {
    // fixme: https://github.com/thysultan/stylis/pull/339
    if (styleStr) {
      styleStr = `@layer ${layer.name} {${styleStr}}`;
    }
    if (layer.dependencies) {
      effectStyle[`@layer ${layer.name}`] = layer.dependencies.map(deps => `@layer ${deps}, ${layer.name};`).join('\n');
    }
  }
  return [styleStr, effectStyle];
};

// ============================================================================
// ==                                Register                                ==
// ============================================================================
exports.parseStyle = parseStyle;
function uniqueHash(path, styleStr) {
  return (0, _hash.default)(`${path.join('%')}${styleStr}`);
}
const STYLE_PREFIX = exports.STYLE_PREFIX = 'style';
/**
 * Register a style to the global style sheet.
 */
function useStyleRegister(info, styleFn) {
  const {
    path,
    hashId,
    layer,
    nonce,
    clientOnly,
    order = 0
  } = info;
  const {
    mock,
    hashPriority,
    container,
    transformers,
    linters,
    cache,
    layer: enableLayer,
    autoPrefix
  } = React.useContext(_StyleContext.default);
  const fullPath = [hashId || ''];
  if (enableLayer) {
    fullPath.push('layer');
  }
  fullPath.push(...path);

  // Check if need insert style
  let isMergedClientSide = _util.isClientSide;
  if (process.env.NODE_ENV !== 'production' && mock !== undefined) {
    isMergedClientSide = mock === 'client';
  }
  (0, _useGlobalCache.default)(STYLE_PREFIX, fullPath,
  // Create cache if needed
  () => {
    const cachePath = fullPath.join('|');

    // Get style from SSR inline style directly
    if ((0, _cacheMapUtil.existPath)(cachePath)) {
      const [inlineCacheStyleStr, styleHash] = (0, _cacheMapUtil.getStyleAndHash)(cachePath);
      if (inlineCacheStyleStr) {
        return [inlineCacheStyleStr, styleHash, {}, clientOnly, order];
      }
    }

    // Generate style
    const styleObj = styleFn();
    const [parsedStyle, effectStyle] = parseStyle(styleObj, {
      hashId,
      hashPriority,
      layer: enableLayer ? layer : undefined,
      path: path.join('-'),
      transformers,
      linters
    });
    const styleStr = normalizeStyle(parsedStyle, autoPrefix || false);
    const styleId = uniqueHash(fullPath, styleStr);
    return [styleStr, styleId, effectStyle, clientOnly, order];
  },
  // Remove cache if no need
  (cacheValue, fromHMR) => {
    const [, styleId] = cacheValue;
    if (fromHMR && _util.isClientSide) {
      (0, _dynamicCSS.removeCSS)(styleId, {
        mark: _StyleContext.ATTR_MARK,
        attachTo: container
      });
    }
  },
  // Effect: Inject style here
  cacheValue => {
    const [styleStr, styleId, effectStyle,, priority] = cacheValue;
    if (isMergedClientSide && styleStr !== _cacheMapUtil.CSS_FILE_STYLE) {
      let mergedCSSConfig = {
        mark: _StyleContext.ATTR_MARK,
        prepend: enableLayer ? false : 'queue',
        attachTo: container,
        priority
      };
      mergedCSSConfig = (0, _util.injectCSPNonce)(mergedCSSConfig, nonce);

      // ================= Split Effect Style =================
      // We will split effectStyle here since @layer should be at the top level
      const effectLayerKeys = [];
      const effectRestKeys = [];
      Object.keys(effectStyle).forEach(key => {
        if (key.startsWith('@layer')) {
          effectLayerKeys.push(key);
        } else {
          effectRestKeys.push(key);
        }
      });

      // ================= Inject Layer Style =================
      // Inject layer style
      effectLayerKeys.forEach(effectKey => {
        (0, _dynamicCSS.updateCSS)(normalizeStyle(effectStyle[effectKey], autoPrefix || false), `_layer-${effectKey}`, {
          ...mergedCSSConfig,
          prepend: true
        });
      });

      // ==================== Inject Style ====================
      // Inject style
      const style = (0, _dynamicCSS.updateCSS)(styleStr, styleId, mergedCSSConfig);
      style[_StyleContext.CSS_IN_JS_INSTANCE] = cache.instanceId;

      // Debug usage. Dev only
      if (process.env.NODE_ENV !== 'production') {
        style.setAttribute(_StyleContext.ATTR_CACHE_PATH, fullPath.join('|'));
      }

      // ================ Inject Effect Style =================
      // Inject client side effect style
      effectRestKeys.forEach(effectKey => {
        (0, _dynamicCSS.updateCSS)(normalizeStyle(effectStyle[effectKey], autoPrefix || false), `_effect-${effectKey}`, mergedCSSConfig);
      });
    }
  });
}
const extract = (cache, effectStyles, options) => {
  const [styleStr, styleId, effectStyle, clientOnly, order] = cache;
  const {
    plain,
    autoPrefix
  } = options || {};

  // Skip client only style
  if (clientOnly) {
    return null;
  }
  let keyStyleText = styleStr;

  // ====================== Share ======================
  // Used for @rc-component/util
  const sharedAttrs = {
    'data-rc-order': 'prependQueue',
    'data-rc-priority': `${order}`
  };

  // ====================== Style ======================
  keyStyleText = (0, _util.toStyleStr)(styleStr, undefined, styleId, sharedAttrs, plain);

  // =============== Create effect style ===============
  if (effectStyle) {
    Object.keys(effectStyle).forEach(effectKey => {
      // Effect style can be reused
      if (!effectStyles[effectKey]) {
        effectStyles[effectKey] = true;
        const effectStyleStr = normalizeStyle(effectStyle[effectKey], autoPrefix || false);
        const effectStyleHTML = (0, _util.toStyleStr)(effectStyleStr, undefined, `_effect-${effectKey}`, sharedAttrs, plain);
        if (effectKey.startsWith('@layer')) {
          keyStyleText = effectStyleHTML + keyStyleText;
        } else {
          keyStyleText += effectStyleHTML;
        }
      }
    });
  }
  return [order, styleId, keyStyleText];
};
exports.extract = extract;