"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.getAllPatterns = void 0;
exports.getBlockRemovalRules = getBlockRemovalRules;
Object.defineProperty(exports, "getBlockSettings", {
  enumerable: true,
  get: function () {
    return _getBlockSettings.getBlockSettings;
  }
});
exports.getBlockStyles = void 0;
exports.getBlockWithoutAttributes = getBlockWithoutAttributes;
exports.getClosestAllowedInsertionPoint = getClosestAllowedInsertionPoint;
exports.getClosestAllowedInsertionPointForPattern = getClosestAllowedInsertionPointForPattern;
exports.getEnabledClientIdsTree = exports.getEnabledBlockParents = exports.getContentLockingParent = void 0;
exports.getExpandedBlock = getExpandedBlock;
exports.getInserterMediaCategories = void 0;
exports.getInsertionPoint = getInsertionPoint;
exports.getLastFocus = getLastFocus;
exports.getLastInsertedBlocksClientIds = getLastInsertedBlocksClientIds;
exports.getOpenedBlockSettingsMenu = getOpenedBlockSettingsMenu;
exports.getPatternBySlug = exports.getParentSectionBlock = void 0;
exports.getRegisteredInserterMediaCategories = getRegisteredInserterMediaCategories;
exports.getRemovalPromptData = getRemovalPromptData;
exports.getReusableBlocks = void 0;
exports.getSectionRootClientId = getSectionRootClientId;
exports.getStyleOverrides = void 0;
exports.getTemporarilyEditingAsBlocks = getTemporarilyEditingAsBlocks;
exports.getTemporarilyEditingFocusModeToRevert = getTemporarilyEditingFocusModeToRevert;
exports.getZoomLevel = getZoomLevel;
exports.hasAllowedPatterns = void 0;
exports.isBlockInterfaceHidden = isBlockInterfaceHidden;
exports.isBlockSubtreeDisabled = void 0;
exports.isDragging = isDragging;
exports.isResolvingPatterns = void 0;
exports.isSectionBlock = isSectionBlock;
exports.isZoomOut = isZoomOut;
var _data = require("@wordpress/data");
var _selectors = require("./selectors");
var _utils = require("./utils");
var _utils2 = require("../components/inserter/block-patterns-tab/utils");
var _constants = require("./constants");
var _lockUnlock = require("../lock-unlock");
var _privateKeys = require("./private-keys");
var _getBlockSettings = require("./get-block-settings");
/**
 * WordPress dependencies
 */

/**
 * Internal dependencies
 */

/**
 * Returns true if the block interface is hidden, or false otherwise.
 *
 * @param {Object} state Global application state.
 *
 * @return {boolean} Whether the block toolbar is hidden.
 */
function isBlockInterfaceHidden(state) {
  return state.isBlockInterfaceHidden;
}

/**
 * Gets the client ids of the last inserted blocks.
 *
 * @param {Object} state Global application state.
 * @return {Array|undefined} Client Ids of the last inserted block(s).
 */
function getLastInsertedBlocksClientIds(state) {
  return state?.lastBlockInserted?.clientIds;
}
function getBlockWithoutAttributes(state, clientId) {
  return state.blocks.byClientId.get(clientId);
}

/**
 * Returns true if all of the descendants of a block with the given client ID
 * have an editing mode of 'disabled', or false otherwise.
 *
 * @param {Object} state    Global application state.
 * @param {string} clientId The block client ID.
 *
 * @return {boolean} Whether the block descendants are disabled.
 */
const isBlockSubtreeDisabled = (state, clientId) => {
  const isChildSubtreeDisabled = childClientId => {
    return (0, _selectors.getBlockEditingMode)(state, childClientId) === 'disabled' && (0, _selectors.getBlockOrder)(state, childClientId).every(isChildSubtreeDisabled);
  };
  return (0, _selectors.getBlockOrder)(state, clientId).every(isChildSubtreeDisabled);
};
exports.isBlockSubtreeDisabled = isBlockSubtreeDisabled;
function getEnabledClientIdsTreeUnmemoized(state, rootClientId) {
  const blockOrder = (0, _selectors.getBlockOrder)(state, rootClientId);
  const result = [];
  for (const clientId of blockOrder) {
    const innerBlocks = getEnabledClientIdsTreeUnmemoized(state, clientId);
    if ((0, _selectors.getBlockEditingMode)(state, clientId) !== 'disabled') {
      result.push({
        clientId,
        innerBlocks
      });
    } else {
      result.push(...innerBlocks);
    }
  }
  return result;
}

