/**
 * WordPress dependencies
 */
import apiFetch from '@wordpress/api-fetch';
import { store as preferencesStore } from '@wordpress/preferences';
import {
	store as editorStore,
	privateApis as editorPrivateApis,
} from '@wordpress/editor';
import deprecated from '@wordpress/deprecated';
import { addAction } from '@wordpress/hooks';
import { store as coreStore } from '@wordpress/core-data';
import { store as noticesStore } from '@wordpress/notices';
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import { getMetaBoxContainer } from '../utils/meta-boxes';
import { unlock } from '../lock-unlock';

const { interfaceStore } = unlock( editorPrivateApis );

/**
 * Returns an action object used in signalling that the user opened an editor sidebar.
 *
 * @param {?string} name Sidebar name to be opened.
 */
export const openGeneralSidebar =
	( name ) =>
	( { registry } ) => {
		registry
			.dispatch( interfaceStore )
			.enableComplementaryArea( 'core', name );
	};

/**
 * Returns an action object signalling that the user closed the sidebar.
 */
export const closeGeneralSidebar =
	() =>
	( { registry } ) =>
		registry.dispatch( interfaceStore ).disableComplementaryArea( 'core' );

/**
 * Returns an action object used in signalling that the user opened a modal.
 *
 * @deprecated since WP 6.3 use `core/interface` store's action with the same name instead.
 *
 *
 * @param {string} name A string that uniquely identifies the modal.
 *
 * @return {Object} Action object.
 */
export const openModal =
	( name ) =>
	( { registry } ) => {
		deprecated( "select( 'core/edit-post' ).openModal( name )", {
			since: '6.3',
			alternative: "select( 'core/interface').openModal( name )",
		} );
		return registry.dispatch( interfaceStore ).openModal( name );
	};

/**
 * Returns an action object signalling that the user closed a modal.
 *
 * @deprecated since WP 6.3 use `core/interface` store's action with the same name instead.
 *
 * @return {Object} Action object.
 */
export const closeModal =
	() =>
	( { registry } ) => {
		deprecated( "select( 'core/edit-post' ).closeModal()", {
			since: '6.3',
			alternative: "select( 'core/interface').closeModal()",
		} );
		return registry.dispatch( interfaceStore ).closeModal();
	};

/**
 * Returns an action object used in signalling that the user opened the publish
 * sidebar.
 * @deprecated
 *
 * @return {Object} Action object
 */
export const openPublishSidebar =
	() =>
	( { registry } ) => {
		deprecated( "dispatch( 'core/edit-post' ).openPublishSidebar", {
			since: '6.6',
			alternative: "dispatch( 'core/editor').openPublishSidebar",
		} );
		registry.dispatch( editorStore ).openPublishSidebar();
	};

/**
 * Returns an action object used in signalling that the user closed the
 * publish sidebar.
 * @deprecated
 *
 * @return {Object} Action object.
 */
export const closePublishSidebar =
	() =>
	( { registry } ) => {
		deprecated( "dispatch( 'core/edit-post' ).closePublishSidebar", {
			since: '6.6',
			alternative: "dispatch( 'core/editor').closePublishSidebar",
		} );
		registry.dispatch( editorStore ).closePublishSidebar();
	};

/**
 * Returns an action object used in signalling that the user toggles the publish sidebar.
 * @deprecated
 *
 * @return {Object} Action object
 */
export const togglePublishSidebar =
	() =>
	( { registry } ) => {
		deprecated( "dispatch( 'core/edit-post' ).togglePublishSidebar", {
			since: '6.6',
			alternative: "dispatch( 'core/editor').togglePublishSidebar",
		} );
		registry.dispatch( editorStore ).togglePublishSidebar();
	};

/**
 * Returns an action object used to enable or disable a panel in the editor.
 *
 * @deprecated
 *
 * @param {string} panelName A string that identifies the panel to enable or disable.
 *
 * @return {Object} Action object.
 */
