import { useSelect, useDispatch } from '@wordpress/data';
import { useInstanceId } from '@wordpress/compose';
import { useEffect, useCallback } from '@wordpress/element';
import {
	InspectorControls,
	useBlockProps,
	store as blockEditorStore,
	useInnerBlocksProps,
	privateApis as blockEditorPrivateApis,
} from '@wordpress/block-editor';
import { __, sprintf } from '@wordpress/i18n';
import { store as coreStore } from '@wordpress/core-data';
import { store as noticesStore } from '@wordpress/notices';
import EnhancedPaginationControl from './inspector-controls/enhanced-pagination-control';
import { unlock } from '../../lock-unlock';
import QueryInspectorControls from './inspector-controls';
import { getQueryContextFromTemplate, useUnsupportedBlocks } from '../utils';
import QueryToolbar from './query-toolbar';

const { HTMLElementControl } = unlock( blockEditorPrivateApis );

const DEFAULTS_POSTS_PER_PAGE = 3;

export default function QueryContent( {
	attributes,
	setAttributes,
	clientId,
	context,
	name,
	isSelected,
} ) {
	const {
		queryId,
		query,
		enhancedPagination,
		tagName: TagName = 'div',
		query: { inherit } = {},
	} = attributes;
	const { templateSlug, postType } = context;
	const { isSingular, templateType } =
		getQueryContextFromTemplate( templateSlug );
	const { __unstableMarkNextChangeAsNotPersistent } =
		useDispatch( blockEditorStore );
	const { createNotice } = useDispatch( noticesStore );
	const unsupportedBlocks = useUnsupportedBlocks( clientId );
	const instanceId = useInstanceId( QueryContent );
	const blockProps = useBlockProps();
	const innerBlocksProps = useInnerBlocksProps( blockProps );
	const { postsPerPage } = useSelect( ( select ) => {
		const { getSettings } = select( blockEditorStore );
		const { getEntityRecord, getEntityRecordEdits, canUser } =
			select( coreStore );
		const settingPerPage = canUser( 'read', {
			kind: 'root',
			name: 'site',
		} )
			? +getEntityRecord( 'root', 'site' )?.posts_per_page
			: +getSettings().postsPerPage;

		// Gets changes made via the template area posts per page setting. These won't be saved
		// until the page is saved, but we should reflect this setting within the query loops
		// that inherit it.
		const editedSettingPerPage = +getEntityRecordEdits( 'root', 'site' )
			?.posts_per_page;

		return {
			postsPerPage:
				editedSettingPerPage ||
				settingPerPage ||
				DEFAULTS_POSTS_PER_PAGE,
		};
	}, [] );

	// Whether the "exclude current" filter applies: we're in a singular template
	// and the post type matches the query.
	const shouldExcludeCurrentPost =
		isSingular &&
		! inherit &&
		( query.postType === postType || query.postType === templateType );

	// There are some effects running where some initialization logic is
	// happening and setting some values to some attributes (ex. queryId).
	// These updates can cause an `undo trap` where undoing will result in
	// resetting again, so we need to mark these changes as not persistent
	// with `__unstableMarkNextChangeAsNotPersistent`.

	// Changes in query property (which is an object) need to be in the same callback,
	// because updates are batched after the render and changes in different query properties
	// would cause to override previous wanted changes.
	const updateQuery = useCallback(
		( newQuery ) =>
			setAttributes( ( prevAttributes ) => ( {
				query: { ...prevAttributes.query, ...newQuery },
			} ) ),
		[ setAttributes ]
	);
	useEffect( () => {
		const newQuery = {};
		// When we inherit from global query always need to set the `perPage`
		// based on the reading settings.
		if ( inherit && query.perPage !== postsPerPage ) {
			newQuery.perPage = postsPerPage;
		} else if ( ! query.perPage && postsPerPage ) {
			newQuery.perPage = postsPerPage;
		}
		// Remove the exclusion when it no longer applies, so the filters stay
		// clean. We never force it on: enabling the exclusion is left to the
		// user. An absent key is already clean — writing `null` into it would
		// change the serialized markup of every pre-existing Query block just
		// by opening the editor.
		if (
			! shouldExcludeCurrentPost &&
			query.excludeCurrent !== undefined &&
			query.excludeCurrent !== null
		) {
			newQuery.excludeCurrent = null;
		}
		if ( !! Object.keys( newQuery ).length ) {
			__unstableMarkNextChangeAsNotPersistent();
			updateQuery( newQuery );
		}
	}, [
		query.perPage,
		query.excludeCurrent,
		inherit,
		postsPerPage,
		shouldExcludeCurrentPost,
		__unstableMarkNextChangeAsNotPersistent,
		updateQuery,
	] );
	// We need this for multi-query block pagination.
	// Query parameters for each block are scoped to their ID.
	useEffect( () => {
		if ( ! Number.isFinite( queryId ) ) {
			__unstableMarkNextChangeAsNotPersistent();
			setAttributes( { queryId: instanceId } );
		}
	}, [
		queryId,
		instanceId,
		__unstableMarkNextChangeAsNotPersistent,
		setAttributes,
	] );
	useEffect( () => {
		if ( enhancedPagination && unsupportedBlocks.length ) {
			__unstableMarkNextChangeAsNotPersistent();
			setAttributes( { enhancedPagination: false } );
			const message = sprintf(
				/* translators: %s: A list of block titles. */
				__(
					`"Reload full page" was enabled because some blocks inside the Query block aren't supported: %s.`
				),
				unsupportedBlocks.join(
					/* translators: Used between list items, there is a space after the comma. */
					__( ', ' ) // eslint-disable-line @wordpress/i18n-no-flanking-whitespace
				)
			);
			createNotice( 'info', message, {
				type: 'snackbar',
				id: 'query-enhanced-pagination-disabled',
			} );
		}
	}, [
		enhancedPagination,
		unsupportedBlocks,
		__unstableMarkNextChangeAsNotPersistent,
		setAttributes,
		createNotice,
	] );

	return (
		<>
			{ isSelected && (
				<QueryToolbar
					clientId={ clientId }
					attributes={ attributes }
					hasInnerBlocks
				/>
			) }
			<InspectorControls>
				<QueryInspectorControls
					name={ name }
					attributes={ attributes }
					setQuery={ updateQuery }
					setAttributes={ setAttributes }
					clientId={ clientId }
					isSingular={ isSingular }
					shouldExcludeCurrentPost={ shouldExcludeCurrentPost }
				/>
			</InspectorControls>
			<InspectorControls group="advanced">
				<HTMLElementControl
					tagName={ TagName }
					onChange={ ( value ) =>
						setAttributes( { tagName: value } )
					}
					clientId={ clientId }
					options={ [
						{ label: __( 'Default (<div>)' ), value: 'div' },
						{ label: '<main>', value: 'main' },
						{ label: '<section>', value: 'section' },
						{ label: '<aside>', value: 'aside' },
					] }
				/>
				<EnhancedPaginationControl
					enhancedPagination={ enhancedPagination }
					setAttributes={ setAttributes }
					clientId={ clientId }
				/>
			</InspectorControls>
			<TagName { ...innerBlocksProps } />
		</>
	);
}
