"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.getBlockSelectors = void 0;
exports.getLayoutStyles = getLayoutStyles;
exports.getNodesWithStyles = exports.getNodesWithSettings = void 0;
exports.getStylesDeclarations = getStylesDeclarations;
exports.processCSSNesting = processCSSNesting;
exports.toStyles = exports.toCustomProperties = void 0;
exports.toSvgFilters = toSvgFilters;
exports.useGlobalStylesOutput = useGlobalStylesOutput;
exports.useGlobalStylesOutputWithConfig = useGlobalStylesOutputWithConfig;
var _blocks = require("@wordpress/blocks");
var _data = require("@wordpress/data");
var _element = require("@wordpress/element");
var _styleEngine = require("@wordpress/style-engine");
var _components = require("@wordpress/components");
var _utils = require("./utils");
var _getBlockCssSelector = require("./get-block-css-selector");
var _typographyUtils = require("./typography-utils");
var _context = require("./context");
var _hooks = require("./hooks");
var _utils2 = require("../duotone/utils");
var _gap = require("../../hooks/gap");
var _background = require("../../hooks/background");
var _store = require("../../store");
var _definitions = require("../../layouts/definitions");
var _object = require("../../utils/object");
var _lockUnlock = require("../../lock-unlock");
/**
 * WordPress dependencies
 */

/**
 * Internal dependencies
 */

// Elements that rely on class names in their selectors.
const ELEMENT_CLASS_NAMES = {
  button: 'wp-element-button',
  caption: 'wp-element-caption'
};

// List of block support features that can have their related styles
// generated under their own feature level selector rather than the block's.
const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = {
  border: 'border',
  color: 'color',
  spacing: 'spacing',
  typography: 'typography'
};
const {
  kebabCase
} = (0, _lockUnlock.unlock)(_components.privateApis);

/**
 * Transform given preset tree into a set of style declarations.
 *
 * @param {Object} blockPresets
 * @param {Object} mergedSettings Merged theme.json settings.
 *
 * @return {Array<Object>} An array of style declarations.
 */
function getPresetsDeclarations(blockPresets = {}, mergedSettings) {
  return _utils.PRESET_METADATA.reduce((declarations, {
    path,
    valueKey,
    valueFunc,
    cssVarInfix
  }) => {
    const presetByOrigin = (0, _object.getValueFromObjectPath)(blockPresets, path, []);
    ['default', 'theme', 'custom'].forEach(origin => {
      if (presetByOrigin[origin]) {
        presetByOrigin[origin].forEach(value => {
          if (valueKey && !valueFunc) {
            declarations.push(`--wp--preset--${cssVarInfix}--${kebabCase(value.slug)}: ${value[valueKey]}`);
          } else if (valueFunc && typeof valueFunc === 'function') {
            declarations.push(`--wp--preset--${cssVarInfix}--${kebabCase(value.slug)}: ${valueFunc(value, mergedSettings)}`);
          }
        });
      }
    });
    return declarations;
  }, []);
}

/**
 * Transform given preset tree into a set of preset class declarations.
 *
 * @param {?string} blockSelector
 * @param {Object}  blockPresets
 * @return {string} CSS declarations for the preset classes.
 */
function getPresetsClasses(blockSelector = '*', blockPresets = {}) {
  return _utils.PRESET_METADATA.reduce((declarations, {
    path,
    cssVarInfix,
    classes
  }) => {
    if (!classes) {
      return declarations;
    }
    const presetByOrigin = (0, _object.getValueFromObjectPath)(blockPresets, path, []);
    ['default', 'theme', 'custom'].forEach(origin => {
      if (presetByOrigin[origin]) {
        presetByOrigin[origin].forEach(({
          slug
        }) => {
          classes.forEach(({
            classSuffix,
            propertyName
          }) => {
            const classSelectorToUse = `.has-${kebabCase(slug)}-${classSuffix}`;
            const selectorToUse = blockSelector.split(',') // Selector can be "h1, h2, h3"
            .map(selector => `${selector}${classSelectorToUse}`).join(',');
            const value = `var(--wp--preset--${cssVarInfix}--${kebabCase(slug)})`;
            declarations += `${selectorToUse}{${propertyName}: ${value} !important;}`;
          });
        });
      }
    });
    return declarations;
  }, '');
}
function getPresetsSvgFilters(blockPresets = {}) {
  return _utils.PRESET_METADATA.filter(
  // Duotone are the only type of filters for now.
  metadata => metadata.path.at(-1) === 'duotone').flatMap(metadata => {
    const presetByOrigin = (0, _object.getValueFromObjectPath)(blockPresets, metadata.path, {});
    return ['default', 'theme'].filter(origin => presetByOrigin[origin]).flatMap(origin => presetByOrigin[origin].map(preset => (0, _utils2.getDuotoneFilter)(`wp-duotone-${preset.slug}`, preset.colors))).join('');
  });
}
function flattenTree(input = {}, prefix, token) {
  let result = [];
  Object.keys(input).forEach(key => {
    const newKey = prefix + kebabCase(key.replace('/', '-'));
    const newLeaf = input[key];
    if (newLeaf instanceof Object) {
      const newPrefix = newKey + token;
      result = [...result, ...flattenTree(newLeaf, newPrefix, token)];
    } else {
      result.push(`${newKey}: ${newLeaf}`);
    }
  });
  return result;
}

