/**
 * WordPress dependencies
 */
import { store as coreStore } from '@wordpress/core-data';
import { __, sprintf } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { store as preferencesStore } from '@wordpress/preferences';
import { addQueryArgs } from '@wordpress/url';
import apiFetch from '@wordpress/api-fetch';
import { parse, __unstableSerializeAndClean } from '@wordpress/blocks';
import { decodeEntities } from '@wordpress/html-entities';

/**
 * Internal dependencies
 */
import isTemplateRevertable from './utils/is-template-revertable';
export * from '../dataviews/store/private-actions';

/**
 * Returns an action object used to set which template is currently being used/edited.
 *
 * @param {string} id Template Id.
 *
 * @return {Object} Action object.
 */
export function setCurrentTemplateId(id) {
  return {
    type: 'SET_CURRENT_TEMPLATE_ID',
    id
  };
}

/**
 * Create a block based template.
 *
 * @param {Object?} template Template to create and assign.
 */
export const createTemplate = template => async ({
  select,
  dispatch,
  registry
}) => {
  const savedTemplate = await registry.dispatch(coreStore).saveEntityRecord('postType', 'wp_template', template);
  registry.dispatch(coreStore).editEntityRecord('postType', select.getCurrentPostType(), select.getCurrentPostId(), {
    template: savedTemplate.slug
  });
  registry.dispatch(noticesStore).createSuccessNotice(__("Custom template created. You're in template mode now."), {
    type: 'snackbar',
    actions: [{
      label: __('Go back'),
      onClick: () => dispatch.setRenderingMode(select.getEditorSettings().defaultRenderingMode)
    }]
  });
  return savedTemplate;
};

/**
 * Update the provided block types to be visible.
 *
 * @param {string[]} blockNames Names of block types to show.
 */
export const showBlockTypes = blockNames => ({
  registry
}) => {
  var _registry$select$get;
  const existingBlockNames = (_registry$select$get = registry.select(preferencesStore).get('core', 'hiddenBlockTypes')) !== null && _registry$select$get !== void 0 ? _registry$select$get : [];
  const newBlockNames = existingBlockNames.filter(type => !(Array.isArray(blockNames) ? blockNames : [blockNames]).includes(type));
  registry.dispatch(preferencesStore).set('core', 'hiddenBlockTypes', newBlockNames);
};

/**
 * Update the provided block types to be hidden.
 *
 * @param {string[]} blockNames Names of block types to hide.
 */
export const hideBlockTypes = blockNames => ({
  registry
}) => {
  var _registry$select$get2;
  const existingBlockNames = (_registry$select$get2 = registry.select(preferencesStore).get('core', 'hiddenBlockTypes')) !== null && _registry$select$get2 !== void 0 ? _registry$select$get2 : [];
  const mergedBlockNames = new Set([...existingBlockNames, ...(Array.isArray(blockNames) ? blockNames : [blockNames])]);
  registry.dispatch(preferencesStore).set('core', 'hiddenBlockTypes', [...mergedBlockNames]);
};

/**
 * Save entity records marked as dirty.
 *
 * @param {Object}   options                      Options for the action.
 * @param {Function} [options.onSave]             Callback when saving happens.
 * @param {object[]} [options.dirtyEntityRecords] Array of dirty entities.
 * @param {object[]} [options.entitiesToSkip]     Array of entities to skip saving.
 * @param {Function} [options.close]              Callback when the actions is called. It should be consolidated with `onSave`.
 */
