/* eslint-env browser */
/* eslint-disable react/prop-types */

/**
 * IconListItemsControl — the "Items" element-control for the AAE Icon List.
 *
 * Registered under the type id 'aae-icon-list-items' (see ./index.js) and
 * rendered by the editing panel where the PHP side places an
 * AAE_A_Icon_List_Items_Control. Mirrors the Social Share's
 * SocialShareItemsControl: a custom list (not Elementor's <Repeater>) whose
 * rows are a LIVE PROJECTION of the icon list's real <e-aae-a-icon-list-item>
 * children — there is no separate repeater data to keep in sync; the list is
 * read straight off the V1 container model via the useListenTo pattern.
 *
 * Interactions:
 *   - Click a row  → expand it (rename field).
 *   - "+" (Add)    → append a new item; its icon/label children are created
 *                    by Elementor's default_children pipeline.
 *   - Duplicate    → clone that item (styles/ids regenerated by the editor).
 *   - Remove (×)   → delete that item (hidden when only one remains).
 *   - Drag a row   → reorder items (HTML5 native drag).
 */

import * as React from 'react';
import {
	createElements,
	duplicateElements,
	getContainer,
	moveElements,
	removeElements,
	updateElementEditorSettings,
	useElementEditorSettings,
} from '@elementor/editor-elements';
import {
	__privateUseListenTo as useListenTo,
	commandEndEvent,
	v1ReadyEvent,
	windowEvent,
} from '@elementor/editor-v1-adapters';
import { useElement } from '@elementor/editor-editing-panel';
import {
	Box,
	Collapse,
	IconButton,
	Stack,
	TextField,
	Tooltip,
	Typography,
} from '@elementor/ui';

const ITEM_TYPE = 'e-aae-a-icon-list-item';

/**
 * Model for a fresh icon-list item. `elements: []` (empty, not undefined)
 * lets Elementor's onElementCreate() populate the default icon/label children
 * via AAE_A_Icon_List_Item::define_default_children().
 */
function buildItemModel( position ) {
	return {
		elType: ITEM_TYPE,
		editor_settings: { title: `Item ${ position }` },
		elements: [],
	};
}

/** Live projection of the icon list's direct item children. */
function useIconListItems( iconListId ) {
	const cacheRef = React.useRef( { signature: null, value: [] } );

	return useListenTo(
		[
			v1ReadyEvent(),
			commandEndEvent( 'document/elements/create' ),
			commandEndEvent( 'document/elements/delete' ),
			commandEndEvent( 'document/elements/update' ),
			commandEndEvent( 'document/elements/settings' ),
			commandEndEvent( 'document/elements/set-settings' ),
			commandEndEvent( 'document/elements/duplicate' ),
			commandEndEvent( 'document/elements/move' ),
			// updateElementEditorSettings() (the rename field below) only does
			// a Backbone model.set('editor_settings', …) — it never runs a
			// document/elements/* command, so without this the row label here
			// goes stale after a rename until add/remove/reorder forces a
			// recompute.
			windowEvent( 'elementor/element/update_editor_settings' ),
		],
		() => {
			const children = getContainer( iconListId )?.model?.get?.( 'elements' );

			if ( ! children ) {
				if ( cacheRef.current.signature !== '' ) {
					cacheRef.current = { signature: '', value: [] };
				}
				return cacheRef.current.value;
			}

			const next = [];
			const signatureParts = [];
			children.each( ( model ) => {
				if ( ( model.get( 'widgetType' ) || model.get( 'elType' ) ) !== ITEM_TYPE ) {
					return;
				}
				const id = model.get( 'id' );
				const editorSettings = model.get( 'editor_settings' ) || {};
				next.push( { id, editorSettings } );
				signatureParts.push( `${ id }:${ editorSettings.title || '' }` );
			} );

			const signature = signatureParts.join( '|' );
			if ( cacheRef.current.signature !== signature ) {
				cacheRef.current = { signature, value: next };
			}
			return cacheRef.current.value;
		},
		[ iconListId ]
	);
}

