import clsx from 'clsx';
import { memo, useMemo, useState } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { __, _x } from '@wordpress/i18n';
import {
	BlockControls,
	BlockContextProvider,
	__experimentalUseBlockPreview as useBlockPreview,
	useBlockProps,
	useInnerBlocksProps,
	store as blockEditorStore,
} from '@wordpress/block-editor';
import { Spinner, ToolbarGroup } from '@wordpress/components';
import { store as coreStore } from '@wordpress/core-data';
import { list, grid } from '@wordpress/icons';

// The Post Template's layout attribute arranges the post items, not the
// blocks of the template, which always stack vertically inside each post
// item. Override the inherited layout so inner block UI (movers, inserters,
// grid controls) doesn't follow the post items' grid.
const INNER_BLOCKS_LAYOUT = { type: 'default' };

function PostTemplateInnerBlocks( { classList } ) {
	const innerBlocksProps = useInnerBlocksProps(
		{ className: clsx( 'wp-block-post', classList ) },
		{
			__unstableDisableLayoutClassNames: true,
			layout: INNER_BLOCKS_LAYOUT,
		}
	);
	return <li { ...innerBlocksProps } />;
}

function PostTemplateBlockPreview( {
	blocks,
	blockContextId,
	classList,
	isHidden,
	setActiveBlockContextId,
} ) {
	const blockPreviewProps = useBlockPreview( {
		blocks,
		props: {
			className: clsx( 'wp-block-post', classList ),
		},
	} );

	const handleOnClick = () => {
		setActiveBlockContextId( blockContextId );
	};

	const style = {
		display: isHidden ? 'none' : undefined,
	};

	return (
		<li
			{ ...blockPreviewProps }
			tabIndex={ 0 }
			// eslint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role
			role="button"
			onClick={ handleOnClick }
			onKeyPress={ handleOnClick }
			style={ style }
		/>
	);
}

const MemoizedPostTemplateBlockPreview = memo( PostTemplateBlockPreview );