export const saveDirtyEntities = ({
  onSave,
  dirtyEntityRecords = [],
  entitiesToSkip = [],
  close
} = {}) => ({
  registry
}) => {
  const PUBLISH_ON_SAVE_ENTITIES = [{
    kind: 'postType',
    name: 'wp_navigation'
  }];
  const saveNoticeId = 'site-editor-save-success';
  const homeUrl = registry.select(coreStore).getEntityRecord('root', '__unstableBase')?.home;
  registry.dispatch(noticesStore).removeNotice(saveNoticeId);
  const entitiesToSave = dirtyEntityRecords.filter(({
    kind,
    name,
    key,
    property
  }) => {
    return !entitiesToSkip.some(elt => elt.kind === kind && elt.name === name && elt.key === key && elt.property === property);
  });
  close?.(entitiesToSave);
  const siteItemsToSave = [];
  const pendingSavedRecords = [];
  entitiesToSave.forEach(({
    kind,
    name,
    key,
    property
  }) => {
    if ('root' === kind && 'site' === name) {
      siteItemsToSave.push(property);
    } else {
      if (PUBLISH_ON_SAVE_ENTITIES.some(typeToPublish => typeToPublish.kind === kind && typeToPublish.name === name)) {
        registry.dispatch(coreStore).editEntityRecord(kind, name, key, {
          status: 'publish'
        });
      }
      pendingSavedRecords.push(registry.dispatch(coreStore).saveEditedEntityRecord(kind, name, key));
    }
  });
  if (siteItemsToSave.length) {
    pendingSavedRecords.push(registry.dispatch(coreStore).__experimentalSaveSpecifiedEntityEdits('root', 'site', undefined, siteItemsToSave));
  }
  registry.dispatch(blockEditorStore).__unstableMarkLastChangeAsPersistent();
  Promise.all(pendingSavedRecords).then(values => {
    return onSave ? onSave(values) : values;
  }).then(values => {
    if (values.some(value => typeof value === 'undefined')) {
      registry.dispatch(noticesStore).createErrorNotice(__('Saving failed.'));
    } else {
      registry.dispatch(noticesStore).createSuccessNotice(__('Site updated.'), {
        type: 'snackbar',
        id: saveNoticeId,
        actions: [{
          label: __('View site'),
          url: homeUrl
        }]
      });
    }
  }).catch(error => registry.dispatch(noticesStore).createErrorNotice(`${__('Saving failed.')} ${error}`));
};

/**
 * Reverts a template to its original theme-provided file.
 *
 * @param {Object}  template            The template to revert.
 * @param {Object}  [options]
 * @param {boolean} [options.allowUndo] Whether to allow the user to undo
 *                                      reverting the template. Default true.
 */
export const revertTemplate = (template, {
  allowUndo = true
} = {}) => async ({
  registry
}) => {
  const noticeId = 'edit-site-template-reverted';
  registry.dispatch(noticesStore).removeNotice(noticeId);
  if (!isTemplateRevertable(template)) {
    registry.dispatch(noticesStore).createErrorNotice(__('This template is not revertable.'), {
      type: 'snackbar'
    });
    return;
  }
  try {
    const templateEntityConfig = registry.select(coreStore).getEntityConfig('postType', template.type);
    if (!templateEntityConfig) {
      registry.dispatch(noticesStore).createErrorNotice(__('The editor has encountered an unexpected error. Please reload.'), {
        type: 'snackbar'
      });
      return;
    }
    const fileTemplatePath = addQueryArgs(`${templateEntityConfig.baseURL}/${template.id}`, {
      context: 'edit',
      source: template.origin
    });
    const fileTemplate = await apiFetch({
      path: fileTemplatePath
    });
    if (!fileTemplate) {
      registry.dispatch(noticesStore).createErrorNotice(__('The editor has encountered an unexpected error. Please reload.'), {
        type: 'snackbar'
      });
      return;
    }
    const serializeBlocks = ({
      blocks: blocksForSerialization = []
    }) => __unstableSerializeAndClean(blocksForSerialization);
    const edited = registry.select(coreStore).getEditedEntityRecord('postType', template.type, template.id);

    // We are fixing up the undo level here to make sure we can undo
    // the revert in the header toolbar correctly.
    registry.dispatch(coreStore).editEntityRecord('postType', template.type, template.id, {
      content: serializeBlocks,
      // Required to make the `undo` behave correctly.
      blocks: edited.blocks,
      // Required to revert the blocks in the editor.
      source: 'custom' // required to avoid turning the editor into a dirty state
    }, {
      undoIgnore: true // Required to merge this edit with the last undo level.
    });
    const blocks = parse(fileTemplate?.content?.raw);
    registry.dispatch(coreStore).editEntityRecord('postType', template.type, fileTemplate.id, {
      content: serializeBlocks,
      blocks,
      source: 'theme'
    });
    if (allowUndo) {
      const undoRevert = () => {
        registry.dispatch(coreStore).editEntityRecord('postType', template.type, edited.id, {
          content: serializeBlocks,
          blocks: edited.blocks,
          source: 'custom'
        });
      };
      registry.dispatch(noticesStore).createSuccessNotice(__('Template reset.'), {
        type: 'snackbar',
        id: noticeId,
        actions: [{
          label: __('Undo'),
          onClick: undoRevert
        }]
      });
    }
  } catch (error) {
    const errorMessage = error.message && error.code !== 'unknown_error' ? error.message : __('Template revert failed. Please reload.');
    registry.dispatch(noticesStore).createErrorNotice(errorMessage, {
      type: 'snackbar'
    });
  }
};

