/* eslint-env browser */
/* eslint-disable react/prop-types */

/**
 * AccordionItemsControl — the "Items" element-control for the AAE Accordion.
 *
 * Registered under the type id 'aae-items' (see ./index.js) and rendered by the
 * editing panel where the PHP side places an AAE_A_Items_Control. Mirrors the
 * Nested Slider's SlidesControl: a custom accordion list (not Elementor's
 * <Repeater> — see SlidesControl.jsx for why) whose rows are a LIVE PROJECTION
 * of the accordion's real <e-aae-a-accordion-item> children.
 *
 * Unlike slides (which live under a track), accordion items are DIRECT children
 * of the accordion, so the child list is read straight off the V1 container
 * model via the useListenTo pattern (same as NavItemsControl) instead of
 * useElementChildren, whose schema only matches descendants.
 *
 * Interactions:
 *   - Click a row  → expand it (rename field) AND open that item in the preview.
 *   - "+" (Add)    → append a new item; its Header/Content children and default
 *                    header styles are created by Elementor's default_children
 *                    pipeline + the accordion-header-styles bridge.
 *   - 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,
} 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-accordion-item';

/**
 * Model for a fresh accordion item. `elements: []` (empty, not undefined) lets
 * Elementor's onElementCreate() populate the default Header/Content children,
 * and keeps the delete command's deselectRecursive() from throwing.
 */
function buildItemModel( position ) {
	return {
		elType: ITEM_TYPE,
		editor_settings: { title: `Accordion Item ${ position }` },
		elements: [],
	};
}

/** Live projection of the accordion's direct item children. */
function useAccordionItems( accordionId ) {
	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' ),
		],
		() => {
			const children = getContainer( accordionId )?.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;
		},
		[ accordionId ]
	);
}

/**
 * Open an accordion item in the preview by clicking its header — same code
 * path a visitor takes, so the runtime's default-state/max-expanded rules and
 * the editor-only content reveal all behave consistently.
 */
function openItemInPreview( itemId ) {
	try {
		const previewWin = window.elementor?.$preview?.[ 0 ]?.contentWindow || null;
		if ( ! previewWin ) {
			return;
		}
		const itemNode = previewWin.document.querySelector( `[data-id="${ itemId }"]` );
		const header = itemNode?.querySelector?.( '.aae-accordion-header' );
		if ( header && ! itemNode.classList.contains( 'active' ) ) {
			header.click();
		}
	} catch ( _e ) {
		/* preview not ready — ignore */
	}
}

export function AccordionItemsControl( { label } ) {
	const { element } = useElement();
	const accordionId = element.id;

	const items = useAccordionItems( accordionId );

	const rows = ( items || [] ).map( ( item, index ) => ( {
		id: item.id,
		title: item.editorSettings?.title || `Accordion 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 ) );
		openItemInPreview( row.id );
	};

	const handleAdd = () => {
		const accordion = getContainer( accordionId );
		if ( ! accordion ) {
			return;
		}
		createElements( {
			title: 'Accordion Item',
			subtitle: 'Item added',
			elements: [
				{
					container: accordion,
					model: buildItemModel( rows.length + 1 ),
					options: { at: rows.length },
				},
			],
		} );
	};

	const handleDuplicate = ( row ) => {
		duplicateElements( {
			elementIds: [ row.id ],
			title: 'Accordion Item',
			subtitle: 'Item duplicated',
		} );
	};

	const handleRemove = ( row ) => {
		removeElements( {
			elementIds: [ row.id ],
			title: 'Accordion 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 accordion = getContainer( accordionId );
		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 accordion.
		if ( accordion && movedElement && movedElement.parent?.id === accordion.id ) {
			moveElements( {
				title: 'Accordion Item',
				subtitle: 'Item reordered',
				moves: [
					{
						element: movedElement,
						targetContainer: accordion,
						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
 * `useAccordionItems`'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>
	);
}