export function IconListItemsControl( { label } ) {
	const { element } = useElement();
	const iconListId = element.id;

	const items = useIconListItems( iconListId );

	const rows = ( items || [] ).map( ( item, index ) => ( {
		id: item.id,
		title: item.editorSettings?.title || `Item ${ index + 1 }`,
		index,
	} ) );

	const [ expandedId, setExpandedId ] = React.useState( null );
	const [ dragFrom, setDragFrom ] = React.useState( null );
	const [ dragOver, setDragOver ] = React.useState( null );

	const handleRowClick = ( row ) => {
		setExpandedId( ( cur ) => ( cur === row.id ? null : row.id ) );
	};

	const handleAdd = () => {
		const iconList = getContainer( iconListId );
		if ( ! iconList ) {
			return;
		}
		createElements( {
			title: 'Icon List Item',
			subtitle: 'Item added',
			elements: [
				{
					container: iconList,
					model: buildItemModel( rows.length + 1 ),
					options: { at: rows.length },
				},
			],
		} );
	};

	const handleDuplicate = ( row ) => {
		duplicateElements( {
			elementIds: [ row.id ],
			title: 'Icon List Item',
			subtitle: 'Item duplicated',
		} );
	};

	const handleRemove = ( row ) => {
		removeElements( {
			elementIds: [ row.id ],
			title: 'Icon List Item',
			subtitle: 'Item removed',
		} );
		if ( expandedId === row.id ) {
			setExpandedId( null );
		}
	};

	const handleDrop = ( toIndex ) => {
		const from = dragFrom;
		setDragFrom( null );
		setDragOver( null );
		if ( from == null || from === toIndex ) {
			return;
		}
		const iconList = getContainer( iconListId );
		const movedId = rows[ from ]?.id;
		const movedElement = movedId ? getContainer( movedId ) : null;
		// Guard against a stale index (concurrent create/delete between render
		// and drop): only move if the item is still a child of this icon list.
		if ( iconList && movedElement && movedElement.parent?.id === iconList.id ) {
			moveElements( {
				title: 'Icon List Item',
				subtitle: 'Item reordered',
				moves: [
					{
						element: movedElement,
						targetContainer: iconList,
						options: { at: toIndex },
					},
				],
			} );
		}
	};

	return (
		<Stack gap={ 1 }>
			<Stack direction="row" alignItems="center" justifyContent="space-between">
				<Typography variant="caption" sx={ { fontWeight: 500, color: 'text.secondary' } }>
					{ label }
				</Typography>
				<Tooltip title="Add Item">
					<IconButton size="tiny" onClick={ handleAdd } aria-label="Add Item">
						<span style={ { fontSize: 16, lineHeight: 1 } }>+</span>
					</IconButton>
				</Tooltip>
			</Stack>

			<Stack gap={ 0.5 }>
				{ rows.map( ( row ) => {
					const isExpanded = expandedId === row.id;
					const isDragOver = dragOver === row.index && dragFrom !== row.index;
					return (
						<Box
							key={ row.id }
							draggable
							onDragStart={ () => setDragFrom( row.index ) }
							onDragOver={ ( e ) => {
								e.preventDefault();
								setDragOver( row.index );
							} }
							onDrop={ () => handleDrop( row.index ) }
							onDragEnd={ () => {
								setDragFrom( null );
								setDragOver( null );
							} }
							sx={ {
								border: '1px solid',
								borderColor: isDragOver ? 'primary.main' : 'divider',
								borderRadius: 1,
								overflow: 'hidden',
								bgcolor: 'background.default',
							} }
						>
							<Stack
								direction="row"
								alignItems="center"
								gap={ 0.5 }
								onClick={ () => handleRowClick( row ) }
								sx={ {
									px: 1,
									py: 0.75,
									cursor: 'pointer',
									userSelect: 'none',
									'&:hover': { bgcolor: 'action.hover' },
								} }
							>
								<Box
									component="span"
									sx={ { color: 'text.tertiary', cursor: 'grab', fontSize: 14, lineHeight: 1 } }
									aria-hidden
								>
									⠿
								</Box>
								<Typography variant="body2" sx={ { flex: 1, fontWeight: isExpanded ? 600 : 400 } }>
									<RowTitle elementId={ row.id } fallback={ row.title } />
								</Typography>
								<Tooltip title="Duplicate">
									<IconButton
										size="tiny"
										aria-label="Duplicate item"
										onClick={ ( e ) => {
											e.stopPropagation();
											handleDuplicate( row );
										} }
									>
										<span style={ { fontSize: 13, lineHeight: 1 } }>⧉</span>
									</IconButton>
								</Tooltip>
								{ rows.length > 1 && (
									<Tooltip title="Remove">
										<IconButton
											size="tiny"
											aria-label="Remove item"
											onClick={ ( e ) => {
												e.stopPropagation();
												handleRemove( row );
											} }
										>
											<span style={ { fontSize: 14, lineHeight: 1 } }>×</span>
										</IconButton>
									</Tooltip>
								) }
							</Stack>

							<Collapse in={ isExpanded } unmountOnExit>
								<Box sx={ { px: 1.5, py: 1.5, borderTop: '1px solid', borderColor: 'divider' } }>
									<ItemNameField elementId={ row.id } />
								</Box>
							</Collapse>
						</Box>
					);
				} ) }
			</Stack>
		</Stack>
	);
}

/**
 * Row label, read live off the element's own editor_settings rather than off
 * `useIconListItems`'s cached projection. The projection only recomputes on a
 * fixed set of document/elements/* commands and a window event that Elementor
 * only dispatches when some OTHER view (e.g. the Navigator row for this
 * element) happens to be listening for the model change — so typing in the
 * rename field below updated the field itself (which already reads this same
 * live hook) but left the row title stuck until something else forced a
 * re-render. Subscribing here directly sidesteps that dependency entirely.
 */
function RowTitle( { elementId, fallback } ) {
	const editorSettings = useElementEditorSettings( elementId );
	return editorSettings?.title || fallback;
}

function ItemNameField( { elementId } ) {
	const editorSettings = useElementEditorSettings( elementId );
	const label = editorSettings?.title ?? '';

	return (
		<Stack gap={ 1 }>
			<Typography variant="caption" sx={ { fontWeight: 500, color: 'text.secondary' } }>
				{ 'Item name' }
			</Typography>
			<TextField
				size="tiny"
				value={ label }
				onChange={ ( { target } ) =>
					updateElementEditorSettings( {
						elementId,
						settings: { title: target.value },
					} )
				}
			/>
		</Stack>
	);
}
