/**
 * WordPress dependencies
 */
import { getBlockSupport } from '@wordpress/blocks';
import { memo, useMemo, useEffect, useId, useState } from '@wordpress/element';
import { useDispatch, useRegistry } from '@wordpress/data';
import { createHigherOrderComponent } from '@wordpress/compose';
import { addFilter } from '@wordpress/hooks';

/**
 * Internal dependencies
 */
import { useBlockEditContext, mayDisplayControlsKey, mayDisplayParentControlsKey } from '../components/block-edit/context';
import { useSettings } from '../components';
import { useSettingsForBlockElement } from '../components/global-styles/hooks';
import { getValueFromObjectPath, setImmutably } from '../utils/object';
import { store as blockEditorStore } from '../store';
import { unlock } from '../lock-unlock';
/**
 * External dependencies
 */
import clsx from 'clsx';

/**
 * Removed falsy values from nested object.
 *
 * @param {*} object
 * @return {*} Object cleaned from falsy values
 */
import { jsx as _jsx } from "react/jsx-runtime";
export const cleanEmptyObject = object => {
  if (object === null || typeof object !== 'object' || Array.isArray(object)) {
    return object;
  }
  const cleanedNestedObjects = Object.entries(object).map(([key, value]) => [key, cleanEmptyObject(value)]).filter(([, value]) => value !== undefined);
  return !cleanedNestedObjects.length ? undefined : Object.fromEntries(cleanedNestedObjects);
};
export function transformStyles(activeSupports, migrationPaths, result, source, index, results) {
  // If there are no active supports return early.
  if (Object.values(activeSupports !== null && activeSupports !== void 0 ? activeSupports : {}).every(isActive => !isActive)) {
    return result;
  }
  // If the condition verifies we are probably in the presence of a wrapping transform
  // e.g: nesting paragraphs in a group or columns and in that case the styles should not be transformed.
  if (results.length === 1 && result.innerBlocks.length === source.length) {
    return result;
  }
  // For cases where we have a transform from one block to multiple blocks
  // or multiple blocks to one block we apply the styles of the first source block
  // to the result(s).
  let referenceBlockAttributes = source[0]?.attributes;
  // If we are in presence of transform between more than one block in the source
  // that has more than one block in the result
  // we apply the styles on source N to the result N,
  // if source N does not exists we do nothing.
  if (results.length > 1 && source.length > 1) {
    if (source[index]) {
      referenceBlockAttributes = source[index]?.attributes;
    } else {
      return result;
    }
  }
  let returnBlock = result;
  Object.entries(activeSupports).forEach(([support, isActive]) => {
    if (isActive) {
      migrationPaths[support].forEach(path => {
        const styleValue = getValueFromObjectPath(referenceBlockAttributes, path);
        if (styleValue) {
          returnBlock = {
            ...returnBlock,
            attributes: setImmutably(returnBlock.attributes, path, styleValue)
          };
        }
      });
    }
  });
  return returnBlock;
}

/**
 * Check whether serialization of specific block support feature or set should
 * be skipped.
 *
 * @param {string|Object} blockNameOrType Block name or block type object.
 * @param {string}        featureSet      Name of block support feature set.
 * @param {string}        feature         Name of the individual feature to check.
 *
 * @return {boolean} Whether serialization should occur.
 */
export function shouldSkipSerialization(blockNameOrType, featureSet, feature) {
  const support = getBlockSupport(blockNameOrType, featureSet);
  const skipSerialization = support?.skipSerialization;
  if (Array.isArray(skipSerialization)) {
    return skipSerialization.includes(feature);
  }
  return skipSerialization;
}
const pendingStyleOverrides = new WeakMap();

/**
 * Override a block editor settings style. Leave the ID blank to create a new
 * style.
 *
 * @param {Object}  override     Override object.
 * @param {?string} override.id  Id of the style override, leave blank to create
 *                               a new style.
 * @param {string}  override.css CSS to apply.
 */