/**
 * Gets variation selector string from feature selector.
 *
 * @param {string} featureSelector        The feature selector.
 *
 * @param {string} styleVariationSelector The style variation selector.
 * @return {string} Combined selector string.
 */
function concatFeatureVariationSelectorString(featureSelector, styleVariationSelector) {
  const featureSelectors = featureSelector.split(',');
  const combinedSelectors = [];
  featureSelectors.forEach(selector => {
    combinedSelectors.push(`${styleVariationSelector.trim()}${selector.trim()}`);
  });
  return combinedSelectors.join(', ');
}

/**
 * Generate style declarations for a block's custom feature and subfeature
 * selectors.
 *
 * NOTE: The passed `styles` object will be mutated by this function.
 *
 * @param {Object} selectors Custom selectors object for a block.
 * @param {Object} styles    A block's styles object.
 *
 * @return {Object} Style declarations.
 */
const getFeatureDeclarations = (selectors, styles) => {
  const declarations = {};
  Object.entries(selectors).forEach(([feature, selector]) => {
    // We're only processing features/subfeatures that have styles.
    if (feature === 'root' || !styles?.[feature]) {
      return;
    }
    const isShorthand = typeof selector === 'string';

    // If we have a selector object instead of shorthand process it.
    if (!isShorthand) {
      Object.entries(selector).forEach(([subfeature, subfeatureSelector]) => {
        // Don't process root feature selector yet or any
        // subfeature that doesn't have a style.
        if (subfeature === 'root' || !styles?.[feature][subfeature]) {
          return;
        }

        // Create a temporary styles object and build
        // declarations for subfeature.
        const subfeatureStyles = {
          [feature]: {
            [subfeature]: styles[feature][subfeature]
          }
        };
        const newDeclarations = getStylesDeclarations(subfeatureStyles);

        // Merge new declarations in with any others that
        // share the same selector.
        declarations[subfeatureSelector] = [...(declarations[subfeatureSelector] || []), ...newDeclarations];

        // Remove the subfeature's style now it will be
        // included under its own selector not the block's.
        delete styles[feature][subfeature];
      });
    }

    // Now subfeatures have been processed and removed, we can
    // process root, or shorthand, feature selectors.
    if (isShorthand || selector.root) {
      const featureSelector = isShorthand ? selector : selector.root;

      // Create temporary style object and build declarations for feature.
      const featureStyles = {
        [feature]: styles[feature]
      };
      const newDeclarations = getStylesDeclarations(featureStyles);

      // Merge new declarations with any others that share the selector.
      declarations[featureSelector] = [...(declarations[featureSelector] || []), ...newDeclarations];

      // Remove the feature from the block's styles now as it will be
      // included under its own selector not the block's.
      delete styles[feature];
    }
  });
  return declarations;
};

/**
 * Transform given style tree into a set of style declarations.
 *
 * @param {Object}  blockStyles         Block styles.
 *
 * @param {string}  selector            The selector these declarations should attach to.
 *
 * @param {boolean} useRootPaddingAlign Whether to use CSS custom properties in root selector.
 *
 * @param {Object}  tree                A theme.json tree containing layout definitions.
 *
 * @param {boolean} disableRootPadding  Whether to force disable the root padding styles.
 * @return {Array} An array of style declarations.
 */
function getStylesDeclarations(blockStyles = {}, selector = '', useRootPaddingAlign, tree = {}, disableRootPadding = false) {
  const isRoot = _utils.ROOT_BLOCK_SELECTOR === selector;
  const output = Object.entries(_blocks.__EXPERIMENTAL_STYLE_PROPERTY).reduce((declarations, [key, {
    value,
    properties,
    useEngine,
    rootOnly
  }]) => {
    if (rootOnly && !isRoot) {
      return declarations;
    }
    const pathToValue = value;
    if (pathToValue[0] === 'elements' || useEngine) {
      return declarations;
    }
    const styleValue = (0, _object.getValueFromObjectPath)(blockStyles, pathToValue);

    // Root-level padding styles don't currently support strings with CSS shorthand values.
    // This may change: https://github.com/WordPress/gutenberg/issues/40132.
    if (key === '--wp--style--root--padding' && (typeof styleValue === 'string' || !useRootPaddingAlign)) {
      return declarations;
    }
    if (properties && typeof styleValue !== 'string') {
      Object.entries(properties).forEach(entry => {
        const [name, prop] = entry;
        if (!(0, _object.getValueFromObjectPath)(styleValue, [prop], false)) {
          // Do not create a declaration
          // for sub-properties that don't have any value.
          return;
        }
        const cssProperty = name.startsWith('--') ? name : kebabCase(name);
        declarations.push(`${cssProperty}: ${(0, _styleEngine.getCSSValueFromRawStyle)((0, _object.getValueFromObjectPath)(styleValue, [prop]))}`);
      });
    } else if ((0, _object.getValueFromObjectPath)(blockStyles, pathToValue, false)) {
      const cssProperty = key.startsWith('--') ? key : kebabCase(key);
      declarations.push(`${cssProperty}: ${(0, _styleEngine.getCSSValueFromRawStyle)((0, _object.getValueFromObjectPath)(blockStyles, pathToValue))}`);
    }
    return declarations;
  }, []);

  /*
   * Preprocess background image values.
   *
   * Note: As we absorb more and more styles into the engine, we could simplify this function.
   * A refactor is for the style engine to handle ref resolution (and possibly defaults)
   * via a public util used internally and externally. Theme.json tree and defaults could be passed
   * as options.
   */
  if (!!blockStyles.background) {
    /*
     * Resolve dynamic values before they are compiled by the style engine,
     * which doesn't (yet) resolve dynamic values.
     */
    if (blockStyles.background?.backgroundImage) {
      blockStyles.background.backgroundImage = (0, _utils.getResolvedValue)(blockStyles.background.backgroundImage, tree);
    }

    /*
     * Set default values for block background styles.
     * Top-level styles are an exception as they are applied to the body.
     */
    if (!isRoot && !!blockStyles.background?.backgroundImage?.id) {
      blockStyles = {
        ...blockStyles,
        background: {
          ...blockStyles.background,
          ...(0, _background.setBackgroundStyleDefaults)(blockStyles.background)
        }
      };
    }
  }
  const extraRules = (0, _styleEngine.getCSSRules)(blockStyles);
  extraRules.forEach(rule => {
    // Don't output padding properties if padding variables are set or if we're not editing a full template.
    if (isRoot && (useRootPaddingAlign || disableRootPadding) && rule.key.startsWith('padding')) {
      return;
    }
    const cssProperty = rule.key.startsWith('--') ? rule.key : kebabCase(rule.key);
    let ruleValue = (0, _utils.getResolvedValue)(rule.value, tree, null);

    // Calculate fluid typography rules where available.
    if (cssProperty === 'font-size') {
      /*
       * getTypographyFontSizeValue() will check
       * if fluid typography has been activated and also
       * whether the incoming value can be converted to a fluid value.
       * Values that already have a "clamp()" function will not pass the test,
       * and therefore the original $value will be returned.
       */
      ruleValue = (0, _typographyUtils.getTypographyFontSizeValue)({
        size: ruleValue
      }, tree?.settings);
    }

    // For aspect ratio to work, other dimensions rules (and Cover block defaults) must be unset.
    // This ensures that a fixed height does not override the aspect ratio.
    if (cssProperty === 'aspect-ratio') {
      output.push('min-height: unset');
    }
    output.push(`${cssProperty}: ${ruleValue}`);
  });
  return output;
}

