

import {
	useState,
	useEffect,
	useMemo,
	useRef
} from '@wordpress/element';

import { useControlSettingSet } from '../_hooks';


import {
	DndContext,
	closestCenter,
	KeyboardSensor,
	PointerSensor,
	useSensor,
	useSensors,
	type DragEndEvent,
} from '@dnd-kit/core';

import {
	arrayMove,
	SortableContext,
	sortableKeyboardCoordinates,
	verticalListSortingStrategy,
} from '@dnd-kit/sortable';

// 要素内のみで制限するようにする
import { restrictToParentElement } from "@dnd-kit/modifiers";

import SortableItem from './SortableItem';

import { __ } from '@wordpress/i18n';

// locked_items DSL の型定義
type LockedItemSpec =
	| string
	| { item: string; depends_on: string; value: string | string[] };

interface Choice {
	value: string;
	label: string;
	subControls?: string[];
}

type Props = {
	values: SortItems,
	title: string,
	description?: string,
	onChange: (newValues: string[]) => void,
	disabledSort?: string[],
	lockedItems?: LockedItemSpec[],
	choices?: Choice[],
}

/**
 * rawLockedItems から現在値を評価し、ロック対象 ID の Set を返す.
 * wp.customize が未定義の場合でも optional chaining で安全に処理する.
 */
function computeLockedSet( specs: LockedItemSpec[] ): Set<string> {
	const set = new Set<string>();

	specs.forEach( ( spec ) => {
		if ( typeof spec === 'string' ) {
			set.add( spec );
			return;
		}

		const setting = ( window as any ).wp?.customize?.( spec.depends_on );
		if ( ! setting ) {
			return;
		}

		const current = setting.get();
		const matched = Array.isArray( spec.value )
			? spec.value.includes( current )
			: current === spec.value;

		if ( matched ) {
			set.add( spec.item );
		}
	} );

	return set;
}