export const toggleEditorPanelEnabled =
	( panelName ) =>
	( { registry } ) => {
		deprecated( "dispatch( 'core/edit-post' ).toggleEditorPanelEnabled", {
			since: '6.5',
			alternative: "dispatch( 'core/editor').toggleEditorPanelEnabled",
		} );
		registry.dispatch( editorStore ).toggleEditorPanelEnabled( panelName );
	};

/**
 * Opens a closed panel and closes an open panel.
 *
 * @deprecated
 *
 * @param {string} panelName A string that identifies the panel to open or close.
 */
export const toggleEditorPanelOpened =
	( panelName ) =>
	( { registry } ) => {
		deprecated( "dispatch( 'core/edit-post' ).toggleEditorPanelOpened", {
			since: '6.5',
			alternative: "dispatch( 'core/editor').toggleEditorPanelOpened",
		} );
		registry.dispatch( editorStore ).toggleEditorPanelOpened( panelName );
	};

/**
 * Returns an action object used to remove a panel from the editor.
 *
 * @deprecated
 *
 * @param {string} panelName A string that identifies the panel to remove.
 *
 * @return {Object} Action object.
 */
export const removeEditorPanel =
	( panelName ) =>
	( { registry } ) => {
		deprecated( "dispatch( 'core/edit-post' ).removeEditorPanel", {
			since: '6.5',
			alternative: "dispatch( 'core/editor').removeEditorPanel",
		} );
		registry.dispatch( editorStore ).removeEditorPanel( panelName );
	};

/**
 * Triggers an action used to toggle a feature flag.
 *
 * @param {string} feature Feature name.
 */
export const toggleFeature =
	( feature ) =>
	( { registry } ) =>
		registry
			.dispatch( preferencesStore )
			.toggle( 'core/edit-post', feature );

/**
 * Triggers an action used to switch editor mode.
 *
 * @deprecated
 *
 * @param {string} mode The editor mode.
 */
export const switchEditorMode =
	( mode ) =>
	( { registry } ) => {
		deprecated( "dispatch( 'core/edit-post' ).switchEditorMode", {
			since: '6.6',
			alternative: "dispatch( 'core/editor').switchEditorMode",
		} );
		registry.dispatch( editorStore ).switchEditorMode( mode );
	};

/**
 * Triggers an action object used to toggle a plugin name flag.
 *
 * @param {string} pluginName Plugin name.
 */
export const togglePinnedPluginItem =
	( pluginName ) =>
	( { registry } ) => {
		const isPinned = registry
			.select( interfaceStore )
			.isItemPinned( 'core', pluginName );

		registry
			.dispatch( interfaceStore )
			[ isPinned ? 'unpinItem' : 'pinItem' ]( 'core', pluginName );
	};

/**
 * Returns an action object used in signaling that a style should be auto-applied when a block is created.
 *
 * @deprecated
 */
export function updatePreferredStyleVariations() {
	deprecated( "dispatch( 'core/edit-post' ).updatePreferredStyleVariations", {
		since: '6.6',
		hint: 'Preferred Style Variations are not supported anymore.',
	} );
	return { type: 'NOTHING' };
}

/**
 * Update the provided block types to be visible.
 *
 * @param {string[]} blockNames Names of block types to show.
 */
export const showBlockTypes =
	( blockNames ) =>
	( { registry } ) => {
		unlock( registry.dispatch( editorStore ) ).showBlockTypes( blockNames );
	};

/**
 * Update the provided block types to be hidden.
 *
 * @param {string[]} blockNames Names of block types to hide.
 */
export const hideBlockTypes =
	( blockNames ) =>
	( { registry } ) => {
		unlock( registry.dispatch( editorStore ) ).hideBlockTypes( blockNames );
	};

/**
 * Stores info about which Meta boxes are available in which location.
 *
 * @param {Object} metaBoxesPerLocation Meta boxes per location.
 */