/**
 * Returns a tree of block objects with only clientID and innerBlocks set.
 * Blocks with a 'disabled' editing mode are not included.
 *
 * @param {Object}  state        Global application state.
 * @param {?string} rootClientId Optional root client ID of block list.
 *
 * @return {Object[]} Tree of block objects with only clientID and innerBlocks set.
 */
const getEnabledClientIdsTree = exports.getEnabledClientIdsTree = (0, _data.createRegistrySelector)(select => (0, _data.createSelector)(getEnabledClientIdsTreeUnmemoized, state => [state.blocks.order, state.blockEditingModes, state.settings.templateLock, state.blockListSettings, select(_constants.STORE_NAME).__unstableGetEditorMode(state), state.zoomLevel, getSectionRootClientId(state)]));

/**
 * Returns a list of a given block's ancestors, from top to bottom. Blocks with
 * a 'disabled' editing mode are excluded.
 *
 * @see getBlockParents
 *
 * @param {Object}  state     Global application state.
 * @param {string}  clientId  The block client ID.
 * @param {boolean} ascending Order results from bottom to top (true) or top
 *                            to bottom (false).
 */
const getEnabledBlockParents = exports.getEnabledBlockParents = (0, _data.createSelector)((state, clientId, ascending = false) => {
  return (0, _selectors.getBlockParents)(state, clientId, ascending).filter(parent => (0, _selectors.getBlockEditingMode)(state, parent) !== 'disabled');
}, state => [state.blocks.parents, state.blockEditingModes, state.settings.templateLock, state.blockListSettings]);

/**
 * Selector that returns the data needed to display a prompt when certain
 * blocks are removed, or `false` if no such prompt is requested.
 *
 * @param {Object} state Global application state.
 *
 * @return {Object|false} Data for removal prompt display, if any.
 */
function getRemovalPromptData(state) {
  return state.removalPromptData;
}

/**
 * Returns true if removal prompt exists, or false otherwise.
 *
 * @param {Object} state Global application state.
 *
 * @return {boolean} Whether removal prompt exists.
 */
function getBlockRemovalRules(state) {
  return state.blockRemovalRules;
}

/**
 * Returns the client ID of the block settings menu that is currently open.
 *
 * @param {Object} state Global application state.
 * @return {string|null} The client ID of the block menu that is currently open.
 */
function getOpenedBlockSettingsMenu(state) {
  return state.openedBlockSettingsMenu;
}

/**
 * Returns all style overrides, intended to be merged with global editor styles.
 *
 * Overrides are sorted to match the order of the blocks they relate to. This
 * is useful to maintain correct CSS cascade order.
 *
 * @param {Object} state Global application state.
 *
 * @return {Array} An array of style ID to style override pairs.
 */
const getStyleOverrides = exports.getStyleOverrides = (0, _data.createSelector)(state => {
  const clientIds = (0, _selectors.getClientIdsWithDescendants)(state);
  const clientIdMap = clientIds.reduce((acc, clientId, index) => {
    acc[clientId] = index;
    return acc;
  }, {});
  return [...state.styleOverrides].sort((overrideA, overrideB) => {
    var _clientIdMap$clientId, _clientIdMap$clientId2;
    // Once the overrides Map is spread to an array, the first element
    // is the key, while the second is the override itself including
    // the clientId to sort by.
    const [, {
      clientId: clientIdA
    }] = overrideA;
    const [, {
      clientId: clientIdB
    }] = overrideB;
    const aIndex = (_clientIdMap$clientId = clientIdMap[clientIdA]) !== null && _clientIdMap$clientId !== void 0 ? _clientIdMap$clientId : -1;
    const bIndex = (_clientIdMap$clientId2 = clientIdMap[clientIdB]) !== null && _clientIdMap$clientId2 !== void 0 ? _clientIdMap$clientId2 : -1;
    return aIndex - bIndex;
  });
}, state => [state.blocks.order, state.styleOverrides]);

/** @typedef {import('./actions').InserterMediaCategory} InserterMediaCategory */
/**
 * Returns the registered inserter media categories through the public API.
 *
 * @param {Object} state Editor state.
 *
 * @return {InserterMediaCategory[]} Inserter media categories.
 */
function getRegisteredInserterMediaCategories(state) {
  return state.registeredInserterMediaCategories;
}

/**
 * Returns an array containing the allowed inserter media categories.
 * It merges the registered media categories from extenders with the
 * core ones. It also takes into account the allowed `mime_types`, which
 * can be altered by `upload_mimes` filter and restrict some of them.
 *
 * @param {Object} state Global application state.
 *
 * @return {InserterMediaCategory[]} Client IDs of descendants.
 */
