/**
 * Slide / block Elements list — click to edit; optional drag reorder (admin block editor).
 */
import { Fragment, useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { isElementVisible } from '../interpolate';
import {
	slideOrderDefaults,
	slideStaticElementKeys,
	styleableFieldsForSlideLayout,
} from './sliderElements';
import {
	effectiveElementsOrder,
	resolveOrderList,
} from './elementsOrder';
import { DropSlot, moveToSlot, slotFromItemPointer } from './dragReorder';

/**
 * Resolve display/content order for a slide layout.
 *
 * @param {unknown}  [order]
 * @param {string}   [layout]
 * @param {string}   [breakpoint]
 * @return {string[]}
 */
export function resolveSlideElementsOrder(
	order,
	layout = 'simple',
	breakpoint = 'desktop'
) {
	const defaults = slideOrderDefaults( layout );
	return effectiveElementsOrder( order, breakpoint, defaults );
}

/**
 * Sort styleable fields by the slide's elementsOrder (reorderable keys only).
 *
 * @param {Array<object>} styleable
 * @param {string}        layout
 * @param {unknown}       [order]
 * @param {string}        [breakpoint]
 * @return {Array<object>}
 */
export function orderedStyleableFieldsForSlide(
	styleable,
	layout,
	order,
	breakpoint = 'desktop'
) {
	const fields = styleableFieldsForSlideLayout( styleable, layout );
	const defaults = slideOrderDefaults( layout );
	const keys = resolveSlideElementsOrder( order, layout, breakpoint );
	const byKey = new Map( fields.map( ( field ) => [ field.key, field ] ) );
	const ordered = [];
	keys.forEach( ( key ) => {
		if ( ! defaults.includes( key ) ) {
			return;
		}
		const field = byKey.get( key );
		if ( field ) {
			ordered.push( field );
			byKey.delete( key );
		}
	} );
	defaults.forEach( ( key ) => {
		const field = byKey.get( key );
		if ( field ) {
			ordered.push( field );
			byKey.delete( key );
		}
	} );
	return ordered;
}

/**
 * Non-reorderable styleable fields for a slide layout (e.g. column Media).
 *
 * @param {Array<object>} styleable
 * @param {string}        layout
 * @return {Array<object>}
 */
export function staticStyleableFieldsForSlide( styleable, layout ) {
	const staticKeys = slideStaticElementKeys( layout );
	if ( ! staticKeys.length ) {
		return [];
	}
	const fields = styleableFieldsForSlideLayout( styleable, layout );
	const byKey = new Map( fields.map( ( field ) => [ field.key, field ] ) );
	return staticKeys.map( ( key ) => byKey.get( key ) ).filter( Boolean );
}

/**
 * @param {object} props
 * @param {object} props.field
 * @param {object} props.attributes
 * @param {string} props.breakpoint
 * @param {number} props.index
 * @param {boolean} props.selected
 * @param {Function} props.onClick
 * @param {boolean} [props.sortable]
 * @param {boolean} [props.isDragging]
 * @param {object} [props.dragHandlers]
 */
function ElementRow( {
	field,
	attributes,
	breakpoint,
	index,
	selected,
	onClick,
	sortable = false,
	isDragging = false,
	dragHandlers = null,
} ) {
	const visible = isElementVisible( field?.key, attributes, breakpoint );
	const bpLabel =
		breakpoint === 'tablet'
			? __( 'Tablet', 'joc-blockora-morph' )
			: breakpoint === 'mobile'
			? __( 'Mobile', 'joc-blockora-morph' )
			: __( 'Desktop', 'joc-blockora-morph' );

	return (
		<button
			type="button"
			draggable={ sortable }
			aria-current={ selected ? 'true' : undefined }
			className={ [
				'bm-gutenberg-element-btn',
				sortable ? 'bm-gutenberg-element-btn--sortable' : '',
				visible ? '' : 'is-element-hidden',
				isDragging ? 'is-dragging' : '',
				selected ? 'is-selected' : '',
			]
				.filter( Boolean )
				.join( ' ' ) }
			onClick={ onClick }
			{ ...( sortable && dragHandlers ? dragHandlers : {} ) }
		>
			<span
				className="bm-gutenberg-element-btn__index"
				aria-hidden="true"
			>
				{ index }
			</span>
			{ sortable ? (
				<span
					className="bm-gutenberg-element-btn__handle"
					aria-hidden="true"
					title={ __( 'Drag to reorder', 'joc-blockora-morph' ) }
				>
					<span className="dashicons dashicons-menu" />
				</span>
			) : null }
			<span className="bm-gutenberg-element-btn__label">
				{ field.label || field.key }
			</span>
			{ visible ? null : (
				<span className="bm-gutenberg-element-btn__badge">
					{ sprintf(
						/* translators: %s: Desktop, Tablet, or Mobile */
						__( 'Hidden · %s', 'joc-blockora-morph' ),
						bpLabel
					) }
				</span>
			) }
			<span className="bm-gutenberg-element-btn__action">
				{ __( 'Edit', 'joc-blockora-morph' ) }
				<span
					className="dashicons dashicons-arrow-right-alt2"
					aria-hidden="true"
				/>
			</span>
		</button>
	);
}

/**
 * @param {object}   props
 * @param {Array<object>} props.fields    Pre-ordered fields (optional)
 * @param {Array<object>} [props.styleable]
 * @param {object}   [props.slide]
 * @param {object}   props.attributes
 * @param {Function} props.onSelect
 * @param {Function} [props.onReorder]  (nextOrder: string[]) => void
 * @param {boolean}  [props.allowReorder] When false, list is select-only (Gutenberg / FE).
 * @param {string}   [props.listKey]
 * @param {string}   [props.breakpoint]
 * @param {unknown}  [props.elementsOrder]
 * @param {string[]} [props.orderDefaults]
 * @param {string[]} [props.staticKeys] Keys shown below the sortable list (not draggable).
 * @param {string|null} [props.selectedKey] Currently selected element key
 */
export default function SlideElementsList( {
	fields: fieldsProp,
	styleable,
	slide,
	attributes,
	onSelect,
	onReorder,
	allowReorder = true,
	listKey = 'slide',
	breakpoint = 'desktop',
	elementsOrder,
	orderDefaults,
	staticKeys: staticKeysProp,
	selectedKey = null,
} ) {
	const canReorder =
		allowReorder !== false && typeof onReorder === 'function';
	const layout = slide?.layout || 'simple';
	const isCollectionList = listKey === 'collection';
	const defaults =
		Array.isArray( orderDefaults ) && orderDefaults.length
			? orderDefaults
			: isCollectionList
				? ( Array.isArray( fieldsProp ) ? fieldsProp : [] )
						.map( ( field ) => field?.key )
						.filter( Boolean )
				: slideOrderDefaults( layout );
	const orderValue =
		elementsOrder !== undefined ? elementsOrder : slide?.elementsOrder;
	const pinnedStaticKeys = (
		Array.isArray( staticKeysProp ) ? staticKeysProp : []
	).filter( ( key ) => typeof key === 'string' && key );

	// Collections (cards, tabs, FAQ, …) pass pre-ordered styleable fields —
	// do not filter against hero/slide order defaults (drops title/description).
	const allFields = Array.isArray( fieldsProp )
		? isCollectionList
			? fieldsProp
			: fieldsProp.filter( ( field ) => defaults.includes( field.key ) )
		: orderedStyleableFieldsForSlide(
				styleable,
				layout,
				orderValue,
				breakpoint
		  );

	const sortableFields =
		pinnedStaticKeys.length > 0
			? allFields.filter(
					( field ) => ! pinnedStaticKeys.includes( field.key )
			  )
			: allFields;

	const slideStatic = slide
		? staticStyleableFieldsForSlide( styleable || fieldsProp || [], layout )
		: [];
	const collectionStatic =
		pinnedStaticKeys.length > 0
			? pinnedStaticKeys
					.map( ( key ) =>
						allFields.find( ( field ) => field.key === key )
					)
					.filter( Boolean )
			: [];
	const staticFields = [ ...slideStatic, ...collectionStatic ];

	const [ dragIndex, setDragIndex ] = useState( null );
	const [ dropSlot, setDropSlot ] = useState( null );
	const dragging = canReorder && dragIndex != null;

	const applySlot = ( from, slot ) => {
		if ( ! canReorder ) {
			return;
		}
		const keys = sortableFields.map( ( field ) => field.key );
		const next = moveToSlot( keys, from, slot );
		if ( next ) {
			// Keep pinned static keys (e.g. pricing button) at the end.
			const pinned = pinnedStaticKeys.filter(
				( key ) =>
					defaults.includes( key ) ||
					allFields.some( ( field ) => field.key === key )
			);
			onReorder?.(
				resolveOrderList( [ ...next, ...pinned ], defaults )
			);
		}
	};

	const readFrom = ( event ) => {
		const raw = event.dataTransfer.getData( 'text/plain' );
		return raw !== '' ? parseInt( raw, 10 ) : dragIndex;
	};

	const nodes = [];
	for ( let index = 0; index < sortableFields.length; index++ ) {
		const field = sortableFields[ index ];
		const isDragging = canReorder && dragIndex === index;

		if ( canReorder ) {
			nodes.push(
				<DropSlot
					key={ `${ listKey }-slot-${ index }` }
					slot={ index }
					active={ dragging && dropSlot === index }
					visible={ dragging }
					onHover={ setDropSlot }
					onDrop={ ( event, slot ) => {
						applySlot( readFrom( event ), slot );
						setDragIndex( null );
						setDropSlot( null );
					} }
				/>
			);
		}

		nodes.push(
			<ElementRow
				key={ `${ listKey }-${ field.key }` }
				field={ field }
				attributes={ attributes }
				breakpoint={ breakpoint }
				index={ index + 1 }
				selected={ selectedKey === field.key }
				onClick={ () => onSelect?.( field.key ) }
				sortable={ canReorder }
				isDragging={ isDragging }
				dragHandlers={
					canReorder
						? {
								onDragStart: ( event ) => {
									setDragIndex( index );
									setDropSlot( index );
									event.dataTransfer.effectAllowed = 'move';
									event.dataTransfer.setData(
										'text/plain',
										String( index )
									);
									if (
										event.currentTarget instanceof
										HTMLElement
									) {
										event.dataTransfer.setDragImage(
											event.currentTarget,
											16,
											16
										);
									}
								},
								onDragEnd: () => {
									setDragIndex( null );
									setDropSlot( null );
								},
								onDragOver: ( event ) => {
									event.preventDefault();
									event.dataTransfer.dropEffect = 'move';
									const slot = slotFromItemPointer(
										event,
										index
									);
									if ( dropSlot !== slot ) {
										setDropSlot( slot );
									}
								},
								onDrop: ( event ) => {
									event.preventDefault();
									event.stopPropagation();
									const slot = slotFromItemPointer(
										event,
										index
									);
									applySlot( readFrom( event ), slot );
									setDragIndex( null );
									setDropSlot( null );
								},
						  }
						: null
				}
			/>
		);
	}

	if ( canReorder ) {
		nodes.push(
			<DropSlot
				key={ `${ listKey }-slot-end` }
				slot={ sortableFields.length }
				active={ dragging && dropSlot === sortableFields.length }
				visible={ dragging }
				onHover={ setDropSlot }
				onDrop={ ( event, slot ) => {
					applySlot( readFrom( event ), slot );
					setDragIndex( null );
					setDropSlot( null );
				} }
			/>
		);
	}

	if ( staticFields.length ) {
		nodes.push(
			<div
				key={ `${ listKey }-static` }
				className="bm-gutenberg-elements__list-static"
			>
				{ staticFields.map( ( field, extraIndex ) => (
					<ElementRow
						key={ `${ listKey }-static-${ field.key }` }
						field={ field }
						attributes={ attributes }
						breakpoint={ breakpoint }
						index={ sortableFields.length + extraIndex + 1 }
						selected={ selectedKey === field.key }
						onClick={ () => onSelect?.( field.key ) }
						sortable={ false }
					/>
				) ) }
			</div>
		);
	}

	return <Fragment>{ nodes }</Fragment>;
}