export function setAvailableMetaBoxesPerLocation( metaBoxesPerLocation ) {
	return {
		type: 'SET_META_BOXES_PER_LOCATIONS',
		metaBoxesPerLocation,
	};
}

/**
 * Update a metabox.
 */
export const requestMetaBoxUpdates =
	() =>
	async ( { registry, select, dispatch } ) => {
		dispatch( {
			type: 'REQUEST_META_BOX_UPDATES',
		} );

		// Saves the wp_editor fields.
		if ( window.tinyMCE ) {
			window.tinyMCE.triggerSave();
		}

		// We gather the base form data.
		const baseFormData = new window.FormData(
			document.querySelector( '.metabox-base-form' )
		);

		const postId = baseFormData.get( 'post_ID' );
		const postType = baseFormData.get( 'post_type' );

		// Additional data needed for backward compatibility.
		// If we do not provide this data, the post will be overridden with the default values.
		// We cannot rely on getCurrentPost because right now on the editor we may be editing a pattern or a template.
		// We need to retrieve the post that the base form data is referring to.
		const post = registry
			.select( coreStore )
			.getEditedEntityRecord( 'postType', postType, postId );
		const additionalData = [
			post.comment_status
				? [ 'comment_status', post.comment_status ]
				: false,
			post.ping_status ? [ 'ping_status', post.ping_status ] : false,
			post.sticky ? [ 'sticky', post.sticky ] : false,
			post.author ? [ 'post_author', post.author ] : false,
		].filter( Boolean );

		// We gather all the metaboxes locations.
		const activeMetaBoxLocations = select.getActiveMetaBoxLocations();
		const formDataToMerge = [
			baseFormData,
			...activeMetaBoxLocations.map(
				( location ) =>
					new window.FormData( getMetaBoxContainer( location ) )
			),
		];

		// Merge all form data objects into a single one.
		const formData = formDataToMerge.reduce( ( memo, currentFormData ) => {
			for ( const [ key, value ] of currentFormData ) {
				memo.append( key, value );
			}
			return memo;
		}, new window.FormData() );
		additionalData.forEach( ( [ key, value ] ) =>
			formData.append( key, value )
		);

		try {
			// Save the metaboxes.
			await apiFetch( {
				url: window._wpMetaBoxUrl,
				method: 'POST',
				body: formData,
				parse: false,
			} );
			dispatch.metaBoxUpdatesSuccess();
		} catch {
			dispatch.metaBoxUpdatesFailure();
		}
	};

/**
 * Returns an action object used to signal a successful meta box update.
 *
 * @return {Object} Action object.
 */
export function metaBoxUpdatesSuccess() {
	return {
		type: 'META_BOX_UPDATES_SUCCESS',
	};
}

/**
 * Returns an action object used to signal a failed meta box update.
 *
 * @return {Object} Action object.
 */
export function metaBoxUpdatesFailure() {
	return {
		type: 'META_BOX_UPDATES_FAILURE',
	};
}

/**
 * Action that changes the width of the editing canvas.
 *
 * @deprecated
 *
 * @param {string} deviceType
 */
export const __experimentalSetPreviewDeviceType =
	( deviceType ) =>
	( { registry } ) => {
		deprecated(
			"dispatch( 'core/edit-post' ).__experimentalSetPreviewDeviceType",
			{
				since: '6.5',
				version: '6.7',
				hint: 'registry.dispatch( editorStore ).setDeviceType',
			}
		);
		registry.dispatch( editorStore ).setDeviceType( deviceType );
	};

/**
 * Returns an action object used to open/close the inserter.
 *
 * @deprecated
 *
 * @param {boolean|Object} value Whether the inserter should be opened (true) or closed (false).
 */
export const setIsInserterOpened =
	( value ) =>
	( { registry } ) => {
		deprecated( "dispatch( 'core/edit-post' ).setIsInserterOpened", {
			since: '6.5',
			alternative: "dispatch( 'core/editor').setIsInserterOpened",
		} );
		registry.dispatch( editorStore ).setIsInserterOpened( value );
	};