const getInserterMediaCategories = exports.getInserterMediaCategories = (0, _data.createSelector)(state => {
  const {
    settings: {
      inserterMediaCategories,
      allowedMimeTypes,
      enableOpenverseMediaCategory
    },
    registeredInserterMediaCategories
  } = state;
  // The allowed `mime_types` can be altered by `upload_mimes` filter and restrict
  // some of them. In this case we shouldn't add the category to the available media
  // categories list in the inserter.
  if (!inserterMediaCategories && !registeredInserterMediaCategories.length || !allowedMimeTypes) {
    return;
  }
  const coreInserterMediaCategoriesNames = inserterMediaCategories?.map(({
    name
  }) => name) || [];
  const mergedCategories = [...(inserterMediaCategories || []), ...(registeredInserterMediaCategories || []).filter(({
    name
  }) => !coreInserterMediaCategoriesNames.includes(name))];
  return mergedCategories.filter(category => {
    // Check if Openverse category is enabled.
    if (!enableOpenverseMediaCategory && category.name === 'openverse') {
      return false;
    }
    return Object.values(allowedMimeTypes).some(mimeType => mimeType.startsWith(`${category.mediaType}/`));
  });
}, state => [state.settings.inserterMediaCategories, state.settings.allowedMimeTypes, state.settings.enableOpenverseMediaCategory, state.registeredInserterMediaCategories]);

/**
 * Returns whether there is at least one allowed pattern for inner blocks children.
 * This is useful for deferring the parsing of all patterns until needed.
 *
 * @param {Object} state               Editor state.
 * @param {string} [rootClientId=null] Target root client ID.
 *
 * @return {boolean} If there is at least one allowed pattern.
 */
const hasAllowedPatterns = exports.hasAllowedPatterns = (0, _data.createRegistrySelector)(select => (0, _data.createSelector)((state, rootClientId = null) => {
  const {
    getAllPatterns
  } = (0, _lockUnlock.unlock)(select(_constants.STORE_NAME));
  const patterns = getAllPatterns();
  const {
    allowedBlockTypes
  } = (0, _selectors.getSettings)(state);
  return patterns.some(pattern => {
    const {
      inserter = true
    } = pattern;
    if (!inserter) {
      return false;
    }
    const grammar = (0, _utils.getGrammar)(pattern);
    return (0, _utils.checkAllowListRecursive)(grammar, allowedBlockTypes) && grammar.every(({
      name: blockName
    }) => (0, _selectors.canInsertBlockType)(state, blockName, rootClientId));
  });
}, (state, rootClientId) => [...(0, _utils.getAllPatternsDependants)(select)(state), ...(0, _utils.getInsertBlockTypeDependants)(select)(state, rootClientId)]));
function mapUserPattern(userPattern, __experimentalUserPatternCategories = []) {
  return {
    name: `core/block/${userPattern.id}`,
    id: userPattern.id,
    type: _utils2.INSERTER_PATTERN_TYPES.user,
    title: userPattern.title.raw,
    categories: userPattern.wp_pattern_category?.map(catId => {
      const category = __experimentalUserPatternCategories.find(({
        id
      }) => id === catId);
      return category ? category.slug : catId;
    }),
    content: userPattern.content.raw,
    syncStatus: userPattern.wp_pattern_sync_status
  };
}
const getPatternBySlug = exports.getPatternBySlug = (0, _data.createRegistrySelector)(select => (0, _data.createSelector)((state, patternName) => {
  var _state$settings$__exp, _state$settings$selec;
  // Only fetch reusable blocks if we know we need them. To do: maybe
  // use the entity record API to retrieve the block by slug.
  if (patternName?.startsWith('core/block/')) {
    const _id = parseInt(patternName.slice('core/block/'.length), 10);
    const block = (0, _lockUnlock.unlock)(select(_constants.STORE_NAME)).getReusableBlocks().find(({
      id
    }) => id === _id);
    if (!block) {
      return null;
    }
    return mapUserPattern(block, state.settings.__experimentalUserPatternCategories);
  }
  return [
  // This setting is left for back compat.
  ...((_state$settings$__exp = state.settings.__experimentalBlockPatterns) !== null && _state$settings$__exp !== void 0 ? _state$settings$__exp : []), ...((_state$settings$selec = state.settings[_privateKeys.selectBlockPatternsKey]?.(select)) !== null && _state$settings$selec !== void 0 ? _state$settings$selec : [])].find(({
    name
  }) => name === patternName);
}, (state, patternName) => patternName?.startsWith('core/block/') ? [(0, _lockUnlock.unlock)(select(_constants.STORE_NAME)).getReusableBlocks(), state.settings.__experimentalReusableBlocks] : [state.settings.__experimentalBlockPatterns, state.settings[_privateKeys.selectBlockPatternsKey]?.(select)]));
const getAllPatterns = exports.getAllPatterns = (0, _data.createRegistrySelector)(select => (0, _data.createSelector)(state => {
  var _state$settings$__exp2, _state$settings$selec2;
  return [...(0, _lockUnlock.unlock)(select(_constants.STORE_NAME)).getReusableBlocks().map(userPattern => mapUserPattern(userPattern, state.settings.__experimentalUserPatternCategories)),
  // This setting is left for back compat.
  ...((_state$settings$__exp2 = state.settings.__experimentalBlockPatterns) !== null && _state$settings$__exp2 !== void 0 ? _state$settings$__exp2 : []), ...((_state$settings$selec2 = state.settings[_privateKeys.selectBlockPatternsKey]?.(select)) !== null && _state$settings$selec2 !== void 0 ? _state$settings$selec2 : [])].filter((x, index, arr) => index === arr.findIndex(y => x.name === y.name));
}, (0, _utils.getAllPatternsDependants)(select)));
const isResolvingPatterns = exports.isResolvingPatterns = (0, _data.createRegistrySelector)(select => (0, _data.createSelector)(state => {
  const blockPatternsSelect = state.settings[_privateKeys.selectBlockPatternsKey];
  const reusableBlocksSelect = state.settings[_privateKeys.reusableBlocksSelectKey];
  return (blockPatternsSelect ? blockPatternsSelect(select) === undefined : false) || (reusableBlocksSelect ? reusableBlocksSelect(select) === undefined : false);
}, (0, _utils.getAllPatternsDependants)(select)));
const EMPTY_ARRAY = [];
const getReusableBlocks = exports.getReusableBlocks = (0, _data.createRegistrySelector)(select => state => {
  var _ref;
  const reusableBlocksSelect = state.settings[_privateKeys.reusableBlocksSelectKey];
  return (_ref = reusableBlocksSelect ? reusableBlocksSelect(select) : state.settings.__experimentalReusableBlocks) !== null && _ref !== void 0 ? _ref : EMPTY_ARRAY;
});