export default function ApuraSortable({ values, title, description, onChange, disabledSort, lockedItems, choices }: Props) {
	const rawLocked = lockedItems ?? [];

	const [sortItems, setSortItems] = useState<SortItems>(values);
	const [isInitialized, setIsInitialized] = useState(false);
	const [lockedSet, setLockedSet] = useState<Set<string>>( () => computeLockedSet( rawLocked ) );
	const initTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

	// センサーの初期化を遅延実行
	const sensors = useSensors(
		useSensor(PointerSensor, {
			activationConstraint: {
				distance: 8, // 8px移動してからドラッグ開始
			},
		}),
		useSensor(KeyboardSensor, {
			coordinateGetter: sortableKeyboardCoordinates,
		})
	);

	// コンポーネントマウント後の遅延初期化
	useEffect(() => {
		// iframe環境での初期化を少し遅延させる
		initTimeoutRef.current = setTimeout(() => {
			setIsInitialized(true);
		}, 100);

		return () => {
			if (initTimeoutRef.current) {
				clearTimeout(initTimeoutRef.current);
			}
		};
	}, []);

	// wp.customize の設定変化を購読して lockedSet を動的更新する.
	// cleanup で必ず unbind し、メモリリークを防ぐ.
	useEffect( () => {
		const unsubscribes: Array<() => void> = [];

		rawLocked.forEach( ( spec ) => {
			if ( typeof spec === 'string' ) {
				return;
			}

			const setting = ( window as any ).wp?.customize?.( spec.depends_on );
			if ( ! setting ) {
				return;
			}

			const handler = () => {
				setLockedSet( computeLockedSet( rawLocked ) );
			};

			setting.bind( handler );
			unsubscribes.push( () => setting.unbind( handler ) );
		} );

		return () => {
			unsubscribes.forEach( ( fn ) => fn() );
		};
		// rawLocked は PHP params 由来でレンダー間で安定しているため deps から除外する
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [] );

	// valuesが変更された時の処理
	useEffect(() => {
		setSortItems(values);
	}, [values]);

	// lockedSet または values が変化したとき表示用 items を正規化する.
	// ロック上塗り（isActive 強制 true + locked フラグ）は UI 表示に限定し、
	// 実際の保存値は sortItems 側のユーザー設定を維持する.
	const normalizedItems = useMemo( () => {
		// ベース items: sortItems を上書きコピー
		const result = sortItems.map( ( item ) => {
			if ( lockedSet.has( item.id ) ) {
				return { ...item, isActive: true, locked: true };
			}
			return { ...item, locked: false };
		} );

		// 保存値配列に存在しないが locked 対象の項目を UI 上だけ末尾に追加
		lockedSet.forEach( ( lockedId ) => {
			const exists = result.find( ( i ) => i.id === lockedId );
			if ( exists ) {
				return;
			}

			// choices prop または params.choices から該当エントリを探す
			const choiceList: Choice[] = choices ?? [];
			const choice = choiceList.find( ( c ) => c.value === lockedId );

			if ( choice ) {
				result.push( {
					id:          choice.value,
					label:       choice.label,
					value:       choice.value,
					isActive:    true,
					locked:      true,
					subControls: choice.subControls ?? null,
				} );
			}
		} );

		return result;
	// lockedSet は設定購読時に新しい Set として更新されるため依存に入れる.
	// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [ sortItems, lockedSet ] );

	function handleDragEnd(event: DragEndEvent) {
		const {active, over} = event;

		if (active.id !== over?.id) {
			setSortItems((items) => {
				const oldIndex = items.findIndex(
					(item) => item.id === active.id
				);
				const newIndex = items.findIndex(
					(item) => item.id === over.id
				);

				if ( oldIndex < 0 || newIndex < 0 ) {
					return items;
				}

				return arrayMove(items, oldIndex, newIndex);
			});
		}
	}

	function handleItemActive(id:string) {
		// locked 項目はトグル不可（念のため二重防衛）
		if ( lockedSet.has( id ) ) {
			return;
		}

		const newValues = sortItems.map((_item) =>{
			if (_item.id === id) {
				return {
					..._item,
					isActive: !_item.isActive,
				};
			} else {
				 return _item;
			}
		});
		setSortItems(newValues);
	}

	// sortItems が変化したとき onChange で保存値を更新する.
	// normalizedItems は Cover 用の強制表示を含むため、保存値には使わない.
	useEffect(() => {
		const result = sortItems.flatMap(obj => obj.isActive ? obj.id : []);
		onChange(result);
	}, [sortItems, onChange]);



	return (
		<div className='apura-sortable-control'>
			<div className='apura-control-title'>{title}</div>
			{description && <div className='customize-control-description'>{description}</div>}
			{isInitialized ? (
				<DndContext
					autoScroll={false}
					sensors={sensors}
					collisionDetection={closestCenter}
					onDragEnd={handleDragEnd}
					modifiers={[restrictToParentElement]}
				>
					<SortableContext
						items={normalizedItems}
						strategy={verticalListSortingStrategy}
					>
						{normalizedItems.map(item => {
							return (
							<SortableItem
								key={item.id}
								id={item.id}
								label={item.label}
								value={item.value}
								sortLabel={ __( 'Sort by drag and drop', 'apura' )}
								isActive={item.isActive}
								disabled={disabledSort?.includes(item.id)}
								locked={item.locked}
								subControls={item.subControls}
								onActive={() => handleItemActive(item.id)}
							/>
							)
						})}
					</SortableContext>
				</DndContext>
			) : (
				// 初期化中の表示（DndContextなし）
				<div className="apura-sortable-loading">
					{normalizedItems.map(item => (
						<SortableItem
							key={item.id}
							id={item.id}
							label={item.label}
							value={item.value}
							sortLabel={ __( 'Sort by drag and drop', 'apura' )}
							isActive={item.isActive}
							disabled={true} // 初期化中はドラッグ無効
							locked={item.locked}
							subControls={item.subControls}
							onActive={() => handleItemActive(item.id)}
						/>
					))}
				</div>
			)}
		</div>
	);
}
