/**
 * External dependencies
 */
import {
	SelectControl,
	__experimentalToggleGroupControl as ToggleGroupControl,
	__experimentalToggleGroupControlOption as ToggleGroupControlOption,
	__experimentalToolsPanel as ToolsPanel,
	__experimentalToolsPanelItem as ToolsPanelItem,
	TextControl,
} from '@wordpress/components';
import { debounce } from '@wordpress/compose';
import { store as coreStore } from '@wordpress/core-data';
import { useSelect } from '@wordpress/data';
import { useEffect, useState, useCallback } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import { OrderControl, OrderByControl } from './order-controls';
import StickyControl from './sticky-control';
import FormatControls from './format-controls';
import PerPageControl from './per-page-control';
import OffsetControl from './offset-controls';
import PagesControl from './pages-control';
import {
	usePostTypes,
	useAllowedControls,
	isControlAllowed,
	useTaxonomies,
	useIsPostTypeHierarchical,
} from '@spectra-pro-helpers/loop-builder';
import ParentControl from './parent-control';
import AuthorControl from './author-control';
import { TaxonomyControls } from './taxonomy-controls';

/**
 * Inspector controls for the loop block.
 *
 * This component displays a set of controls for the loop block,
 * allowing the user to customize the query used to fetch posts.
 *
 * @param {Object} props The component props.
 * @return {Element} The component.
 */