export default function PostTemplateEdit( {
	setAttributes,
	clientId,
	context: {
		query: {
			perPage,
			offset = 0,
			// Match `build_query_vars_from_query_block()`, which queries posts when
			// `query` has no post type.
			postType = 'post',
			order,
			orderBy,
			author,
			search,
			exclude,
			sticky,
			inherit,
			taxQuery,
			parents,
			pages,
			format,
			excludeCurrent,
			// We gather extra query args to pass to the REST API call.
			// This way extenders of Query Loop can add their own query args,
			// and have accurate previews in the editor.
			// Noting though that these args should either be supported by the
			// REST API or be handled by custom REST filters like `rest_{$this->post_type}_query`.
			...restQueryArgs
		} = {},
		templateSlug,
		previewPostType,
		postId,
	},
	attributes: { layout },
	__unstableLayoutClassNames,
} ) {
	const {
		type: layoutType,
		columnCount = 3,
		minimumColumnWidth,
	} = layout || {};
	const [ activeBlockContextId, setActiveBlockContextId ] = useState();
	const { posts, blocks } = useSelect(
		( select ) => {
			const { getEntityRecords, getTaxonomies } = select( coreStore );
			const { getBlocks } = select( blockEditorStore );
			const templateCategory =
				inherit &&
				templateSlug?.startsWith( 'category-' ) &&
				getEntityRecords( 'taxonomy', 'category', {
					context: 'view',
					per_page: 1,
					_fields: [ 'id' ],
					slug: templateSlug.replace( 'category-', '' ),
				} );
			const templateTag =
				inherit &&
				templateSlug?.startsWith( 'tag-' ) &&
				getEntityRecords( 'taxonomy', 'post_tag', {
					context: 'view',
					per_page: 1,
					_fields: [ 'id' ],
					slug: templateSlug.replace( 'tag-', '' ),
				} );
			const query = {
				offset: offset || 0,
				order,
				orderby: orderBy,
			};
			// There is no need to build the taxQuery if we inherit.
			if ( taxQuery && ! inherit ) {
				const taxonomies = getTaxonomies( {
					type: postType,
					per_page: -1,
					context: 'view',
				} );
				// Build REST API parameters from taxonomy terms, e.g.
				// `category`, `tags_exclude`.
				const buildTaxQuery = ( terms, suffix = '' ) => {
					return Object.entries( terms || {} ).reduce(
						( accumulator, [ taxonomySlug, termIds ] ) => {
							const taxonomy = taxonomies?.find(
								( { slug } ) => slug === taxonomySlug
							);
							if ( taxonomy?.rest_base && termIds?.length ) {
								accumulator[ taxonomy.rest_base + suffix ] =
									termIds;
							}
							return accumulator;
						},
						{}
					);
				};
				const builtTaxQuery = buildTaxQuery( taxQuery.include );
				if ( taxQuery.exclude ) {
					Object.assign(
						builtTaxQuery,
						buildTaxQuery( taxQuery.exclude, '_exclude' )
					);
				}

				if ( !! Object.keys( builtTaxQuery ).length ) {
					Object.assign( query, builtTaxQuery );
				}
			}
			if ( perPage ) {
				query.per_page = perPage;
			}
			if ( author ) {
				query.author = author;
			}
			if ( search ) {
				query.search = search;
			}
			if ( exclude?.length ) {
				query.exclude = exclude;
			}
			if ( parents?.length ) {
				query.parent = parents;
			}
			if ( format?.length ) {
				query.format = format;
			}
			if ( excludeCurrent && postId ) {
				if ( query.exclude ) {
					query.exclude = [ ...query.exclude, postId ];
				} else {
					query.exclude = [ postId ];
				}
			}

			/*
			 * Handle cases where sticky is set to `exclude` or `only`.
			 * Which works as a `post__in/post__not_in` query for sticky posts.
			 */
			if ( [ 'exclude', 'only' ].includes( sticky ) ) {
				query.sticky = sticky === 'only';
			}

			// Empty string represents the default behavior of including sticky posts.
			if ( [ '', 'ignore' ].includes( sticky ) ) {
				// Remove any leftover sticky query parameter.
				delete query.sticky;
				query.ignore_sticky = sticky === 'ignore';
			}

			// If `inherit` is truthy, adjust conditionally the query to create a better preview.
			let currentPostType = postType;
			if ( inherit ) {
				// Change the post-type if needed.
				if ( templateSlug?.startsWith( 'archive-' ) ) {
					query.postType = templateSlug.replace( 'archive-', '' );
					currentPostType = query.postType;
				} else if ( templateCategory ) {
					query.categories = templateCategory[ 0 ]?.id;
				} else if ( templateTag ) {
					query.tags = templateTag[ 0 ]?.id;
				} else if (
					templateSlug?.startsWith( 'taxonomy-post_format' )
				) {
					// Get the post format slug from the template slug by removing the prefix.
					query.format = templateSlug.replace(
						'taxonomy-post_format-post-format-',
						''
					);
				}
			}
			// When we preview Query Loop blocks we should prefer the current
			// block's postType, which is passed through block context.
			const usedPostType = previewPostType || currentPostType;
			return {
				posts: getEntityRecords( 'postType', usedPostType, {
					...query,
					...restQueryArgs,
				} ),
				blocks: getBlocks( clientId ),
			};
		},
		[
			perPage,
			offset,
			order,
			orderBy,
			clientId,
			author,
			search,
			postType,
			exclude,
			sticky,
			inherit,
			templateSlug,
			taxQuery,
			parents,
			format,
			restQueryArgs,
			previewPostType,
		]
	);
	const blockContexts = useMemo(
		() =>
			posts?.map( ( post ) => ( {
				postType: post.type,
				postId: post.id,
				classList: post.class_list ?? '',
			} ) ),
		[ posts ]
	);

	const blockProps = useBlockProps( {
		className: clsx( __unstableLayoutClassNames, {
			[ `columns-${ columnCount }` ]:
				layoutType === 'grid' && columnCount, // Ensure column count is flagged via classname for backwards compatibility.
			'has-native-responsive-grid':
				layoutType === 'grid' && columnCount && minimumColumnWidth, // Flag native responsive grid when minimum column width is provided.
		} ),
	} );

	if ( ! posts ) {
		return (
			<p { ...blockProps }>
				<Spinner />
			</p>
		);
	}

	if ( ! posts.length ) {
		return <p { ...blockProps }> { __( 'No results found.' ) }</p>;
	}

	const setDisplayLayout = ( newDisplayLayout ) =>
		setAttributes( {
			layout: { ...layout, ...newDisplayLayout },
		} );

	const displayLayoutControls = [
		{
			icon: list,
			title: _x( 'List view', 'Post template block display setting' ),
			onClick: () => setDisplayLayout( { type: 'default' } ),
			isActive: layoutType === 'default' || layoutType === 'constrained',
		},
		{
			icon: grid,
			title: _x( 'Grid view', 'Post template block display setting' ),
			onClick: () =>
				setDisplayLayout( {
					type: 'grid',
					columnCount,
				} ),
			isActive: layoutType === 'grid',
		},
	];

	// To avoid flicker when switching active block contexts, a preview is rendered
	// for each block context, but the preview for the active block context is hidden.
	// This ensures that when it is displayed again, the cached rendering of the
	// block preview is used, instead of having to re-render the preview from scratch.
	return (
		<>
			<BlockControls>
				<ToolbarGroup controls={ displayLayoutControls } />
			</BlockControls>

			<ul { ...blockProps }>
				{ blockContexts &&
					blockContexts.map( ( blockContext ) => (
						<BlockContextProvider
							key={ blockContext.postId }
							value={ blockContext }
						>
							{ blockContext.postId ===
							( activeBlockContextId ||
								blockContexts[ 0 ]?.postId ) ? (
								<PostTemplateInnerBlocks
									classList={ blockContext.classList }
								/>
							) : null }
							<MemoizedPostTemplateBlockPreview
								blocks={ blocks }
								blockContextId={ blockContext.postId }
								classList={ blockContext.classList }
								setActiveBlockContextId={
									setActiveBlockContextId
								}
								isHidden={
									blockContext.postId ===
									( activeBlockContextId ||
										blockContexts[ 0 ]?.postId )
								}
							/>
						</BlockContextProvider>
					) ) }
			</ul>
		</>
	);
}
