/**
 * Select control inspired by jQuery ASM Select
 */

/**
 * WordPress provides React during the page load. This is for TypeScript type checking only.
 *
 * Importing React from @wordpress/element causes "Cannot read property 'createElement' of undefined"
 */
import React, { useEffect, useState } from 'react';
import { ReactSortable } from 'react-sortablejs';

import { Button, Modal } from '@wordpress/components';
import { cancelCircleFilled, tool } from '@wordpress/icons';
import { __ } from '@wordpress/i18n';

function SortableItem( {
	id,
	item,
	valueItem,
	onDelete,
	setMeta,
	itemOptions,
	itemOptionsModalTitle,
}: {
	id: string;
	item: ASMSelectControlItem;
	valueItem: ValueItem;
	// eslint-disable-next-line no-shadow
	onDelete: ( id: string ) => void;
	setMeta: ( meta: ASMSelectControlItem[ 'meta' ] ) => void;
	itemOptions?: ASMSelectControlItemOptions
	itemOptionsModalTitle?: string
} ) {
	const [ isOpen, setOpen ] = useState( false );
	const CustomOptions = item?.customOptions;
	const ItemOptions = itemOptions;

	return (
		<li className="gpeb-asm-select-control-item">
			<span className="gpeb-asm-select-control-item-label" title={ valueItem?.meta?.label || item?.label }>
				{ valueItem?.meta?.label || item?.label || __( 'Loading…', 'gp-entry-blocks' ) }
			</span>

			<span style={ { flex: 1 } } />

			{ ( item?.customOptions || ItemOptions ) ? (
				<Button
					icon={ tool }
					label={ __( 'Open Options', 'gp-entry-blocks' ) }
					onClick={ () => setOpen( true ) }
				/>
			) : undefined }

			{ isOpen && ( item?.customOptions || ItemOptions ) ? (
				<Modal
					title={ itemOptionsModalTitle ?? __( 'Options', 'gp-entry-blocks' ) }
					onRequestClose={ () => setOpen( false ) }
				>
					{ ItemOptions ? <ItemOptions item={ item } meta={ valueItem.meta } setMeta={ setMeta } /> : undefined }
					{ CustomOptions ? <CustomOptions item={ item } meta={ valueItem.meta } setMeta={ setMeta } /> : undefined }
				</Modal>
			) : undefined }

			<Button
				icon={ cancelCircleFilled }
				label={ __( 'Delete', 'gp-entry-blocks' ) }
				onClick={ () => onDelete( id ) }
			/>
		</li>
	);
}

export type ASMSelectControlItemOptions = React.FunctionComponent<{
	item: ASMSelectControlItem,
	meta: ASMSelectControlItem[ 'meta' ],
	// eslint-disable-next-line no-shadow
	setMeta: ( meta: ASMSelectControlItem[ 'meta' ] ) => void
}>;

export interface ASMSelectControlItem {
	type: string;
	label: string;
	optionLabel?: string;
	group?: string;
	customOptions?: ASMSelectControlItemOptions;
	allowMultiple?: boolean | string;
	labelRequired?: boolean;
	meta?: {
		[key: string]: any;
	};
	[key: string]: any;
}

type ValueItem = Omit<ASMSelectControlItem, 'label' | 'allowMultiple' | 'customOptions'> & {
	id: string;
};

type Items = ASMSelectControlItem[];
type ValueItems = ValueItem[];

export interface ASMSelectControlProps {
	items: Items;
	value: ValueItems;
	id: string;
	label: string;
	optionLabel?: string;
	placeholder: string;
	onChange: ( val: ValueItems ) => void;
	itemOptions?: ASMSelectControlItemOptions
	itemOptionsModalTitle?: string
}