/**
 * Returns the element of the last element that had focus when focus left the editor canvas.
 *
 * @param {Object} state Block editor state.
 *
 * @return {Object} Element.
 */
function getLastFocus(state) {
  return state.lastFocus;
}

/**
 * Returns true if the user is dragging anything, or false otherwise. It is possible for a
 * user to be dragging data from outside of the editor, so this selector is separate from
 * the `isDraggingBlocks` selector which only returns true if the user is dragging blocks.
 *
 * @param {Object} state Global application state.
 *
 * @return {boolean} Whether user is dragging.
 */
function isDragging(state) {
  return state.isDragging;
}

/**
 * Retrieves the expanded block from the state.
 *
 * @param {Object} state Block editor state.
 *
 * @return {string|null} The client ID of the expanded block, if set.
 */
function getExpandedBlock(state) {
  return state.expandedBlock;
}

/**
 * Retrieves the client ID of the ancestor block that is content locking the block
 * with the provided client ID.
 *
 * @param {Object} state    Global application state.
 * @param {string} clientId Client Id of the block.
 *
 * @return {?string} Client ID of the ancestor block that is content locking the block.
 */
const getContentLockingParent = (state, clientId) => {
  let current = clientId;
  let result;
  while (!result && (current = state.blocks.parents.get(current))) {
    if ((0, _selectors.getTemplateLock)(state, current) === 'contentOnly') {
      result = current;
    }
  }
  return result;
};

/**
 * Retrieves the client ID of the parent section block.
 *
 * @param {Object} state    Global application state.
 * @param {string} clientId Client Id of the block.
 *
 * @return {?string} Client ID of the ancestor block that is content locking the block.
 */
exports.getContentLockingParent = getContentLockingParent;
const getParentSectionBlock = (state, clientId) => {
  let current = clientId;
  let result;
  while (!result && (current = state.blocks.parents.get(current))) {
    if (isSectionBlock(state, current)) {
      result = current;
    }
  }
  return result;
};

/**
 * Retrieves the client ID is a content locking parent
 *
 * @param {Object} state    Global application state.
 * @param {string} clientId Client Id of the block.
 *
 * @return {boolean} Whether the block is a content locking parent.
 */
exports.getParentSectionBlock = getParentSectionBlock;
function isSectionBlock(state, clientId) {
  const sectionRootClientId = getSectionRootClientId(state);
  const sectionClientIds = (0, _selectors.getBlockOrder)(state, sectionRootClientId);
  return (0, _selectors.getBlockName)(state, clientId) === 'core/block' || (0, _selectors.getTemplateLock)(state, clientId) === 'contentOnly' || (0, _selectors.isNavigationMode)(state) && sectionClientIds.includes(clientId);
}