export const QueryInspectorControls = ( props ) => {
	const { attributes, setQuery } = props;
	const { query } = attributes;
	const {
		order,
		orderBy,
		author: authorIds,
		pages,
		postType,
		perPage,
		offset,
		sticky,
		taxQuery,
		parents,
		format,
	} = query;

	const allowedControls = useAllowedControls( attributes );
	const showSticky = postType === 'post';
	const {
		postTypesTaxonomiesMap,
		postTypesSelectOptions,
		postTypeFormatSupportMap,
	} = usePostTypes();
	const taxonomies = useTaxonomies( postType );
	const isPostTypeHierarchical = useIsPostTypeHierarchical( postType );

	/**
	 * Handles the change of post type in the query settings.
	 *
	 * This function updates the query object when the post type changes.
	 * It adjusts various query properties based on the selected post type,
	 * ensuring compatibility with the available taxonomies and post format support.
	 *
	 * @since x.x.x
	 *
	 * @param {string} newValue - The new post type selected by the user.
	 */
	const onPostTypeChange = ( newValue ) => {
		const updateQuery = { postType: newValue };
		// We need to dynamically update the `taxQuery` property,
		// by removing any not supported taxonomy from the query.
		const supportedTaxonomies = postTypesTaxonomiesMap[ newValue ];
		const updatedTaxQuery = Object.entries( taxQuery || {} ).reduce(
			( accumulator, [ taxonomySlug, terms ] ) => {
				if ( supportedTaxonomies.includes( taxonomySlug ) ) {
					accumulator[ taxonomySlug ] = terms;
				}
				return accumulator;
			},
			{}
		);
		updateQuery.taxQuery = !! Object.keys( updatedTaxQuery ).length
			? updatedTaxQuery
			: undefined;

		if ( newValue !== 'post' ) {
			updateQuery.sticky = '';
		}
		// We need to reset `parents` because they are tied to each post type.
		updateQuery.parents = [];
		// Post types can register post format support with `add_post_type_support`.
		// But we need to reset the `format` property when switching to post types
		// that do not support post formats.
		const hasFormatSupport = postTypeFormatSupportMap[ newValue ];
		if ( ! hasFormatSupport ) {
			updateQuery.format = [];
		}

		setQuery( updateQuery );
	};
	const [ querySearch, setQuerySearch ] = useState( query.search );

	// Debounce the search query.
	const onChangeDebounced = useCallback(
		debounce( () => {
			if ( query.search !== querySearch ) {
				setQuery( { search: querySearch } );
			}
		}, 250 ),
		[ querySearch, query.search ]
	);
	useEffect( () => {
		onChangeDebounced();
		return onChangeDebounced.cancel;
	}, [ querySearch, onChangeDebounced ] );

	// Show the post type control if the control is allowed.
	const showPostTypeControl = isControlAllowed( allowedControls, 'postType' );

	// The label for the post type control.
	const postTypeControlLabel = __( 'Post type', 'spectra-pro' );

	// The help text for the post type control.
	const postTypeControlHelp = __(
		'Select the type of content to display: posts, pages, or custom post types.',
		'spectra-pro'
	);
	const showColumnsControl = false;

	// Show the order control if the control is allowed.
	const showOrderControl = isControlAllowed( allowedControls, 'order' );

	// Show the order by control if the control is allowed.
	const showOrderByControl = isControlAllowed( allowedControls, 'orderBy' );

	// Show the sticky control if the post type is `post` and if the control is allowed.
	const showStickyControl =
		showSticky && isControlAllowed( allowedControls, 'sticky' );

	// Show the settings panel if any of the controls are allowed.
	const showSettingsPanel =
		showPostTypeControl ||
		showColumnsControl ||
		showOrderControl ||
		showOrderByControl ||
		showStickyControl;
	const showTaxControl =
		!! taxonomies?.length &&
		isControlAllowed( allowedControls, 'taxQuery' );

	// Show the author control if the control is allowed.
	const showAuthorControl = isControlAllowed( allowedControls, 'author' );

	// Show the search control if the control is allowed.
	const showSearchControl = isControlAllowed( allowedControls, 'search' );

	// Show the parent control if the post type is hierarchical and if the control is allowed.
	const showParentControl =
		isControlAllowed( allowedControls, 'parents' ) &&
		isPostTypeHierarchical;

	const postTypeHasFormatSupport = postTypeFormatSupportMap[ postType ];

	// Show the format control if the post type supports post formats and if the control is allowed.
	const showFormatControl = useSelect(
		( select ) => {
			// Check if the post type supports post formats and if the control is allowed.
			if (
				! postTypeHasFormatSupport ||
				! isControlAllowed( allowedControls, 'format' )
			) {
				return false;
			}

			const themeSupports = select( coreStore ).getThemeSupports();

			// If there are no supported formats, getThemeSupports still includes the default 'standard' format,
			// and in this case the control should not be shown since the user has no other formats to choose from.
			return (
				themeSupports.formats &&
				themeSupports.formats.length > 0 &&
				themeSupports.formats.some( ( type ) => type !== 'standard' )
			);
		},
		[ allowedControls, postTypeHasFormatSupport ]
	);

	// Show the filters panel if any of the controls are shown.
	const showFiltersPanel =
		showTaxControl ||
		showAuthorControl ||
		showSearchControl ||
		showParentControl ||
		showFormatControl;

	// Show the pagination panel if any of the controls are shown.
	const showPostCountControl = isControlAllowed(
		allowedControls,
		'postCount'
	);

	// Show the pagination panel if any of the controls are shown.
	const showOffSetControl = isControlAllowed( allowedControls, 'offset' );

	// Show the pagination panel if any of the controls are shown.
	const showPagesControl = isControlAllowed( allowedControls, 'pages' );

	// Show the display panel if any of the controls are shown.
	const showDisplayPanel =
		showPostCountControl || showOffSetControl || showPagesControl;

	return (
		<>
			{ /* This ToolsPanel contains general settings for the Loop Builder block:
				- Post Type
				- Order
				- Order By
				- Sticky Posts
			*/ }
			{ showSettingsPanel && (
				<ToolsPanel
					label={ __( 'Settings', 'spectra-pro' ) }
					resetAll={ () => {
						setQuery( {
							postType: 'post',
							order: 'desc',
							orderBy: 'date',
							sticky: '',
						} );
					} }
				>
					{ /* This ToolsPanelItem will reset when the following condition is met:
							- The "postType" attribute is set to "post". Default: 'post'.
					*/ }
					{ showPostTypeControl && (
						<ToolsPanelItem
							hasValue={ () => postType !== 'post' }
							label={ postTypeControlLabel }
							onDeselect={ () => onPostTypeChange( 'post' ) }
							isShownByDefault
						>
							{ postTypesSelectOptions.length > 2 ? (
								<SelectControl
									__nextHasNoMarginBottom
									__next40pxDefaultSize
									options={ postTypesSelectOptions }
									value={ postType }
									label={ postTypeControlLabel }
									onChange={ onPostTypeChange }
									help={ postTypeControlHelp }
								/>
							) : (
								<ToggleGroupControl
									__nextHasNoMarginBottom
									__next40pxDefaultSize
									isBlock
									value={ postType }
									label={ postTypeControlLabel }
									onChange={ onPostTypeChange }
									help={ postTypeControlHelp }
								>
									{ postTypesSelectOptions.map(
										( option ) => (
											<ToggleGroupControlOption
												key={ option.value }
												value={ option.value }
												label={ option.label }
											/>
										)
									) }
								</ToggleGroupControl>
							) }
						</ToolsPanelItem>
					) }

					{ /* This ToolsPanelItem will reset when the following condition is met:
							- The "order" attribute is set to "desc". Default: 'desc'.
					*/ }
					{ showOrderControl && (
						<ToolsPanelItem
							hasValue={ () => order !== 'desc' }
							label={ __( 'Order', 'spectra-pro' ) }
							onDeselect={ () => setQuery( { order: 'desc' } ) }
							isShownByDefault
						>
							<OrderControl
								{ ...{ order } }
								onChange={ setQuery }
							/>
						</ToolsPanelItem>
					) }

					{ /* This ToolsPanelItem will reset when the following condition is met:
							- The "orderBy" attribute is set to "date". Default: 'date'.
					*/ }
					{ showOrderByControl && (
						<ToolsPanelItem
							hasValue={ () => orderBy !== 'date' }
							label={ __( 'Order by', 'spectra-pro' ) }
							onDeselect={ () => setQuery( { orderBy: 'date' } ) }
							isShownByDefault
						>
							<OrderByControl
								{ ...{ orderBy } }
								onChange={ setQuery }
							/>
						</ToolsPanelItem>
					) }

					{ /* This ToolsPanelItem will reset when the following condition is met:
							- The "sticky" attribute is set to an empty string. Default: ''.
					*/ }
					{ showStickyControl && (
						<ToolsPanelItem
							hasValue={ () => !! sticky }
							label={ __( 'Sticky posts', 'spectra-pro' ) }
							onDeselect={ () => setQuery( { sticky: '' } ) }
							isShownByDefault
						>
							<StickyControl
								value={ sticky }
								onChange={ ( value ) =>
									setQuery( { sticky: value } )
								}
							/>
						</ToolsPanelItem>
					) }
				</ToolsPanel>
			) }

			{ /* This ToolsPanel contains display settings for the Loop Builder block:
				- Posts per Page
				- Offset
				- Max Pages to Show
			*/ }
			{ showDisplayPanel && (
				<ToolsPanel
					label={ __( 'Display', 'spectra-pro' ) }
					resetAll={ () => {
						setQuery( {
							offset: 0,
							pages: 0,
						} );
					} }
				>
					{ /* This ToolsPanelItem will reset when the following condition is met:
						- The "perPage" attribute is set to 0 or less. Default: 0.
					*/ }
					<ToolsPanelItem
						label={ __( 'Posts per page', 'spectra-pro' ) }
						hasValue={ () => perPage > 0 }
					>
						<PerPageControl
							perPage={ perPage }
							offset={ offset }
							onChange={ setQuery }
						/>
					</ToolsPanelItem>

					{ /* This ToolsPanelItem will reset when the following condition is met:
						- The "offset" attribute is set to 0. Default: 0.
					*/ }
					<ToolsPanelItem
						label={ __( 'Offset', 'spectra-pro' ) }
						hasValue={ () => offset > 0 }
						onDeselect={ () => setQuery( { offset: 0 } ) }
					>
						<OffsetControl
							offset={ offset }
							onChange={ setQuery }
						/>
					</ToolsPanelItem>

					{ /* This ToolsPanelItem will reset when the following condition is met:
						- The "pages" attribute is set to 0. Default: 0.
					*/ }
					<ToolsPanelItem
						label={ __( 'Max pages to show', 'spectra-pro' ) }
						hasValue={ () => pages > 0 }
						onDeselect={ () => setQuery( { pages: 0 } ) }
					>
						<PagesControl pages={ pages } onChange={ setQuery } />
					</ToolsPanelItem>
				</ToolsPanel>
			) }

			{ /* This ToolsPanel contains filter settings for the Loop Builder block:
				- Taxonomies
				- Authors
				- Keyword (Search)
				- Parents
				- Formats
			*/ }
			{ showFiltersPanel && (
				<ToolsPanel
					label={ __( 'Filters', 'spectra-pro' ) }
					resetAll={ () => {
						setQuery( {
							author: '',
							parents: [],
							search: '',
							taxQuery: null,
							format: [],
						} );
						setQuerySearch( '' );
					} }
				>
					{ /* This ToolsPanelItem will reset when the following condition is met:
							- The "taxQuery" attribute is set to null. Default: null.
					*/ }
					{ showTaxControl && (
						<ToolsPanelItem
							label={ __( 'Taxonomies', 'spectra-pro' ) }
							hasValue={ () =>
								Object.values( taxQuery || {} ).some(
									( terms ) => !! terms.length
								)
							}
							onDeselect={ () => setQuery( { taxQuery: null } ) }
						>
							<TaxonomyControls
								onChange={ setQuery }
								query={ query }
							/>
						</ToolsPanelItem>
					) }
					{ /* This ToolsPanelItem will reset when the following condition is met:
							- The "author" attribute is set to an empty string. Default: ''.
					*/ }
					{ showAuthorControl && (
						<ToolsPanelItem
							hasValue={ () => !! authorIds }
							label={ __( 'Authors', 'spectra-pro' ) }
							onDeselect={ () => setQuery( { author: '' } ) }
						>
							<AuthorControl
								value={ authorIds }
								onChange={ setQuery }
							/>
						</ToolsPanelItem>
					) }
					{ /* This ToolsPanelItem will reset when the following condition is met:
							- The "search" attribute is set to an empty string. Default: ''.
					*/ }
					{ showSearchControl && (
						<ToolsPanelItem
							hasValue={ () => !! querySearch }
							label={ __( 'Keyword', 'spectra-pro' ) }
							onDeselect={ () => setQuerySearch( '' ) }
						>
							<TextControl
								__nextHasNoMarginBottom
								__next40pxDefaultSize
								label={ __( 'Keyword', 'spectra-pro' ) }
								value={ querySearch }
								onChange={ setQuerySearch }
							/>
						</ToolsPanelItem>
					) }
					{ /* This ToolsPanelItem will reset when the following condition is met:
							- The "parents" attribute is set to an empty array. Default: [].
					*/ }
					{ showParentControl && (
						<ToolsPanelItem
							hasValue={ () => !! parents?.length }
							label={ __( 'Parents', 'spectra-pro' ) }
							onDeselect={ () => setQuery( { parents: [] } ) }
						>
							<ParentControl
								parents={ parents }
								postType={ postType }
								onChange={ setQuery }
							/>
						</ToolsPanelItem>
					) }
					{ /* This ToolsPanelItem will reset when the following condition is met:
							- The "format" attribute is set to an empty array. Default: [].
					*/ }
					{ showFormatControl && (
						<ToolsPanelItem
							hasValue={ () => !! format?.length }
							label={ __( 'Formats', 'spectra-pro' ) }
							onDeselect={ () => setQuery( { format: [] } ) }
						>
							<FormatControls
								onChange={ setQuery }
								query={ query }
							/>
						</ToolsPanelItem>
					) }
				</ToolsPanel>
			) }
		</>
	);
};

export default QueryInspectorControls;