export const ASMSelectControl = ( {
	items,
	onChange,
	placeholder,
	value,
	label,
	id,
	itemOptions,
	itemOptionsModalTitle,
}: ASMSelectControlProps ) => {
	// Ensure all items have an ID. It's easy to forget to add an ID when configuring block variations/templates.
	// This prevents an issue where deleting items causes all items without an ID to be deleted.
	useEffect( () => {
		for ( const valueItem of value ) {
			if ( typeof valueItem.id === 'undefined' ) {
				valueItem.id = Math.random().toString();
			}
		}

		onChange( value );
	}, [ value ] );

	// eslint-disable-next-line no-shadow, @typescript-eslint/no-shadow
	function onDelete( id: string ) {
		onChange( value.filter( ( valueItem ) => valueItem.id !== id ) );
	}

	const getItem = ( valueItem: ValueItem ): ASMSelectControlItem => {
		let item = items.find(
			( currentItem ) => valueItem.type === currentItem.type
		);

		if ( item && typeof item.allowMultiple === 'string' ) {
			item = items.find(
				( currentItem ) =>
					valueItem.type === currentItem.type &&
					valueItem.meta?.[ currentItem.allowMultiple as string ] ===
					currentItem.meta?.[
						currentItem.allowMultiple as string
					]
			);
		}

		return item;
	};

	const ItemOption = ( { item }: { item: ASMSelectControlItem } ) => {
		const { allowMultiple } = item;
		let hasExisting: boolean;

		if ( ! allowMultiple ) {
			hasExisting =
				value.findIndex(
					( searchItem ) => searchItem.type === item.type
				) !== -1;
		} else if ( typeof allowMultiple === 'string' ) {
			hasExisting =
				value.findIndex( ( searchItem ) => {
					return (
						searchItem.type === item.type &&
						searchItem.meta?.[ allowMultiple ] ===
						item.meta?.[ allowMultiple ]
					);
				} ) !== -1;
		}

		return (
			<option
				value={ item.type }
				key={ JSON.stringify( item ) }
				disabled={ hasExisting }
				data-meta={ JSON.stringify( item.meta ) }
			>
				{ item.optionLabel ?? item.label }
			</option>
		);
	};

	const OptGroup = (
		props: React.PropsWithChildren<{ label: string }>
	) => {
		return <optgroup label={ props.label }>{ props.children }</optgroup>;
	};

	const groupedItems: { [group: string]: Items } = items.reduce(
		( acc, curr ) => {
			const group = curr.group ?? 'ungrouped';

			if ( ! acc[ group ] ) {
				acc[ group ] = [];
			}

			acc[ group ].push( curr );

			return acc;
		},
		{}
	);

	const selectChange = ( event ) => {
		const option = event.target.options[ event.target.selectedIndex ];
		let meta;

		if ( option.dataset?.meta ) {
			meta = JSON.parse( option.dataset.meta );
		}

		if ( ! event.target.value ) {
			return;
		}

		onChange( [
			...value,
			{
				type: event.target.value as string,
				id: Math.random().toString(),
				meta,
			},
		] );
	};

	return (
		<>
			<div
				className="gpeb-asm-select-control"
			>
				<label style={ { marginBottom: 4 } } htmlFor={ id }>
					{ label }
				</label>

				<select
					value=""
					onChange={ selectChange }
					onBlur={ selectChange }
					id={ id }
				>
					<option value="" disabled={ true }>
						{ placeholder }
					</option>

					{ Object.entries( groupedItems ).map(
						( [ optgroup, groupItems ] ) => {
							const options = groupItems.map( ( item ) => {
								let key = item.type;

								if ( typeof item.allowMultiple === 'string' ) {
									key = `${ item.type }-${
										item.meta?.[ item.allowMultiple ]
									}`;
								}

								return <ItemOption key={ key } item={ item } />;
							} );

							if ( optgroup === 'ungrouped' ) {
								return options;
							}

							return (
								<OptGroup label={ optgroup } key={ optgroup }>
									{ options }
								</OptGroup>
							);
						}
					) }
				</select>
			</div>

			<ul>
				<ReactSortable list={ value } setList={ ( state ) => {
					onChange( state );
				} }>
					{ value.map( ( valueItem, index ) => (
						<SortableItem
							key={ JSON.stringify( { id: valueItem?.id, type: valueItem.type } ) }
							valueItem={ valueItem }
							item={ getItem( valueItem ) }
							itemOptions={ itemOptions }
							itemOptionsModalTitle={ itemOptionsModalTitle }
							setMeta={ ( meta: ASMSelectControlItem[ 'meta' ] ) => {
								const edited = [ ...value ];
								edited[ index ].meta = meta;

								onChange( edited );
							} }
							id={ valueItem.id }
							onDelete={ onDelete }
						/>
					) ) }
				</ReactSortable>
			</ul>
		</>
	);
};
