import { useSelect } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { useAsync } from 'react-async-hook';
import {
	useMemo,
	useRef,
	useEffect,
	useContext,
	createContext,
} from '@wordpress/element';
import { adminAjaxFetchMemoized } from './helpers';
import { GPEntriesBlockAttributes } from './blocks/entries';

export function useFormId(): number | undefined {
	return useSelect((select: any) => {
		const {
			getSelectedBlock,
			getBlockParentsByBlockName,
			getBlocksByClientId,
		} = select(blockEditorStore);

		const clientId = getSelectedBlock()?.clientId;

		if (!clientId) {
			return undefined;
		}

		const parentIds = getBlockParentsByBlockName(clientId, [
			'gp-entry-blocks/entries',
		]);
		const parents = getBlocksByClientId(parentIds);

		if (!parents.length) {
			return undefined;
		}

		// Get last element in parents array
		const parent = parents[parents.length - 1];

		return parent.attributes.formId;
	}, []);
}

/**
 * TODO, for now we just use the form ID and then add in the form fields using getFormFields().
 */
export function useForm(): { id: number; fields: any[] } {
	const formId = useFormId();

	return useSelect(
		(select: any) => {
			const fields = select('gp-entry-blocks').getFormFields(formId);

			return {
				id: formId,
				fields,
			};
		},
		[formId]
	);
}

export function useSelectedBlock() {
	return useSelect((select) => {
		return select(blockEditorStore).getSelectedBlock();
	}, []);
}

export function useMultiEntryBlockCount(block?): number {
	const entriesBlock = useEntriesBlock(block);

	return entriesBlock?.innerBlocks?.filter((innerBlock) =>
		[
			'gp-entry-blocks/entries-index',
			'gp-entry-blocks/entries-table',
			'gp-entry-blocks/entries-loop',
			'gp-entry-blocks/pagination',
		].includes(innerBlock.name)
	).length;
}

export function useDefaultView(): 'entries' | 'view' | 'edit' {
	const viewEntryBlock = useViewEntryBlock();
	const editEntryBlock = useEditEntryBlock();
	const multiEntryBlockCount = useMultiEntryBlockCount();

	if (multiEntryBlockCount === 0) {
		if (viewEntryBlock) {
			return 'view';
		}

		if (editEntryBlock) {
			return 'edit';
		}
	}

	return 'entries';
}

export function useViewDisplay(view: string): 'none' | undefined {
	const currentView = useCurrentView();

	return currentView === view || currentView === 'entries'
		? undefined
		: 'none';
}

export const ViewContext = createContext([]);

export function useCurrentView(): 'entries' | 'view' | 'edit' | undefined {
	const [view, setView, rootClientId] = useContext(ViewContext);
	const selectedBlock = useSelectedBlock();
	const entriesBlock = useEntriesBlock();
	const viewBlock = useViewEntryBlock();
	const editBlock = useEditEntryBlock();
	const defaultView = useDefaultView();
	const getBlockParents = useSelect(
		(select) => select(blockEditorStore).getBlockParents,
		[]
	);
	const getBlock = useSelect(
		(select) => select(blockEditorStore).getBlock,
		[]
	);

	// When the selectedBlock changes, update the view.
	useEffect(() => {
		if (!selectedBlock || !setView) {
			return;
		}

		// If the selected block is not or is not inside the entries block, the behavior differs.
		const parents = [...getBlockParents(selectedBlock.clientId)].reverse();
		let shouldSetView = false;

		let entriesBlockParentCount = 0;

		for (const parentBlockClientId of parents) {
			const parentBlock = getBlock(parentBlockClientId);

			if (parentBlock.name === 'gp-entry-blocks/entries') {
				entriesBlockParentCount++;
			}
		}

		for (const parentBlockClientId of parents) {
			const parentBlock = getBlock(parentBlockClientId);

			// Do not set the view if we're literally selecting a nested entry block
			if (
				selectedBlock.name === 'gp-entry-blocks/entries' &&
				entriesBlockParentCount >= 1
			) {
				return;
			}

			if (
				rootClientId === selectedBlock.clientId ||
				rootClientId === parentBlockClientId
			) {
				shouldSetView = true;
				break;
			}

			// If this block has more than one Entries parent/ancestor, there is a top spinning we need to return
			// out so we don't change the view in the ancestor entries block in case the entries block is nested inside
			// the Edit/View view.
			if (
				entriesBlockParentCount > 1 &&
				parentBlock.name === 'gp-entry-blocks/entries'
			) {
				return;
			}
		}

		// If we're changing completely outside of the context of the entries block, set it back to the
		// default view.
		if (!shouldSetView) {
			setView(defaultView);
			return;
		}

		if (
			selectedBlock.clientId === viewBlock?.clientId ||
			parents.includes(viewBlock?.clientId)
		) {
			setView('view');
			return;
		}

		if (
			selectedBlock.clientId === editBlock?.clientId ||
			parents.includes(editBlock?.clientId)
		) {
			setView('edit');
			return;
		}

		setView(defaultView);
	}, [
		selectedBlock,
		setView,
		getBlock,
		entriesBlock,
		rootClientId,
		getBlockParents,
		defaultView,
		editBlock,
		viewBlock,
	]);

	return view;
}

