import { Fragment, JSXElementConstructor, ReactElement, ReactNode, useEffect, useState } from 'react';

import { Field, FieldProps, Focusable, GamepadButton } from '../components';

/**
 * A ReorderableList entry of type <T>.
 * @param label The name of this entry in the list.
 * @param data Optional data to connect to this entry.
 * @param position The position of this entry in the list.
 */
export type ReorderableEntry<T> = {
	label: ReactNode;
	data?: T;
	position: number;
};

/**
 * Properties for a ReorderableList component of type <T>.
 *
 * @param animate If the list should animate. @default true
 */
export type ReorderableListProps<T> = {
	entries: ReorderableEntry<T>[];
	onSave: (entries: ReorderableEntry<T>[]) => void;
	interactables?: JSXElementConstructor<{ entry: ReorderableEntry<T> }>;
	fieldProps?: FieldProps;
	animate?: boolean;
};

/**
 * A component for creating reorderable lists.
 *
 * See an example implementation {@linkplain https://github.com/Tormak9970/Component-Testing-Plugin/blob/main/src/testing-window/ReorderableListTest.tsx here}.
 */
export function ReorderableList<T>(props: ReorderableListProps<T>) {
	if (props.animate === undefined) props.animate = true;
	const [entryList, setEntryList] = useState<ReorderableEntry<T>[]>([...props.entries].sort((a: ReorderableEntry<T>, b: ReorderableEntry<T>) => a.position - b.position));
	const [reorderEnabled, setReorderEnabled] = useState<boolean>(false);

	useEffect(() => {
		setEntryList([...props.entries].sort((a: ReorderableEntry<T>, b: ReorderableEntry<T>) => a.position - b.position));
	}, [props.entries]);

	function toggleReorderEnabled(): void {
		let newReorderValue = !reorderEnabled;
		setReorderEnabled(newReorderValue);

		if (!newReorderValue) {
			props.onSave(entryList);
		}
	}

	function saveOnBackout(e: Event) {
		const event = e as CustomEvent;
		if (event.detail.button == GamepadButton.CANCEL && reorderEnabled) {
			setReorderEnabled(!reorderEnabled);
			props.onSave(entryList);
		}
	}

	return (
		<Fragment>
			<div
				style={{
					width: 'inherit',
					height: 'inherit',
					flex: '1 1 1px',
					scrollPadding: '48px 0px',
					display: 'flex',
					flexDirection: 'column',
					justifyContent: 'flex-start',
					alignContent: 'stretch',
				}}
			>
				<Focusable
					onSecondaryButton={toggleReorderEnabled}
					onSecondaryActionDescription={reorderEnabled ? 'Save Order' : 'Reorder'}
					onClick={toggleReorderEnabled}
					onButtonDown={saveOnBackout}
				>
					{entryList.map((entry: ReorderableEntry<T>) => (
						<ReorderableItem
							animate={props.animate!}
							listData={entryList}
							entryData={entry}
							reorderEntryFunc={setEntryList}
							reorderEnabled={reorderEnabled}
							fieldProps={props.fieldProps}
						>
							{props.interactables ? <props.interactables entry={entry} /> : null}
						</ReorderableItem>
					))}
				</Focusable>
			</div>
		</Fragment>
	);
}

/**
 * Properties for a ReorderableItem component of type <T>
 */
export type ReorderableListEntryProps<T> = {
	fieldProps?: FieldProps;
	listData: ReorderableEntry<T>[];
	entryData: ReorderableEntry<T>;
	reorderEntryFunc: CallableFunction;
	reorderEnabled: boolean;
	animate: boolean;
	children: ReactElement | null;
};

function ReorderableItem<T>(props: ReorderableListEntryProps<T>) {
	const [isSelected, _setIsSelected] = useState<boolean>(false);
	const [isSelectedLastFrame, setIsSelectedLastFrame] = useState<boolean>(false);
	const listEntries = props.listData;

	function onReorder(e: Event): void {
		if (!props.reorderEnabled) return;

		const event = e as CustomEvent;
		const currentIdx = listEntries.findIndex((entryData: ReorderableEntry<T>) => entryData === props.entryData);
		const currentIdxValue = listEntries[currentIdx];
		if (currentIdx < 0) return;

		let targetPosition: number = -1;
		if (event.detail.button == GamepadButton.DIR_DOWN) {
			targetPosition = currentIdxValue.position + 1;
		} else if (event.detail.button == GamepadButton.DIR_UP) {
			targetPosition = currentIdxValue.position - 1;
		}

		if (targetPosition >= listEntries.length || targetPosition < 0) return;

		let otherToUpdate = listEntries.find((entryData: ReorderableEntry<T>) => entryData.position === targetPosition);
		if (!otherToUpdate) return;

		let currentPosition = currentIdxValue.position;

		currentIdxValue.position = otherToUpdate.position;
		otherToUpdate.position = currentPosition;

		props.reorderEntryFunc([...listEntries].sort((a: ReorderableEntry<T>, b: ReorderableEntry<T>) => a.position - b.position));
	}

	async function setIsSelected(val: boolean) {
		_setIsSelected(val);
		// Wait 3 frames, then set. I have no idea why, but if you dont wait long enough it doesn't work.
		for (let i = 0; i < 3; i++) await new Promise((res) => requestAnimationFrame(res));
		setIsSelectedLastFrame(val);
	}

	return (
		<div
			style={
				props.animate
					? {
							transition: isSelected || isSelectedLastFrame ? '' : 'transform 0.3s cubic-bezier(0.25, 1, 0.5, 1), opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1)', // easeOutQuart https://easings.net/#easeOutQuart
							transform: !props.reorderEnabled || isSelected ? 'scale(1)' : 'scale(0.9)',
							opacity: !props.reorderEnabled || isSelected ? 1 : 0.7,
						}
					: {}
			}
		>
			<Field
				label={props.entryData.label}
				{...props.fieldProps}
				focusable={!props.children}
				onButtonDown={onReorder}
				onGamepadBlur={() => setIsSelected(false)}
				onGamepadFocus={() => setIsSelected(true)}
			>
				<Focusable style={{ display: 'flex', width: '100%', position: 'relative' }}>{props.children}</Focusable>
			</Field>
		</div>
	);
}