/**
 * Get generated CSS for layout styles by looking up layout definitions provided
 * in theme.json, and outputting common layout styles, and specific blockGap values.
 *
 * @param {Object}  props
 * @param {Object}  props.layoutDefinitions     Layout definitions, keyed by layout type.
 * @param {Object}  props.style                 A style object containing spacing values.
 * @param {string}  props.selector              Selector used to group together layout styling rules.
 * @param {boolean} props.hasBlockGapSupport    Whether or not the theme opts-in to blockGap support.
 * @param {boolean} props.hasFallbackGapSupport Whether or not the theme allows fallback gap styles.
 * @param {?string} props.fallbackGapValue      An optional fallback gap value if no real gap value is available.
 * @return {string} Generated CSS rules for the layout styles.
 */
function getLayoutStyles({
  layoutDefinitions = _definitions.LAYOUT_DEFINITIONS,
  style,
  selector,
  hasBlockGapSupport,
  hasFallbackGapSupport,
  fallbackGapValue
}) {
  let ruleset = '';
  let gapValue = hasBlockGapSupport ? (0, _gap.getGapCSSValue)(style?.spacing?.blockGap) : '';

  // Ensure a fallback gap value for the root layout definitions,
  // and use a fallback value if one is provided for the current block.
  if (hasFallbackGapSupport) {
    if (selector === _utils.ROOT_BLOCK_SELECTOR) {
      gapValue = !gapValue ? '0.5em' : gapValue;
    } else if (!hasBlockGapSupport && fallbackGapValue) {
      gapValue = fallbackGapValue;
    }
  }
  if (gapValue && layoutDefinitions) {
    Object.values(layoutDefinitions).forEach(({
      className,
      name,
      spacingStyles
    }) => {
      // Allow outputting fallback gap styles for flex layout type when block gap support isn't available.
      if (!hasBlockGapSupport && 'flex' !== name && 'grid' !== name) {
        return;
      }
      if (spacingStyles?.length) {
        spacingStyles.forEach(spacingStyle => {
          const declarations = [];
          if (spacingStyle.rules) {
            Object.entries(spacingStyle.rules).forEach(([cssProperty, cssValue]) => {
              declarations.push(`${cssProperty}: ${cssValue ? cssValue : gapValue}`);
            });
          }
          if (declarations.length) {
            let combinedSelector = '';
            if (!hasBlockGapSupport) {
              // For fallback gap styles, use lower specificity, to ensure styles do not unintentionally override theme styles.
              combinedSelector = selector === _utils.ROOT_BLOCK_SELECTOR ? `:where(.${className}${spacingStyle?.selector || ''})` : `:where(${selector}.${className}${spacingStyle?.selector || ''})`;
            } else {
              combinedSelector = selector === _utils.ROOT_BLOCK_SELECTOR ? `:root :where(.${className})${spacingStyle?.selector || ''}` : `:root :where(${selector}-${className})${spacingStyle?.selector || ''}`;
            }
            ruleset += `${combinedSelector} { ${declarations.join('; ')}; }`;
          }
        });
      }
    });
    // For backwards compatibility, ensure the legacy block gap CSS variable is still available.
    if (selector === _utils.ROOT_BLOCK_SELECTOR && hasBlockGapSupport) {
      ruleset += `${_utils.ROOT_CSS_PROPERTIES_SELECTOR} { --wp--style--block-gap: ${gapValue}; }`;
    }
  }

  // Output base styles
  if (selector === _utils.ROOT_BLOCK_SELECTOR && layoutDefinitions) {
    const validDisplayModes = ['block', 'flex', 'grid'];
    Object.values(layoutDefinitions).forEach(({
      className,
      displayMode,
      baseStyles
    }) => {
      if (displayMode && validDisplayModes.includes(displayMode)) {
        ruleset += `${selector} .${className} { display:${displayMode}; }`;
      }
      if (baseStyles?.length) {
        baseStyles.forEach(baseStyle => {
          const declarations = [];
          if (baseStyle.rules) {
            Object.entries(baseStyle.rules).forEach(([cssProperty, cssValue]) => {
              declarations.push(`${cssProperty}: ${cssValue}`);
            });
          }
          if (declarations.length) {
            const combinedSelector = `.${className}${baseStyle?.selector || ''}`;
            ruleset += `${combinedSelector} { ${declarations.join('; ')}; }`;
          }
        });
      }
    });
  }
  return ruleset;
}
const STYLE_KEYS = ['border', 'color', 'dimensions', 'spacing', 'typography', 'filter', 'outline', 'shadow', 'background'];
function pickStyleKeys(treeToPickFrom) {
  if (!treeToPickFrom) {
    return {};
  }
  const entries = Object.entries(treeToPickFrom);
  const pickedEntries = entries.filter(([key]) => STYLE_KEYS.includes(key));
  // clone the style objects so that `getFeatureDeclarations` can remove consumed keys from it
  const clonedEntries = pickedEntries.map(([key, style]) => [key, JSON.parse(JSON.stringify(style))]);
  return Object.fromEntries(clonedEntries);
}
const getNodesWithStyles = (tree, blockSelectors) => {
  var _tree$styles$blocks;
  const nodes = [];
  if (!tree?.styles) {
    return nodes;
  }

  // Top-level.
  const styles = pickStyleKeys(tree.styles);
  if (styles) {
    nodes.push({
      styles,
      selector: _utils.ROOT_BLOCK_SELECTOR,
      // Root selector (body) styles should not be wrapped in `:root where()` to keep
      // specificity at (0,0,1) and maintain backwards compatibility.
      skipSelectorWrapper: true
    });
  }
  Object.entries(_blocks.__EXPERIMENTAL_ELEMENTS).forEach(([name, selector]) => {
    if (tree.styles?.elements?.[name]) {
      nodes.push({
        styles: tree.styles?.elements?.[name],
        selector,
        // Top level elements that don't use a class name should not receive the
        // `:root :where()` wrapper to maintain backwards compatibility.
        skipSelectorWrapper: !ELEMENT_CLASS_NAMES[name]
      });
    }
  });

  // Iterate over blocks: they can have styles & elements.
  Object.entries((_tree$styles$blocks = tree.styles?.blocks) !== null && _tree$styles$blocks !== void 0 ? _tree$styles$blocks : {}).forEach(([blockName, node]) => {
    var _node$elements;
    const blockStyles = pickStyleKeys(node);
    if (node?.variations) {
      const variations = {};
      Object.entries(node.variations).forEach(([variationName, variation]) => {
        var _variation$elements, _variation$blocks;
        variations[variationName] = pickStyleKeys(variation);
        if (variation?.css) {
          variations[variationName].css = variation.css;
        }
        const variationSelector = blockSelectors[blockName]?.styleVariationSelectors?.[variationName];

        // Process the variation's inner element styles.
        // This comes before the inner block styles so the
        // element styles within the block type styles take
        // precedence over these.
        Object.entries((_variation$elements = variation?.elements) !== null && _variation$elements !== void 0 ? _variation$elements : {}).forEach(([element, elementStyles]) => {
          if (elementStyles && _blocks.__EXPERIMENTAL_ELEMENTS[element]) {
            nodes.push({
              styles: elementStyles,
              selector: (0, _utils.scopeSelector)(variationSelector, _blocks.__EXPERIMENTAL_ELEMENTS[element])
            });
          }
        });

        // Process the variations inner block type styles.
        Object.entries((_variation$blocks = variation?.blocks) !== null && _variation$blocks !== void 0 ? _variation$blocks : {}).forEach(([variationBlockName, variationBlockStyles]) => {
          var _variationBlockStyles;
          const variationBlockSelector = (0, _utils.scopeSelector)(variationSelector, blockSelectors[variationBlockName]?.selector);
          const variationDuotoneSelector = (0, _utils.scopeSelector)(variationSelector, blockSelectors[variationBlockName]?.duotoneSelector);
          const variationFeatureSelectors = (0, _utils.scopeFeatureSelectors)(variationSelector, blockSelectors[variationBlockName]?.featureSelectors);
          const variationBlockStyleNodes = pickStyleKeys(variationBlockStyles);
          if (variationBlockStyles?.css) {
            variationBlockStyleNodes.css = variationBlockStyles.css;
          }
          nodes.push({
            selector: variationBlockSelector,
            duotoneSelector: variationDuotoneSelector,
            featureSelectors: variationFeatureSelectors,
            fallbackGapValue: blockSelectors[variationBlockName]?.fallbackGapValue,
            hasLayoutSupport: blockSelectors[variationBlockName]?.hasLayoutSupport,
            styles: variationBlockStyleNodes
          });

          // Process element styles for the inner blocks
          // of the variation.
          Object.entries((_variationBlockStyles = variationBlockStyles.elements) !== null && _variationBlockStyles !== void 0 ? _variationBlockStyles : {}).forEach(([variationBlockElement, variationBlockElementStyles]) => {
            if (variationBlockElementStyles && _blocks.__EXPERIMENTAL_ELEMENTS[variationBlockElement]) {
              nodes.push({
                styles: variationBlockElementStyles,
                selector: (0, _utils.scopeSelector)(variationBlockSelector, _blocks.__EXPERIMENTAL_ELEMENTS[variationBlockElement])
              });
            }
          });
        });
      });
      blockStyles.variations = variations;
    }
    if (blockSelectors?.[blockName]?.selector) {
      nodes.push({
        duotoneSelector: blockSelectors[blockName].duotoneSelector,
        fallbackGapValue: blockSelectors[blockName].fallbackGapValue,
        hasLayoutSupport: blockSelectors[blockName].hasLayoutSupport,
        selector: blockSelectors[blockName].selector,
        styles: blockStyles,
        featureSelectors: blockSelectors[blockName].featureSelectors,
        styleVariationSelectors: blockSelectors[blockName].styleVariationSelectors
      });
    }
    Object.entries((_node$elements = node?.elements) !== null && _node$elements !== void 0 ? _node$elements : {}).forEach(([elementName, value]) => {
      if (value && blockSelectors?.[blockName] && _blocks.__EXPERIMENTAL_ELEMENTS[elementName]) {
        nodes.push({
          styles: value,
          selector: blockSelectors[blockName]?.selector.split(',').map(sel => {
            const elementSelectors = _blocks.__EXPERIMENTAL_ELEMENTS[elementName].split(',');
            return elementSelectors.map(elementSelector => sel + ' ' + elementSelector);
          }).join(',')
        });
      }
    });
  });
  return nodes;
};
exports.getNodesWithStyles = getNodesWithStyles;
const getNodesWithSettings = (tree, blockSelectors) => {
  var _tree$settings$blocks;
  const nodes = [];
  if (!tree?.settings) {
    return nodes;
  }
  const pickPresets = treeToPickFrom => {
    let presets = {};
    _utils.PRESET_METADATA.forEach(({
      path
    }) => {
      const value = (0, _object.getValueFromObjectPath)(treeToPickFrom, path, false);
      if (value !== false) {
        presets = (0, _object.setImmutably)(presets, path, value);
      }
    });
    return presets;
  };

  // Top-level.
  const presets = pickPresets(tree.settings);
  const custom = tree.settings?.custom;
  if (Object.keys(presets).length > 0 || custom) {
    nodes.push({
      presets,
      custom,
      selector: _utils.ROOT_CSS_PROPERTIES_SELECTOR
    });
  }

  // Blocks.
  Object.entries((_tree$settings$blocks = tree.settings?.blocks) !== null && _tree$settings$blocks !== void 0 ? _tree$settings$blocks : {}).forEach(([blockName, node]) => {
    const blockPresets = pickPresets(node);
    const blockCustom = node.custom;
    if (Object.keys(blockPresets).length > 0 || blockCustom) {
      nodes.push({
        presets: blockPresets,
        custom: blockCustom,
        selector: blockSelectors[blockName]?.selector
      });
    }
  });
  return nodes;
};
exports.getNodesWithSettings = getNodesWithSettings;
const toCustomProperties = (tree, blockSelectors) => {
  const settings = getNodesWithSettings(tree, blockSelectors);
  let ruleset = '';
  settings.forEach(({
    presets,
    custom,
    selector
  }) => {
    const declarations = getPresetsDeclarations(presets, tree?.settings);
    const customProps = flattenTree(custom, '--wp--custom--', '--');
    if (customProps.length > 0) {
      declarations.push(...customProps);
    }
    if (declarations.length > 0) {
      ruleset += `${selector}{${declarations.join(';')};}`;
    }
  });
  return ruleset;
};
exports.toCustomProperties = toCustomProperties;
const toStyles = (tree, blockSelectors, hasBlockGapSupport, hasFallbackGapSupport, disableLayoutStyles = false, disableRootPadding = false, styleOptions = undefined) => {
  // These allow opting out of certain sets of styles.
  const options = {
    blockGap: true,
    blockStyles: true,
    layoutStyles: true,
    marginReset: true,
    presets: true,
    rootPadding: true,
    variationStyles: false,
    ...styleOptions
  };
  const nodesWithStyles = getNodesWithStyles(tree, blockSelectors);
  const nodesWithSettings = getNodesWithSettings(tree, blockSelectors);
  const useRootPaddingAlign = tree?.settings?.useRootPaddingAwareAlignments;
  const {
    contentSize,
    wideSize
  } = tree?.settings?.layout || {};
  const hasBodyStyles = options.marginReset || options.rootPadding || options.layoutStyles;
  let ruleset = '';
  if (options.presets && (contentSize || wideSize)) {
    ruleset += `${_utils.ROOT_CSS_PROPERTIES_SELECTOR} {`;
    ruleset = contentSize ? ruleset + ` --wp--style--global--content-size: ${contentSize};` : ruleset;
    ruleset = wideSize ? ruleset + ` --wp--style--global--wide-size: ${wideSize};` : ruleset;
    ruleset += '}';
  }
  if (hasBodyStyles) {
    /*
     * Reset default browser margin on the body element.
     * This is set on the body selector **before** generating the ruleset
     * from the `theme.json`. This is to ensure that if the `theme.json` declares
     * `margin` in its `spacing` declaration for the `body` element then these
     * user-generated values take precedence in the CSS cascade.
     * @link https://github.com/WordPress/gutenberg/issues/36147.
     */
    ruleset += ':where(body) {margin: 0;';

    // Root padding styles should be output for full templates, patterns and template parts.
    if (options.rootPadding && useRootPaddingAlign) {
      /*
       * These rules reproduce the ones from https://github.com/WordPress/gutenberg/blob/79103f124925d1f457f627e154f52a56228ed5ad/lib/class-wp-theme-json-gutenberg.php#L2508
       * almost exactly, but for the selectors that target block wrappers in the front end. This code only runs in the editor, so it doesn't need those selectors.
       */
      ruleset += `padding-right: 0; padding-left: 0; padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom) }
				.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }
				.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }
				.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) { padding-right: 0; padding-left: 0; }
				.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) > .alignfull { margin-left: 0; margin-right: 0;
				`;
    }
    ruleset += '}';
  }
  if (options.blockStyles) {
    nodesWithStyles.forEach(({
      selector,
      duotoneSelector,
      styles,
      fallbackGapValue,
      hasLayoutSupport,
      featureSelectors,
      styleVariationSelectors,
      skipSelectorWrapper
    }) => {
      // Process styles for block support features with custom feature level
      // CSS selectors set.
      if (featureSelectors) {
        const featureDeclarations = getFeatureDeclarations(featureSelectors, styles);
        Object.entries(featureDeclarations).forEach(([cssSelector, declarations]) => {
          if (declarations.length) {
            const rules = declarations.join(';');
            ruleset += `:root :where(${cssSelector}){${rules};}`;
          }
        });
      }

      // Process duotone styles.
      if (duotoneSelector) {
        const duotoneStyles = {};
        if (styles?.filter) {
          duotoneStyles.filter = styles.filter;
          delete styles.filter;
        }
        const duotoneDeclarations = getStylesDeclarations(duotoneStyles);
        if (duotoneDeclarations.length) {
          ruleset += `${duotoneSelector}{${duotoneDeclarations.join(';')};}`;
        }
      }

      // Process blockGap and layout styles.
      if (!disableLayoutStyles && (_utils.ROOT_BLOCK_SELECTOR === selector || hasLayoutSupport)) {
        ruleset += getLayoutStyles({
          style: styles,
          selector,
          hasBlockGapSupport,
          hasFallbackGapSupport,
          fallbackGapValue
        });
      }

      // Process the remaining block styles (they use either normal block class or __experimentalSelector).
      const styleDeclarations = getStylesDeclarations(styles, selector, useRootPaddingAlign, tree, disableRootPadding);
      if (styleDeclarations?.length) {
        const generalSelector = skipSelectorWrapper ? selector : `:root :where(${selector})`;
        ruleset += `${generalSelector}{${styleDeclarations.join(';')};}`;
      }
      if (styles?.css) {
        ruleset += processCSSNesting(styles.css, `:root :where(${selector})`);
      }
      if (options.variationStyles && styleVariationSelectors) {
        Object.entries(styleVariationSelectors).forEach(([styleVariationName, styleVariationSelector]) => {
          const styleVariations = styles?.variations?.[styleVariationName];
          if (styleVariations) {
            // If the block uses any custom selectors for block support, add those first.
            if (featureSelectors) {
              const featureDeclarations = getFeatureDeclarations(featureSelectors, styleVariations);
              Object.entries(featureDeclarations).forEach(([baseSelector, declarations]) => {
                if (declarations.length) {
                  const cssSelector = concatFeatureVariationSelectorString(baseSelector, styleVariationSelector);
                  const rules = declarations.join(';');
                  ruleset += `:root :where(${cssSelector}){${rules};}`;
                }
              });
            }

            // Otherwise add regular selectors.
            const styleVariationDeclarations = getStylesDeclarations(styleVariations, styleVariationSelector, useRootPaddingAlign, tree);
            if (styleVariationDeclarations.length) {
              ruleset += `:root :where(${styleVariationSelector}){${styleVariationDeclarations.join(';')};}`;
            }
            if (styleVariations?.css) {
              ruleset += processCSSNesting(styleVariations.css, `:root :where(${styleVariationSelector})`);
            }
          }
        });
      }

      // Check for pseudo selector in `styles` and handle separately.
      const pseudoSelectorStyles = Object.entries(styles).filter(([key]) => key.startsWith(':'));
      if (pseudoSelectorStyles?.length) {
        pseudoSelectorStyles.forEach(([pseudoKey, pseudoStyle]) => {
          const pseudoDeclarations = getStylesDeclarations(pseudoStyle);
          if (!pseudoDeclarations?.length) {
            return;
          }

          // `selector` may be provided in a form
          // where block level selectors have sub element
          // selectors appended to them as a comma separated
          // string.
          // e.g. `h1 a,h2 a,h3 a,h4 a,h5 a,h6 a`;
          // Split and append pseudo selector to create
          // the proper rules to target the elements.
          const _selector = selector.split(',').map(sel => sel + pseudoKey).join(',');

          // As pseudo classes such as :hover, :focus etc. have class-level
          // specificity, they must use the `:root :where()` wrapper. This.
          // caps the specificity at `0-1-0` to allow proper nesting of variations
          // and block type element styles.
          const pseudoRule = `:root :where(${_selector}){${pseudoDeclarations.join(';')};}`;
          ruleset += pseudoRule;
        });
      }
    });
  }
  if (options.layoutStyles) {
    /* Add alignment / layout styles */
    ruleset = ruleset + '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }';
    ruleset = ruleset + '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }';
    ruleset = ruleset + '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }';
  }
  if (options.blockGap && hasBlockGapSupport) {
    // Use fallback of `0.5em` just in case, however if there is blockGap support, there should nearly always be a real value.
    const gapValue = (0, _gap.getGapCSSValue)(tree?.styles?.spacing?.blockGap) || '0.5em';
    ruleset = ruleset + `:root :where(.wp-site-blocks) > * { margin-block-start: ${gapValue}; margin-block-end: 0; }`;
    ruleset = ruleset + ':root :where(.wp-site-blocks) > :first-child { margin-block-start: 0; }';
    ruleset = ruleset + ':root :where(.wp-site-blocks) > :last-child { margin-block-end: 0; }';
  }
  if (options.presets) {
    nodesWithSettings.forEach(({
      selector,
      presets
    }) => {
      if (_utils.ROOT_BLOCK_SELECTOR === selector || _utils.ROOT_CSS_PROPERTIES_SELECTOR === selector) {
        // Do not add extra specificity for top-level classes.
        selector = '';
      }
      const classes = getPresetsClasses(selector, presets);
      if (classes.length > 0) {
        ruleset += classes;
      }
    });
  }
  return ruleset;
};
exports.toStyles = toStyles;
function toSvgFilters(tree, blockSelectors) {
  const nodesWithSettings = getNodesWithSettings(tree, blockSelectors);
  return nodesWithSettings.flatMap(({
    presets
  }) => {
    return getPresetsSvgFilters(presets);
  });
}
const getSelectorsConfig = (blockType, rootSelector) => {
  if (blockType?.selectors && Object.keys(blockType.selectors).length > 0) {
    return blockType.selectors;
  }
  const config = {
    root: rootSelector
  };
  Object.entries(BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS).forEach(([featureKey, featureName]) => {
    const featureSelector = (0, _getBlockCssSelector.getBlockCSSSelector)(blockType, featureKey);
    if (featureSelector) {
      config[featureName] = featureSelector;
    }
  });
  return config;
};
const getBlockSelectors = (blockTypes, getBlockStyles, variationInstanceId) => {
  const result = {};
  blockTypes.forEach(blockType => {
    const name = blockType.name;
    const selector = (0, _getBlockCssSelector.getBlockCSSSelector)(blockType);
    let duotoneSelector = (0, _getBlockCssSelector.getBlockCSSSelector)(blockType, 'filter.duotone');

    // Keep backwards compatibility for support.color.__experimentalDuotone.
    if (!duotoneSelector) {
      const rootSelector = (0, _getBlockCssSelector.getBlockCSSSelector)(blockType);
      const duotoneSupport = (0, _blocks.getBlockSupport)(blockType, 'color.__experimentalDuotone', false);
      duotoneSelector = duotoneSupport && (0, _utils.scopeSelector)(rootSelector, duotoneSupport);
    }
    const hasLayoutSupport = !!blockType?.supports?.layout || !!blockType?.supports?.__experimentalLayout;
    const fallbackGapValue = blockType?.supports?.spacing?.blockGap?.__experimentalDefault;
    const blockStyleVariations = getBlockStyles(name);
    const styleVariationSelectors = {};
    blockStyleVariations?.forEach(variation => {
      const variationSuffix = variationInstanceId ? `-${variationInstanceId}` : '';
      const variationName = `${variation.name}${variationSuffix}`;
      const styleVariationSelector = (0, _utils.getBlockStyleVariationSelector)(variationName, selector);
      styleVariationSelectors[variationName] = styleVariationSelector;
    });

    // For each block support feature add any custom selectors.
    const featureSelectors = getSelectorsConfig(blockType, selector);
    result[name] = {
      duotoneSelector,
      fallbackGapValue,
      featureSelectors: Object.keys(featureSelectors).length ? featureSelectors : undefined,
      hasLayoutSupport,
      name,
      selector,
      styleVariationSelectors: blockStyleVariations?.length ? styleVariationSelectors : undefined
    };
  });
  return result;
};