export function useStyleOverride({
  id,
  css
}) {
  return usePrivateStyleOverride({
    id,
    css
  });
}
export function usePrivateStyleOverride({
  id,
  css,
  assets,
  __unstableType,
  variation,
  clientId
} = {}) {
  const {
    setStyleOverride,
    deleteStyleOverride
  } = unlock(useDispatch(blockEditorStore));
  const registry = useRegistry();
  const fallbackId = useId();
  useEffect(() => {
    // Unmount if there is CSS and assets are empty.
    if (!css && !assets) {
      return;
    }
    const _id = id || fallbackId;
    const override = {
      id,
      css,
      assets,
      __unstableType,
      variation,
      clientId
    };
    // Batch updates to style overrides to avoid triggering cascading renders
    // for each style override block included in a tree and optimize initial render.
    if (!pendingStyleOverrides.get(registry)) {
      pendingStyleOverrides.set(registry, []);
    }
    pendingStyleOverrides.get(registry).push([_id, override]);
    window.queueMicrotask(() => {
      if (pendingStyleOverrides.get(registry)?.length) {
        registry.batch(() => {
          pendingStyleOverrides.get(registry).forEach(args => {
            setStyleOverride(...args);
          });
          pendingStyleOverrides.set(registry, []);
        });
      }
    });
    return () => {
      const isPending = pendingStyleOverrides.get(registry)?.find(([currentId]) => currentId === _id);
      if (isPending) {
        pendingStyleOverrides.set(registry, pendingStyleOverrides.get(registry).filter(([currentId]) => currentId !== _id));
      } else {
        deleteStyleOverride(_id);
      }
    };
  }, [id, css, clientId, assets, __unstableType, fallbackId, setStyleOverride, deleteStyleOverride, registry]);
}

/**
 * Based on the block and its context, returns an object of all the block settings.
 * This object can be passed as a prop to all the Styles UI components
 * (TypographyPanel, DimensionsPanel...).
 *
 * @param {string} name         Block name.
 * @param {*}      parentLayout Parent layout.
 *
 * @return {Object} Settings object.
 */