/**
 * Returns an action object used to open/close the list view.
 *
 * @deprecated
 *
 * @param {boolean} isOpen A boolean representing whether the list view should be opened or closed.
 */
export const setIsListViewOpened =
	( isOpen ) =>
	( { registry } ) => {
		deprecated( "dispatch( 'core/edit-post' ).setIsListViewOpened", {
			since: '6.5',
			alternative: "dispatch( 'core/editor').setIsListViewOpened",
		} );
		registry.dispatch( editorStore ).setIsListViewOpened( isOpen );
	};

/**
 * Returns an action object used to switch to template editing.
 *
 * @deprecated
 */
export function setIsEditingTemplate() {
	deprecated( "dispatch( 'core/edit-post' ).setIsEditingTemplate", {
		since: '6.5',
		alternative: "dispatch( 'core/editor').setRenderingMode",
	} );
	return { type: 'NOTHING' };
}

/**
 * Create a block based template.
 *
 * @deprecated
 */
export function __unstableCreateTemplate() {
	deprecated( "dispatch( 'core/edit-post' ).__unstableCreateTemplate", {
		since: '6.5',
	} );
	return { type: 'NOTHING' };
}

let metaBoxesInitialized = false;

/**
 * Initializes WordPress `postboxes` script and the logic for saving meta boxes.
 */
export const initializeMetaBoxes =
	() =>
	( { registry, select, dispatch } ) => {
		const isEditorReady = registry
			.select( editorStore )
			.__unstableIsEditorReady();

		if ( ! isEditorReady ) {
			return;
		}
		// Only initialize once.
		if ( metaBoxesInitialized ) {
			return;
		}
		const postType = registry.select( editorStore ).getCurrentPostType();
		if ( window.postboxes.page !== postType ) {
			window.postboxes.add_postbox_toggles( postType );
		}

		metaBoxesInitialized = true;

		// Save metaboxes on save completion, except for autosaves.
		addAction(
			'editor.savePost',
			'core/edit-post/save-metaboxes',
			async ( post, options ) => {
				if ( ! options.isAutosave && select.hasMetaBoxes() ) {
					await dispatch.requestMetaBoxUpdates();
				}
			}
		);

		dispatch( {
			type: 'META_BOXES_INITIALIZED',
		} );
	};

/**
 * Action that toggles Distraction free mode.
 * Distraction free mode expects there are no sidebars, as due to the
 * z-index values set, you can't close sidebars.
 *
 * @deprecated
 */
export const toggleDistractionFree =
	() =>
	( { registry } ) => {
		deprecated( "dispatch( 'core/edit-post' ).toggleDistractionFree", {
			since: '6.6',
			alternative: "dispatch( 'core/editor').toggleDistractionFree",
		} );
		registry.dispatch( editorStore ).toggleDistractionFree();
	};

/**
 * Action that toggles the Fullscreen Mode view option.
 */
export const toggleFullscreenMode =
	() =>
	( { registry } ) => {
		const isFullscreen = registry
			.select( preferencesStore )
			.get( 'core/edit-post', 'fullscreenMode' );

		registry
			.dispatch( preferencesStore )
			.toggle( 'core/edit-post', 'fullscreenMode' );

		registry
			.dispatch( noticesStore )
			.createInfoNotice(
				isFullscreen
					? __( 'Fullscreen mode deactivated.' )
					: __( 'Fullscreen mode activated.' ),
				{
					id: 'core/edit-post/toggle-fullscreen-mode/notice',
					type: 'snackbar',
					actions: [
						{
							label: __( 'Undo' ),

							onClick: () => {
								registry
									.dispatch( preferencesStore )
									.toggle(
										'core/edit-post',
										'fullscreenMode'
									);
							},
						},
					],
				}
			);
	};