/**
 * If there is a separator block whose color is defined in theme.json via background,
 * update the separator color to the same value by using border color.
 *
 * @param {Object} config Theme.json configuration file object.
 * @return {Object} configTheme.json configuration file object updated.
 */
exports.getBlockSelectors = getBlockSelectors;
function updateConfigWithSeparator(config) {
  const needsSeparatorStyleUpdate = config.styles?.blocks?.['core/separator'] && config.styles?.blocks?.['core/separator'].color?.background && !config.styles?.blocks?.['core/separator'].color?.text && !config.styles?.blocks?.['core/separator'].border?.color;
  if (needsSeparatorStyleUpdate) {
    return {
      ...config,
      styles: {
        ...config.styles,
        blocks: {
          ...config.styles.blocks,
          'core/separator': {
            ...config.styles.blocks['core/separator'],
            color: {
              ...config.styles.blocks['core/separator'].color,
              text: config.styles?.blocks['core/separator'].color.background
            }
          }
        }
      }
    };
  }
  return config;
}
function processCSSNesting(css, blockSelector) {
  let processedCSS = '';
  if (!css || css.trim() === '') {
    return processedCSS;
  }

  // Split CSS nested rules.
  const parts = css.split('&');
  parts.forEach(part => {
    if (!part || part.trim() === '') {
      return;
    }
    const isRootCss = !part.includes('{');
    if (isRootCss) {
      // If the part doesn't contain braces, it applies to the root level.
      processedCSS += `:root :where(${blockSelector}){${part.trim()}}`;
    } else {
      // If the part contains braces, it's a nested CSS rule.
      const splittedPart = part.replace('}', '').split('{');
      if (splittedPart.length !== 2) {
        return;
      }
      const [nestedSelector, cssValue] = splittedPart;

      // Handle pseudo elements such as ::before, ::after, etc. Regex will also
      // capture any leading combinator such as >, +, or ~, as well as spaces.
      // This allows pseudo elements as descendants e.g. `.parent ::before`.
      const matches = nestedSelector.match(/([>+~\s]*::[a-zA-Z-]+)/);
      const pseudoPart = matches ? matches[1] : '';
      const withoutPseudoElement = matches ? nestedSelector.replace(pseudoPart, '').trim() : nestedSelector.trim();
      let combinedSelector;
      if (withoutPseudoElement === '') {
        // Only contained a pseudo element to use the block selector to form
        // the final `:root :where()` selector.
        combinedSelector = blockSelector;
      } else {
        // If the nested selector is a descendant of the block scope it with the
        // block selector. Otherwise append it to the block selector.
        combinedSelector = nestedSelector.startsWith(' ') ? (0, _utils.scopeSelector)(blockSelector, withoutPseudoElement) : (0, _utils.appendToSelector)(blockSelector, withoutPseudoElement);
      }

      // Build final rule, re-adding any pseudo element outside the `:where()`
      // to maintain valid CSS selector.
      processedCSS += `:root :where(${combinedSelector})${pseudoPart}{${cssValue.trim()}}`;
    }
  });
  return processedCSS;
}