/**
 * Action that removes an array of templates, template parts or patterns.
 *
 * @param {Array} items An array of template,template part or pattern objects to remove.
 */
export const removeTemplates = items => async ({
  registry
}) => {
  const isResetting = items.every(item => item?.has_theme_file);
  const promiseResult = await Promise.allSettled(items.map(item => {
    return registry.dispatch(coreStore).deleteEntityRecord('postType', item.type, item.id, {
      force: true
    }, {
      throwOnError: true
    });
  }));

  // If all the promises were fulfilled with sucess.
  if (promiseResult.every(({
    status
  }) => status === 'fulfilled')) {
    let successMessage;
    if (items.length === 1) {
      // Depending on how the entity was retrieved its title might be
      // an object or simple string.
      let title;
      if (typeof items[0].title === 'string') {
        title = items[0].title;
      } else if (typeof items[0].title?.rendered === 'string') {
        title = items[0].title?.rendered;
      } else if (typeof items[0].title?.raw === 'string') {
        title = items[0].title?.raw;
      }
      successMessage = isResetting ? sprintf( /* translators: The template/part's name. */
      __('"%s" reset.'), decodeEntities(title)) : sprintf( /* translators: The template/part's name. */
      __('"%s" deleted.'), decodeEntities(title));
    } else {
      successMessage = isResetting ? __('Items reset.') : __('Items deleted.');
    }
    registry.dispatch(noticesStore).createSuccessNotice(successMessage, {
      type: 'snackbar',
      id: 'editor-template-deleted-success'
    });
  } else {
    // If there was at lease one failure.
    let errorMessage;
    // If we were trying to delete a single template.
    if (promiseResult.length === 1) {
      if (promiseResult[0].reason?.message) {
        errorMessage = promiseResult[0].reason.message;
      } else {
        errorMessage = isResetting ? __('An error occurred while reverting the item.') : __('An error occurred while deleting the item.');
      }
      // If we were trying to delete a multiple templates
    } else {
      const errorMessages = new Set();
      const failedPromises = promiseResult.filter(({
        status
      }) => status === 'rejected');
      for (const failedPromise of failedPromises) {
        if (failedPromise.reason?.message) {
          errorMessages.add(failedPromise.reason.message);
        }
      }
      if (errorMessages.size === 0) {
        errorMessage = __('An error occurred while deleting the items.');
      } else if (errorMessages.size === 1) {
        errorMessage = isResetting ? sprintf( /* translators: %s: an error message */
        __('An error occurred while reverting the items: %s'), [...errorMessages][0]) : sprintf( /* translators: %s: an error message */
        __('An error occurred while deleting the items: %s'), [...errorMessages][0]);
      } else {
        errorMessage = isResetting ? sprintf( /* translators: %s: a list of comma separated error messages */
        __('Some errors occurred while reverting the items: %s'), [...errorMessages].join(',')) : sprintf( /* translators: %s: a list of comma separated error messages */
        __('Some errors occurred while deleting the items: %s'), [...errorMessages].join(','));
      }
    }
    registry.dispatch(noticesStore).createErrorNotice(errorMessage, {
      type: 'snackbar'
    });
  }
};
//# sourceMappingURL=private-actions.js.map