/**
 * External dependencies.
 */
import { InspectorControls } from '@wordpress/block-editor';
import {
	ToggleControl,
	SelectControl,
	FormTokenField,
	__experimentalToolsPanel as ToolsPanel,
	__experimentalToolsPanelItem as ToolsPanelItem,
	__experimentalToggleGroupControl as ToggleGroupControl,
	__experimentalToggleGroupControlOption as ToggleGroupControlOption,
} from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { memo, useMemo } from '@wordpress/element';
import { decodeEntities } from '@wordpress/html-entities';
import { __, _x } from '@wordpress/i18n';

/**
 * Array of filter type options for the Query Category block.
 *
 * Defines the available filter display types: Select, Checkbox, and Button.
 *
 * @type {Array<Object>}
 */
const FILTER_TYPES = [
	{
		label: _x( 'Select', 'Filter type option for Query Category block', 'spectra-pro' ),
		value: 'select',
	},
	{
		label: _x( 'Checkbox', 'Filter type option for Query Category block', 'spectra-pro' ),
		value: 'checkbox',
	},
	{
		label: _x( 'Button', 'Filter type option for Query Category block', 'spectra-pro' ),
		value: 'button',
	},
];

/**
 * Filters taxonomy term suggestions based on already selected tokens.
 *
 * Excludes terms that are already selected to avoid duplicates in the suggestion list.
 *
 * @param {Array<Object>} terms          Array of term objects with label and value properties.
 * @param {Array<number>} selectedTokens Array of selected term IDs.
 * @return {Array<string>} Array of term labels available as suggestions.
 */
const getSuggestions = ( terms, selectedTokens ) => {
	if ( ! terms.length ) {
		return [];
	}
	return terms.filter( ( term ) => ! selectedTokens.includes( term.value ) ).map( ( term ) => term.label );
};

/**
 * Element Sub-settings: General settings.
 *
 * @param {Object} props The element props.
 * @since x.x.x
 * @return {Element} The rendered block settings.
 */