/**
 * Returns the global styles output using a global styles configuration.
 * If wishing to generate global styles and settings based on the
 * global styles config loaded in the editor context, use `useGlobalStylesOutput()`.
 * The use case for a custom config is to generate bespoke styles
 * and settings for previews, or other out-of-editor experiences.
 *
 * @param {Object}  mergedConfig       Global styles configuration.
 * @param {boolean} disableRootPadding Disable root padding styles.
 *
 * @return {Array} Array of stylesheets and settings.
 */
function useGlobalStylesOutputWithConfig(mergedConfig = {}, disableRootPadding) {
  const [blockGap] = (0, _hooks.useGlobalSetting)('spacing.blockGap');
  const hasBlockGapSupport = blockGap !== null;
  const hasFallbackGapSupport = !hasBlockGapSupport; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback styles support.
  const disableLayoutStyles = (0, _data.useSelect)(select => {
    const {
      getSettings
    } = select(_store.store);
    return !!getSettings().disableLayoutStyles;
  });
  const {
    getBlockStyles
  } = (0, _data.useSelect)(_blocks.store);
  return (0, _element.useMemo)(() => {
    var _updatedConfig$styles;
    if (!mergedConfig?.styles || !mergedConfig?.settings) {
      return [];
    }
    const updatedConfig = updateConfigWithSeparator(mergedConfig);
    const blockSelectors = getBlockSelectors((0, _blocks.getBlockTypes)(), getBlockStyles);
    const customProperties = toCustomProperties(updatedConfig, blockSelectors);
    const globalStyles = toStyles(updatedConfig, blockSelectors, hasBlockGapSupport, hasFallbackGapSupport, disableLayoutStyles, disableRootPadding);
    const svgs = toSvgFilters(updatedConfig, blockSelectors);
    const styles = [{
      css: customProperties,
      isGlobalStyles: true
    }, {
      css: globalStyles,
      isGlobalStyles: true
    },
    // Load custom CSS in own stylesheet so that any invalid CSS entered in the input won't break all the global styles in the editor.
    {
      css: (_updatedConfig$styles = updatedConfig.styles.css) !== null && _updatedConfig$styles !== void 0 ? _updatedConfig$styles : '',
      isGlobalStyles: true
    }, {
      assets: svgs,
      __unstableType: 'svg',
      isGlobalStyles: true
    }];

    // Loop through the blocks to check if there are custom CSS values.
    // If there are, get the block selector and push the selector together with
    // the CSS value to the 'stylesheets' array.
    (0, _blocks.getBlockTypes)().forEach(blockType => {
      if (updatedConfig.styles.blocks[blockType.name]?.css) {
        const selector = blockSelectors[blockType.name].selector;
        styles.push({
          css: processCSSNesting(updatedConfig.styles.blocks[blockType.name]?.css, selector),
          isGlobalStyles: true
        });
      }
    });
    return [styles, updatedConfig.settings];
  }, [hasBlockGapSupport, hasFallbackGapSupport, mergedConfig, disableLayoutStyles, disableRootPadding, getBlockStyles]);
}

/**
 * Returns the global styles output based on the current state of global styles config loaded in the editor context.
 *
 * @param {boolean} disableRootPadding Disable root padding styles.
 *
 * @return {Array} Array of stylesheets and settings.
 */
function useGlobalStylesOutput(disableRootPadding = false) {
  const {
    merged: mergedConfig
  } = (0, _element.useContext)(_context.GlobalStylesContext);
  return useGlobalStylesOutputWithConfig(mergedConfig, disableRootPadding);
}
//# sourceMappingURL=use-global-styles-output.js.map