export function useBlockSettings(name, parentLayout) {
  const [backgroundImage, backgroundSize, customFontFamilies, defaultFontFamilies, themeFontFamilies, defaultFontSizesEnabled, customFontSizes, defaultFontSizes, themeFontSizes, customFontSize, fontStyle, fontWeight, lineHeight, textAlign, textColumns, textDecoration, writingMode, textTransform, letterSpacing, padding, margin, blockGap, defaultSpacingSizesEnabled, customSpacingSize, userSpacingSizes, defaultSpacingSizes, themeSpacingSizes, units, aspectRatio, minHeight, layout, borderColor, borderRadius, borderStyle, borderWidth, customColorsEnabled, customColors, customDuotone, themeColors, defaultColors, defaultPalette, defaultDuotone, userDuotonePalette, themeDuotonePalette, defaultDuotonePalette, userGradientPalette, themeGradientPalette, defaultGradientPalette, defaultGradients, areCustomGradientsEnabled, isBackgroundEnabled, isLinkEnabled, isTextEnabled, isHeadingEnabled, isButtonEnabled, shadow] = useSettings('background.backgroundImage', 'background.backgroundSize', 'typography.fontFamilies.custom', 'typography.fontFamilies.default', 'typography.fontFamilies.theme', 'typography.defaultFontSizes', 'typography.fontSizes.custom', 'typography.fontSizes.default', 'typography.fontSizes.theme', 'typography.customFontSize', 'typography.fontStyle', 'typography.fontWeight', 'typography.lineHeight', 'typography.textAlign', 'typography.textColumns', 'typography.textDecoration', 'typography.writingMode', 'typography.textTransform', 'typography.letterSpacing', 'spacing.padding', 'spacing.margin', 'spacing.blockGap', 'spacing.defaultSpacingSizes', 'spacing.customSpacingSize', 'spacing.spacingSizes.custom', 'spacing.spacingSizes.default', 'spacing.spacingSizes.theme', 'spacing.units', 'dimensions.aspectRatio', 'dimensions.minHeight', 'layout', 'border.color', 'border.radius', 'border.style', 'border.width', 'color.custom', 'color.palette.custom', 'color.customDuotone', 'color.palette.theme', 'color.palette.default', 'color.defaultPalette', 'color.defaultDuotone', 'color.duotone.custom', 'color.duotone.theme', 'color.duotone.default', 'color.gradients.custom', 'color.gradients.theme', 'color.gradients.default', 'color.defaultGradients', 'color.customGradient', 'color.background', 'color.link', 'color.text', 'color.heading', 'color.button', 'shadow');
  const rawSettings = useMemo(() => {
    return {
      background: {
        backgroundImage,
        backgroundSize
      },
      color: {
        palette: {
          custom: customColors,
          theme: themeColors,
          default: defaultColors
        },
        gradients: {
          custom: userGradientPalette,
          theme: themeGradientPalette,
          default: defaultGradientPalette
        },
        duotone: {
          custom: userDuotonePalette,
          theme: themeDuotonePalette,
          default: defaultDuotonePalette
        },
        defaultGradients,
        defaultPalette,
        defaultDuotone,
        custom: customColorsEnabled,
        customGradient: areCustomGradientsEnabled,
        customDuotone,
        background: isBackgroundEnabled,
        link: isLinkEnabled,
        heading: isHeadingEnabled,
        button: isButtonEnabled,
        text: isTextEnabled
      },
      typography: {
        fontFamilies: {
          custom: customFontFamilies,
          default: defaultFontFamilies,
          theme: themeFontFamilies
        },
        fontSizes: {
          custom: customFontSizes,
          default: defaultFontSizes,
          theme: themeFontSizes
        },
        customFontSize,
        defaultFontSizes: defaultFontSizesEnabled,
        fontStyle,
        fontWeight,
        lineHeight,
        textAlign,
        textColumns,
        textDecoration,
        textTransform,
        letterSpacing,
        writingMode
      },
      spacing: {
        spacingSizes: {
          custom: userSpacingSizes,
          default: defaultSpacingSizes,
          theme: themeSpacingSizes
        },
        customSpacingSize,
        defaultSpacingSizes: defaultSpacingSizesEnabled,
        padding,
        margin,
        blockGap,
        units
      },
      border: {
        color: borderColor,
        radius: borderRadius,
        style: borderStyle,
        width: borderWidth
      },
      dimensions: {
        aspectRatio,
        minHeight
      },
      layout,
      parentLayout,
      shadow
    };
  }, [backgroundImage, backgroundSize, customFontFamilies, defaultFontFamilies, themeFontFamilies, defaultFontSizesEnabled, customFontSizes, defaultFontSizes, themeFontSizes, customFontSize, fontStyle, fontWeight, lineHeight, textAlign, textColumns, textDecoration, textTransform, letterSpacing, writingMode, padding, margin, blockGap, defaultSpacingSizesEnabled, customSpacingSize, userSpacingSizes, defaultSpacingSizes, themeSpacingSizes, units, aspectRatio, minHeight, layout, parentLayout, borderColor, borderRadius, borderStyle, borderWidth, customColorsEnabled, customColors, customDuotone, themeColors, defaultColors, defaultPalette, defaultDuotone, userDuotonePalette, themeDuotonePalette, defaultDuotonePalette, userGradientPalette, themeGradientPalette, defaultGradientPalette, defaultGradients, areCustomGradientsEnabled, isBackgroundEnabled, isLinkEnabled, isTextEnabled, isHeadingEnabled, isButtonEnabled, shadow]);
  return useSettingsForBlockElement(rawSettings, name);
}
export function createBlockEditFilter(features) {
  // We don't want block controls to re-render when typing inside a block.
  // `memo` will prevent re-renders unless props change, so only pass the
  // needed props and not the whole attributes object.
  features = features.map(settings => {
    return {
      ...settings,
      Edit: memo(settings.edit)
    };
  });
  const withBlockEditHooks = createHigherOrderComponent(OriginalBlockEdit => props => {
    const context = useBlockEditContext();
    // CAUTION: code added before this line will be executed for all
    // blocks, not just those that support the feature! Code added
    // above this line should be carefully evaluated for its impact on
    // performance.
    return [...features.map((feature, i) => {
      const {
        Edit,
        hasSupport,
        attributeKeys = [],
        shareWithChildBlocks
      } = feature;
      const shouldDisplayControls = context[mayDisplayControlsKey] || context[mayDisplayParentControlsKey] && shareWithChildBlocks;
      if (!shouldDisplayControls || !hasSupport(props.name)) {
        return null;
      }
      const neededProps = {};
      for (const key of attributeKeys) {
        if (props.attributes[key]) {
          neededProps[key] = props.attributes[key];
        }
      }
      return /*#__PURE__*/_jsx(Edit
      // We can use the index because the array length
      // is fixed per page load right now.
      , {
        name: props.name,
        isSelected: props.isSelected,
        clientId: props.clientId,
        setAttributes: props.setAttributes,
        __unstableParentLayout: props.__unstableParentLayout
        // This component is pure, so only pass needed
        // props!!!
        ,
        ...neededProps
      }, i);
    }), /*#__PURE__*/_jsx(OriginalBlockEdit, {
      ...props
    }, "edit")];
  }, 'withBlockEditHooks');
  addFilter('editor.BlockEdit', 'core/editor/hooks', withBlockEditHooks);
}
function BlockProps({
  index,
  useBlockProps: hook,
  setAllWrapperProps,
  ...props
}) {
  const wrapperProps = hook(props);
  const setWrapperProps = next => setAllWrapperProps(prev => {
    const nextAll = [...prev];
    nextAll[index] = next;
    return nextAll;
  });
  // Setting state after every render is fine because this component is
  // pure and will only re-render when needed props change.
  useEffect(() => {
    // We could shallow compare the props, but since this component only
    // changes when needed attributes change, the benefit is probably small.
    setWrapperProps(wrapperProps);
    return () => {
      setWrapperProps(undefined);
    };
  });
  return null;
}
const BlockPropsPure = memo(BlockProps);
export function createBlockListBlockFilter(features) {
  const withBlockListBlockHooks = createHigherOrderComponent(BlockListBlock => props => {
    const [allWrapperProps, setAllWrapperProps] = useState(Array(features.length).fill(undefined));
    return [...features.map((feature, i) => {
      const {
        hasSupport,
        attributeKeys = [],
        useBlockProps,
        isMatch
      } = feature;
      const neededProps = {};
      for (const key of attributeKeys) {
        if (props.attributes[key]) {
          neededProps[key] = props.attributes[key];
        }
      }
      if (
      // Skip rendering if none of the needed attributes are
      // set.
      !Object.keys(neededProps).length || !hasSupport(props.name) || isMatch && !isMatch(neededProps)) {
        return null;
      }
      return /*#__PURE__*/_jsx(BlockPropsPure
      // We can use the index because the array length
      // is fixed per page load right now.
      , {
        index: i,
        useBlockProps: useBlockProps
        // This component is pure, so we must pass a stable
        // function reference.
        ,
        setAllWrapperProps: setAllWrapperProps,
        name: props.name,
        clientId: props.clientId
        // This component is pure, so only pass needed
        // props!!!
        ,
        ...neededProps
      }, i);
    }), /*#__PURE__*/_jsx(BlockListBlock, {
      ...props,
      wrapperProps: allWrapperProps.filter(Boolean).reduce((acc, wrapperProps) => {
        return {
          ...acc,
          ...wrapperProps,
          className: clsx(acc.className, wrapperProps.className),
          style: {
            ...acc.style,
            ...wrapperProps.style
          }
        };
      }, props.wrapperProps || {})
    }, "edit")];
  }, 'withBlockListBlockHooks');
  addFilter('editor.BlockListBlock', 'core/editor/hooks', withBlockListBlockHooks);
}
export function createBlockSaveFilter(features) {
  function extraPropsFromHooks(props, name, attributes) {
    return features.reduce((accu, feature) => {
      const {
        hasSupport,
        attributeKeys = [],
        addSaveProps
      } = feature;
      const neededAttributes = {};
      for (const key of attributeKeys) {
        if (attributes[key]) {
          neededAttributes[key] = attributes[key];
        }
      }
      if (
      // Skip rendering if none of the needed attributes are
      // set.
      !Object.keys(neededAttributes).length || !hasSupport(name)) {
        return accu;
      }
      return addSaveProps(accu, name, neededAttributes);
    }, props);
  }
  addFilter('blocks.getSaveContent.extraProps', 'core/editor/hooks', extraPropsFromHooks, 0);
  addFilter('blocks.getSaveContent.extraProps', 'core/editor/hooks', props => {
    // Previously we had a filter deleting the className if it was an empty
    // string. That filter is no longer running, so now we need to delete it
    // here.
    if (props.hasOwnProperty('className') && !props.className) {
      delete props.className;
    }
    return props;
  });
}
//# sourceMappingURL=utils.js.map