const BlockSettings = memo( ( props ) => {
	// Destructure the required props.
	const {
		setAttributes,
		attributes: {
			filterType,
			showPostCount,
			showEmptyTaxonomy,
			showChildren,
			filterMode,
			taxonomyType,
			tagTaxonomyInclude,
			tagTaxonomyExclude,
			catTaxonomyInclude,
			catTaxonomyExclude,
		},
	} = props;

	/**
	 * Resets all settings to their default values.
	 *
	 * Updates the block attributes to their initial states, clearing taxonomy filters
	 * and resetting toggles and selections.
	 */
	const resetAllSettings = () => {
		setAttributes( {
			filterType: 'select', // Default to select filter type.
			showPostCount: true, // Default to showing post counts.
			showEmptyTaxonomy: false, // Default to hiding empty terms.
			showChildren: false, // Default to hiding child terms.
			filterMode: 'include', // Default to include mode.
			taxonomyType: 'category', // Default to category taxonomy.
			tagTaxonomyInclude: [], // Clear included tags.
			tagTaxonomyExclude: [], // Clear excluded tags.
			catTaxonomyInclude: [], // Clear included categories and custom taxonomies.
			catTaxonomyExclude: [], // Clear excluded categories and custom taxonomies.
			placeholder: __( 'Select', 'spectra-pro' ), // Default placeholder text.
		} );
	};

	// Fetch taxonomy terms based on the selected taxonomy type and settings.
	const terms = useSelect(
		( select ) => {
			// Return empty array if no taxonomy type is selected.
			if ( ! taxonomyType ) {
				return [];
			}

			// Fetch terms from the core data store.
			const fetchedTerms =
				select( 'core' ).getEntityRecords( 'taxonomy', taxonomyType, {
					per_page: -1,
					_fields: [ 'id', 'name', 'parent' ], // Limit fields for efficiency.
					hide_empty: ! showEmptyTaxonomy, // Filter out empty terms unless specified.
				} ) || []; // Fallback to empty array if fetch fails.

			// Filter out child terms if showChildren is false.
			const filteredTerms = showChildren ? fetchedTerms : fetchedTerms.filter( ( term ) => ! term.parent );

			// Map terms to label/value pairs for use in controls.
			return filteredTerms.map( ( term ) => ( {
				label: term.name,
				value: term.id,
			} ) );
		},
		[ taxonomyType, showChildren, showEmptyTaxonomy ]
	);

	const termsOptions = useSelect( ( select ) => {
		// Destructure functions to fetch data from the WordPress core store.
		const { getTaxonomies } = select( coreStore );

		// Fetch all registered taxonomies from the WordPress REST API.
		const taxonomies = getTaxonomies();

		// If no taxonomies are found, return an empty array with a default message.
		if ( ! taxonomies ) {
			return [
				{
					label: __( 'No Taxonomies Found', 'spectra-pro' ),
					value: '',
				},
			];
		}

		// Filter taxonomies that are publicly queryable.
		const filteredTaxonomies = taxonomies.filter( ( { visibility } ) => !! visibility?.publicly_queryable );

		// Map over filtered taxonomies and create options for the select dropdown.
		return filteredTaxonomies
			.map( ( taxObj ) => ( {
				label: decodeEntities( taxObj.name ), // Decode HTML entities in taxonomy name.
				value: taxObj.slug, // Taxonomy slug as the value.
			} ) )
			.filter( Boolean ); // Remove any null values.
	}, [] );

	// Memoize suggestions for each token field to optimize performance.
	const catTaxonomyIncludeSuggestions = useMemo( () => getSuggestions( terms, catTaxonomyInclude ), [
		terms,
		catTaxonomyInclude,
	] ); // Filter suggestions for category include.
	const catTaxonomyExcludeSuggestions = useMemo( () => getSuggestions( terms, catTaxonomyExclude ), [
		terms,
		catTaxonomyExclude,
	] ); // Filter suggestions for category exclude.
	const tagTaxonomyIncludeSuggestions = useMemo( () => getSuggestions( terms, tagTaxonomyInclude ), [
		terms,
		tagTaxonomyInclude,
	] ); // Filter suggestions for tag include.
	const tagTaxonomyExcludeSuggestions = useMemo( () => getSuggestions( terms, tagTaxonomyExclude ), [
		terms,
		tagTaxonomyExclude,
	] ); // Filter suggestions for tag exclude.

	// Convert stored token IDs to their corresponding labels for display in FormTokenField.
	const mapTokenValuesToLabels = ( selectedTokens ) => {
		return selectedTokens.map( ( id ) => {
			const term = terms.find( ( t ) => t.value === id );
			return term ? term.label : id; // Fallback to id if term isn’t found.
		} );
	};

	return (
		<InspectorControls group="settings">
			{ /* This ToolsPanel contains general settings for the Query Category block:
				- Filter Display Type
				- Post Counts
				- Empty Terms
				- Child Terms
				- Taxonomy Selection
				- Taxonomy Include/Exclude Settings
			*/ }
			<ToolsPanel label={ __( 'General', 'spectra-pro' ) } resetAll={ resetAllSettings }>
				{ /* This ToolsPanelItem will reset when the following condition is met:
					- The "filterType" attribute is set to "select". Default: 'select'.
				*/ }
				<ToolsPanelItem
					hasValue={ () => !! filterType && filterType !== 'select' }
					label={ __( 'Filter Display Type', 'spectra-pro' ) }
					onDeselect={ () => setAttributes( { filterType: 'select' } ) }
					isShownByDefault
				>
					<ToggleGroupControl
						__next40pxDefaultSize
						__nextHasNoMarginBottom
						label={ __( 'Filter Display Type', 'spectra-pro' ) }
						value={ filterType }
						onChange={ ( value ) => {
							setAttributes( { filterType: value } );
						} }
						help={
							'button' !== filterType && __( 'Block Spacing will not affect this type', 'spectra-pro' )
						}
						isBlock
					>
						{ FILTER_TYPES.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 "showPostCount" attribute is set to true. Default: true.
				*/ }
				<ToolsPanelItem
					hasValue={ () => ! showPostCount }
					label={ __( 'Post Counts', 'spectra-pro' ) }
					onDeselect={ () => setAttributes( { showPostCount: true } ) }
					isShownByDefault
				>
					<ToggleControl
						__nextHasNoMarginBottom
						label={ __( 'Show Posts Count', 'spectra-pro' ) }
						help={ __( 'Display the number of posts associated with each term.', 'spectra-pro' ) }
						onChange={ ( value ) => setAttributes( { showPostCount: value } ) }
						checked={ showPostCount === true }
					/>
				</ToolsPanelItem>

				{ /* This ToolsPanelItem will reset when the following condition is met:
					- The "showEmptyTaxonomy" attribute is set to false. Default: false.
				*/ }
				<ToolsPanelItem
					hasValue={ () => ! showEmptyTaxonomy }
					label={ __( 'Empty Terms', 'spectra-pro' ) }
					onDeselect={ () => setAttributes( { showEmptyTaxonomy: false } ) }
					isShownByDefault
				>
					<ToggleControl
						__nextHasNoMarginBottom
						label={ __( 'Show Empty Terms', 'spectra-pro' ) }
						help={ __( 'Include taxonomies even if they have no associated posts.', 'spectra-pro' ) }
						checked={ showEmptyTaxonomy === true }
						onChange={ ( value ) => setAttributes( { showEmptyTaxonomy: value } ) }
					/>
				</ToolsPanelItem>

				{ /* This ToolsPanelItem will reset when the following condition is met:
					- The "showChildren" attribute is set to false. Default: false.
				*/ }
				<ToolsPanelItem
					hasValue={ () => ! showChildren }
					label={ __( 'Child Terms', 'spectra-pro' ) }
					onDeselect={ () => setAttributes( { showChildren: true } ) }
					isShownByDefault
				>
					<ToggleControl
						__nextHasNoMarginBottom
						label={ __( 'Show Child Terms', 'spectra-pro' ) }
						help={ __( 'Display hierarchical relationships between terms.', 'spectra-pro' ) }
						onChange={ ( value ) => setAttributes( { showChildren: value } ) }
						checked={ showChildren === true }
					/>
				</ToolsPanelItem>

				{ /* This ToolsPanelItem will reset when the following condition is met:
					- The "taxonomyType" attribute is set to "category". Default: 'category'.
				*/ }
				<ToolsPanelItem
					hasValue={ () => !! taxonomyType }
					label={ __( 'Choose Taxonomy', 'spectra-pro' ) }
					onDeselect={ () => setAttributes( { taxonomyType: 'category' } ) }
					isShownByDefault
				>
					<SelectControl
						__next40pxDefaultSize
						__nextHasNoMarginBottom
						label={ __( 'Choose Taxonomy', 'spectra-pro' ) }
						value={ taxonomyType }
						options={ termsOptions }
						onChange={ ( value ) =>
							setAttributes( {
								taxonomyType: value,
								catTaxonomyInclude: [],
								catTaxonomyExclude: [],
								tagTaxonomyInclude: [],
								tagTaxonomyExclude: [],
							} )
						}
						help={ __( 'Select the taxonomy type for filtering.', 'spectra-pro' ) }
					/>
				</ToolsPanelItem>

				{ taxonomyType && (
					<>
						{ /* This ToolsPanelItem will reset when the following condition is met:
							- The "filterMode" attribute is set to "include". Default: 'include'.
						*/ }
						<ToolsPanelItem
							hasValue={ () => !! filterMode }
							label={ __( 'Taxonomy Settings', 'spectra-pro' ) }
							onDeselect={ () => setAttributes( { filterMode: 'include' } ) }
							isShownByDefault
						>
							<ToggleGroupControl
								__next40pxDefaultSize
								__nextHasNoMarginBottom
								label={ __( 'Filter Mode', 'spectra-pro' ) }
								value={ filterMode }
								onChange={ ( value ) => setAttributes( { filterMode: value } ) }
								help={ __(
									'Choose whether to include or exclude the selected taxonomy.',
									'spectra-pro'
								) }
								isBlock
							>
								<ToggleGroupControlOption
									value="include"
									label={ _x( 'Include', 'Taxonomy filter option', 'spectra-pro' ) }
								/>
								<ToggleGroupControlOption
									value="exclude"
									label={ _x( 'Exclude', 'Taxonomy filter option', 'spectra-pro' ) }
								/>
							</ToggleGroupControl>
						</ToolsPanelItem>

						{ /* Tax Type: Include/Exclude */ }
						{ filterMode === 'include' && (
							<>
								{ /* This ToolsPanelItem will reset when the following condition is met:
									- The "catTaxonomyInclude" attribute is set to an empty array. Default: [].
								*/ }
								{ 'category' === taxonomyType && (
									<ToolsPanelItem
										hasValue={ () => !! catTaxonomyInclude }
										label={ __( 'Include Taxonomy', 'spectra-pro' ) }
										onDeselect={ () =>
											setAttributes( {
												catTaxonomyInclude: [],
											} )
										}
										isShownByDefault
									>
										<FormTokenField
											label={ __( 'Include Taxonomy', 'spectra-pro' ) }
											value={ mapTokenValuesToLabels( catTaxonomyInclude ) }
											suggestions={ catTaxonomyIncludeSuggestions }
											onChange={ ( values ) =>
												setAttributes( {
													catTaxonomyInclude: values.map( ( label ) => {
														const term = terms.find( ( t ) => t.label === label );
														return term ? term.value : label;
													} ),
												} )
											}
											__experimentalShowHowTo={ false }
											__experimentalExpandOnFocus
											__nextHasNoMarginBottom
											__next40pxDefaultSize
										/>
									</ToolsPanelItem>
								) }

								{ /* This ToolsPanelItem will reset when the following condition is met:
									- The "tagTaxonomyInclude" attribute is set to an empty array. Default: [].
								*/ }
								{ 'post_tag' === taxonomyType && (
									<ToolsPanelItem
										hasValue={ () => !! tagTaxonomyInclude }
										label={ __( 'Include Taxonomy', 'spectra-pro' ) }
										onDeselect={ () =>
											setAttributes( {
												tagTaxonomyInclude: [],
											} )
										}
										isShownByDefault
									>
										<FormTokenField
											label={ __( 'Include Taxonomy', 'spectra-pro' ) }
											value={ mapTokenValuesToLabels( tagTaxonomyInclude ) }
											suggestions={ tagTaxonomyIncludeSuggestions }
											onChange={ ( values ) =>
												setAttributes( {
													tagTaxonomyInclude: values.map( ( label ) => {
														const term = terms.find( ( t ) => t.label === label );
														return term ? term.value : label;
													} ),
												} )
											}
											__experimentalShowHowTo={ false }
											__experimentalExpandOnFocus
											__nextHasNoMarginBottom
											__next40pxDefaultSize
										/>
									</ToolsPanelItem>
								) }

								{ /* This ToolsPanelItem will reset when the following condition is met:
									- The "taxonomyInclude" attribute is set to an empty array. Default: [].
								*/ }
								{ ! [ 'category', 'post_tag' ].includes( taxonomyType ) && (
									<ToolsPanelItem
										hasValue={ () => !! catTaxonomyInclude }
										label={ __( 'Include Taxonomy', 'spectra-pro' ) }
										onDeselect={ () =>
											setAttributes( {
												catTaxonomyInclude: [],
											} )
										}
										isShownByDefault
									>
										<FormTokenField
											label={ __( 'Include Taxonomy', 'spectra-pro' ) }
											value={ mapTokenValuesToLabels( catTaxonomyInclude ) }
											suggestions={ catTaxonomyIncludeSuggestions }
											onChange={ ( values ) =>
												setAttributes( {
													catTaxonomyInclude: values.map( ( label ) => {
														const term = terms.find( ( t ) => t.label === label );
														return term ? term.value : label;
													} ),
												} )
											}
											__experimentalShowHowTo={ false }
											__experimentalExpandOnFocus
											__nextHasNoMarginBottom
											__next40pxDefaultSize
										/>
									</ToolsPanelItem>
								) }
							</>
						) }

						{ filterMode === 'exclude' && (
							<>
								{ /* This ToolsPanelItem will reset when the following condition is met:
									- The "catTaxonomyExclude" attribute is set to an empty array. Default: [].
								*/ }
								{ 'category' === taxonomyType && (
									<ToolsPanelItem
										hasValue={ () => !! catTaxonomyExclude }
										label={ __( 'Include Taxonomy', 'spectra-pro' ) }
										onDeselect={ () =>
											setAttributes( {
												catTaxonomyExclude: [],
											} )
										}
										isShownByDefault
									>
										<FormTokenField
											label={ __( 'Exclude Taxonomy', 'spectra-pro' ) }
											value={ mapTokenValuesToLabels( catTaxonomyExclude ) }
											suggestions={ catTaxonomyExcludeSuggestions }
											onChange={ ( values ) =>
												setAttributes( {
													catTaxonomyExclude: values.map( ( label ) => {
														const term = terms.find( ( t ) => t.label === label );
														return term ? term.value : label;
													} ),
												} )
											}
											__experimentalShowHowTo={ false }
											__experimentalExpandOnFocus
											__nextHasNoMarginBottom
											__next40pxDefaultSize
										/>
									</ToolsPanelItem>
								) }

								{ /* This ToolsPanelItem will reset when the following condition is met:
									- The "tagTaxonomyExclude" attribute is set to an empty array. Default: [].
								*/ }
								{ 'post_tag' === taxonomyType && (
									<ToolsPanelItem
										hasValue={ () => !! tagTaxonomyExclude }
										label={ __( 'Exclude Taxonomy', 'spectra-pro' ) }
										onDeselect={ () =>
											setAttributes( {
												tagTaxonomyExclude: [],
											} )
										}
										isShownByDefault
									>
										<FormTokenField
											label={ __( 'Exclude Taxonomy', 'spectra-pro' ) }
											value={ mapTokenValuesToLabels( tagTaxonomyExclude ) }
											suggestions={ tagTaxonomyExcludeSuggestions }
											onChange={ ( values ) =>
												setAttributes( {
													tagTaxonomyExclude: values.map( ( label ) => {
														const term = terms.find( ( t ) => t.label === label );
														return term ? term.value : label;
													} ),
												} )
											}
											__experimentalShowHowTo={ false }
											__experimentalExpandOnFocus
											__nextHasNoMarginBottom
											__next40pxDefaultSize
										/>
									</ToolsPanelItem>
								) }

								{ /* This ToolsPanelItem will reset when the following condition is met:
									- The "taxonomyExclude" attribute is set to an empty array. Default: [].
								*/ }
								{ ! [ 'category', 'post_tag' ].includes( taxonomyType ) && (
									<ToolsPanelItem
										hasValue={ () => !! catTaxonomyExclude }
										label={ __( 'Exclude Taxonomy', 'spectra-pro' ) }
										onDeselect={ () =>
											setAttributes( {
												catTaxonomyExclude: [],
											} )
										}
										isShownByDefault
									>
										<FormTokenField
											label={ __( 'Exclude Taxonomy', 'spectra-pro' ) }
											value={ mapTokenValuesToLabels( catTaxonomyExclude ) }
											suggestions={ catTaxonomyExcludeSuggestions }
											onChange={ ( values ) =>
												setAttributes( {
													catTaxonomyExclude: values.map( ( label ) => {
														const term = terms.find( ( t ) => t.label === label );
														return term ? term.value : label;
													} ),
												} )
											}
											__experimentalShowHowTo={ false }
											__experimentalExpandOnFocus
											__nextHasNoMarginBottom
											__next40pxDefaultSize
										/>
									</ToolsPanelItem>
								) }
							</>
						) }
					</>
				) }
			</ToolsPanel>
		</InspectorControls>
	);
} );

/**
 * The Editor settings.
 *
 * @param {Object} props The element props.
 * @since x.x.x
 * @return {Element} The rendered settings.
 */
const Settings = ( props ) => <BlockSettings { ...{ ...props } } />;

export default memo( Settings );