export function useEntriesBlock(block?) {
	const selectedBlock = useSelectedBlock();

	if (!block) {
		block = selectedBlock;
	}

	return useSelect(
		(select) => {
			const { getBlockParentsByBlockName, getBlock } =
				select(blockEditorStore);

			if (!block) {
				return undefined;
			}

			if (block.name === 'gp-entry-blocks/entries') {
				return getBlock(block.clientId);
			}

			const entriesBlockClientId = getBlockParentsByBlockName(
				block?.clientId,
				'gp-entry-blocks/entries'
			)?.slice(-1)[0];

			if (!entriesBlockClientId) {
				return undefined;
			}

			return getBlock(entriesBlockClientId);
		},
		[block]
	);
}

export function useEntriesBlocks(): BlockProps<GPEntriesBlockAttributes>[] {
	return useSelect((select: any) => {
		const { getBlocks } = select(blockEditorStore);

		const entriesBlocks: BlockProps<GPEntriesBlockAttributes>[] = [];

		for (const block of getBlocks()) {
			if (block.name !== 'gp-entry-blocks/entries') {
				continue;
			}

			entriesBlocks.push(block);
		}

		return entriesBlocks;
	}, []);
}

export function useEntriesIndexBlock(block?) {
	const entriesBlock = useEntriesBlock(block);

	return entriesBlock?.innerBlocks?.find(
		(innerBlock) => innerBlock.name === 'gp-entry-blocks/entries-index'
	);
}

export function useViewEntryBlock(block?) {
	const entriesBlock = useEntriesBlock(block);

	return entriesBlock?.innerBlocks?.find(
		(innerBlock) => innerBlock.name === 'gp-entry-blocks/view-entry'
	);
}

export function useEditEntryBlock(block?) {
	const entriesBlock = useEntriesBlock(block);

	return entriesBlock?.innerBlocks?.find(
		(innerBlock) => innerBlock.name === 'gp-entry-blocks/edit-entry'
	);
}

export function useBlockAJAXPreview<B = any>(
	block: BlockProps<B>,
	content?: string
) {
	/* https://github.com/slorber/react-async-hook/issues/31#issuecomment-662883815 */
	const params = useMemo(
		() => ({
			action: 'gpeb_block_preview',
			block: JSON.stringify(block),
			content,
		}),
		[JSON.stringify(block), content]
	); // JSON.stringify() here to avoid unnecessary re-renders

	const response = useAsync<{ html: string }>(
		adminAjaxFetchMemoized,
		[params],
		{
			setLoading: (state) => ({ ...state, loading: true }),
		}
	);

	if (!response.result) {
		return null;
	}

	return response.result.html;
}

export function usePreventInnerEventsRef(innerContents: React.ReactNode) {
	const ref = useRef(null);

	/**
	 * Disable onClick of all elements inside the ref.
	 */
	useEffect(() => {
		if (!ref.current) {
			return;
		}

		for (const childEl of ref.current.querySelectorAll('*')) {
			childEl.onclick = (event) => event.preventDefault();
		}
	}, [ref, innerContents]);

	return ref;
}