/**
 * Retrieves the client ID of the block that is content locked but is
 * currently being temporarily edited as a non-locked block.
 *
 * @param {Object} state Global application state.
 *
 * @return {?string} The client ID of the block being temporarily edited as a non-locked block.
 */
function getTemporarilyEditingAsBlocks(state) {
  return state.temporarilyEditingAsBlocks;
}

/**
 * Returns the focus mode that should be reapplied when the user stops editing
 * a content locked blocks as a block without locking.
 *
 * @param {Object} state Global application state.
 *
 * @return {?string} The focus mode that should be re-set when temporarily editing as blocks stops.
 */
function getTemporarilyEditingFocusModeToRevert(state) {
  return state.temporarilyEditingFocusModeRevert;
}

/**
 * Returns the style attributes of multiple blocks.
 *
 * @param {Object}   state     Global application state.
 * @param {string[]} clientIds An array of block client IDs.
 *
 * @return {Object} An object where keys are client IDs and values are the corresponding block styles or undefined.
 */
const getBlockStyles = exports.getBlockStyles = (0, _data.createSelector)((state, clientIds) => clientIds.reduce((styles, clientId) => {
  styles[clientId] = state.blocks.attributes.get(clientId)?.style;
  return styles;
}, {}), (state, clientIds) => [...clientIds.map(clientId => state.blocks.attributes.get(clientId)?.style)]);

/**
 * Retrieves the client ID of the block which contains the blocks
 * acting as "sections" in the editor. This is typically the "main content"
 * of the template/post.
 *
 * @param {Object} state Editor state.
 *
 * @return {string|undefined} The section root client ID or undefined if not set.
 */
function getSectionRootClientId(state) {
  return state.settings?.[_privateKeys.sectionRootClientIdKey];
}

/**
 * Returns whether the editor is considered zoomed out.
 *
 * @param {Object} state Global application state.
 * @return {boolean} Whether the editor is zoomed.
 */
function isZoomOut(state) {
  return state.zoomLevel === 'auto-scaled' || state.zoomLevel < 100;
}

/**
 * Returns whether the zoom level.
 *
 * @param {Object} state Global application state.
 * @return {number|"auto-scaled"} Zoom level.
 */
function getZoomLevel(state) {
  return state.zoomLevel;
}

/**
 * Finds the closest block where the block is allowed to be inserted.
 *
 * @param {Object}            state    Editor state.
 * @param {string[] | string} name     Block name or names.
 * @param {string}            clientId Default insertion point.
 *
 * @return {string} clientID of the closest container when the block name can be inserted.
 */
function getClosestAllowedInsertionPoint(state, name, clientId = '') {
  const blockNames = Array.isArray(name) ? name : [name];
  const areBlockNamesAllowedInClientId = id => blockNames.every(currentName => (0, _selectors.canInsertBlockType)(state, currentName, id));

  // If we're trying to insert at the root level and it's not allowed
  // Try the section root instead.
  if (!clientId) {
    if (areBlockNamesAllowedInClientId(clientId)) {
      return clientId;
    }
    const sectionRootClientId = getSectionRootClientId(state);
    if (sectionRootClientId && areBlockNamesAllowedInClientId(sectionRootClientId)) {
      return sectionRootClientId;
    }
    return null;
  }

  // Traverse the block tree up until we find a place where we can insert.
  let current = clientId;
  while (current !== null && !areBlockNamesAllowedInClientId(current)) {
    const parentClientId = (0, _selectors.getBlockRootClientId)(state, current);
    current = parentClientId;
  }
  return current;
}
function getClosestAllowedInsertionPointForPattern(state, pattern, clientId) {
  const {
    allowedBlockTypes
  } = (0, _selectors.getSettings)(state);
  const isAllowed = (0, _utils.checkAllowListRecursive)((0, _utils.getGrammar)(pattern), allowedBlockTypes);
  if (!isAllowed) {
    return null;
  }
  const names = (0, _utils.getGrammar)(pattern).map(({
    blockName: name
  }) => name);
  return getClosestAllowedInsertionPoint(state, names, clientId);
}

/**
 * Where the point where the next block will be inserted into.
 *
 * @param {Object} state
 * @return {Object} where the insertion point in the block editor is or null if none is set.
 */
function getInsertionPoint(state) {
  return state.insertionPoint;
}
//# sourceMappingURL=private-selectors.js.map