import { Point } from '@toeverything/utils';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useEditor } from './Contexts';
import {
    AsyncBlock,
    BlockEditor,
    SelectEventTypes,
    SelectionInfo,
    SelectionSettingsMap,
} from './editor';

function useRequestReRender() {
    const [, setUpdateCounter] = useState(0);
    const animationFrameRef = useRef<number | null>(null);

    const requestReRender = useCallback((immediate = false) => {
        if (animationFrameRef.current && !immediate) {
            return;
        }

        if (!immediate) {
            animationFrameRef.current = requestAnimationFrame(() => {
                setUpdateCounter(state => state + 1);
                animationFrameRef.current = null;
            });
            return;
        }

        if (animationFrameRef.current) {
            cancelAnimationFrame(animationFrameRef.current);
            animationFrameRef.current = null;
        }

        setUpdateCounter(state => state + 1);
    }, []);

    useEffect(() => {
        if (animationFrameRef.current) {
            cancelAnimationFrame(animationFrameRef.current);
            animationFrameRef.current = null;
        }
    });

    useEffect(
        () => () => {
            if (animationFrameRef.current) {
                cancelAnimationFrame(animationFrameRef.current);
            }
        },
        []
    );

    return requestReRender;
}

export const useBlock = (blockId: string) => {
    const [block, setBlock] = useState<AsyncBlock>();
    const requestReRender = useRequestReRender();
    const { editor } = useEditor();
    useEffect(() => {
        if (!blockId) {
            return undefined;
        }
        let valid = true;
        let offUpdate: () => void | undefined = undefined;
        editor.getBlockById(blockId).then(node => {
            if (!valid) {
                return;
            }
            if (!node) {
                console.warn('Failed to get block by id', blockId);
                return undefined;
            }
            setBlock(node);
            offUpdate = node.onUpdate(() => {
                requestReRender();
            });
        });

        return () => {
            valid = false;
            offUpdate?.();
        };
    }, [blockId, editor, requestReRender]);

    return { block };
};

/**
 *
 * hooks run when block node selected as a block
 * @export
 */
export const useOnSelect = (
    blockId: string,
    cb: (isSelect: boolean) => void
) => {
    const { editor } = useEditor();
    useEffect(() => {
        editor.selectionManager.observe(blockId, SelectEventTypes.onSelect, cb);
        return () => {
            editor.selectionManager.unobserve(
                blockId,
                SelectEventTypes.onSelect,
                cb
            );
        };
    }, [cb, blockId, editor]);
};

/**
 *
 * hooks run when block was set focused by selection manager
 * @export
 */
export const useOnSelectActive = (
    blockId: string,
    cb: (position: Point | undefined) => void
) => {
    const { editor } = useEditor();
    useEffect(() => {
        editor.selectionManager.observe(blockId, SelectEventTypes.active, cb);
        return () => {
            editor.selectionManager.unobserve(
                blockId,
                SelectEventTypes.active,
                cb
            );
        };
    }, [cb, blockId, editor]);
};

/**
 *
 * hooks run when block was set range by selection manager
 * @export
 */
export const useOnSelectSetSelection = <T extends keyof SelectionSettingsMap>(
    blockId: string,
    cb: (args: SelectionSettingsMap[T]) => void
) => {
    const { editor } = useEditor();
    useEffect(() => {
        editor.selectionManager.observe(
            blockId,
            SelectEventTypes.setSelection,
            cb
        );
        return () => {
            editor.selectionManager.unobserve(
                blockId,
                SelectEventTypes.setSelection,
                cb
            );
        };
    }, [cb, blockId, editor]);
};

/**
 *
 * hooks run when selection type or range is changed
 * @export
 */
export const useOnSelectChange = (cb: (info: SelectionInfo) => void) => {
    const { editor } = useEditor();
    useEffect(() => {
        editor.selectionManager.onSelectionChange(cb);
        return () => {
            editor.selectionManager.unBindSelectionChange(cb);
        };
    }, [editor, cb]);
};

/**
 *
 * hooks run when select end (based on mouse up)
 * @export
 */
export const useOnSelectEnd = (cb: (info: SelectionInfo) => void) => {
    const { editor } = useEditor();
    useEffect(() => {
        editor.selectionManager.onSelectEnd(cb);
        return () => {
            editor.selectionManager.onSelectEnd(cb);
        };
    }, [editor, cb]);
};

/**
 *
 * hooks run when select range start with the block node
 * @export
 */
export const useOnSelectStartWith = (
    blockId: string,
    cb: (args: MouseEvent) => void
) => {
    const { editor } = useEditor();
    useEffect(() => {
        editor.mouseManager.onSelectStartWith(blockId, cb);
        return () => {
            editor.mouseManager.offSelectStartWith(blockId, cb);
        };
    }, [editor, cb, blockId]);
};

/**
 *
 * hooks run when drag state change
 * @export
 */
export const useIsOnDrag = (editor: BlockEditor) => {
    const editorInstance = editor;
    const [isOnDrag, setIsOnDrag] = useState(false);
    useEffect(() => {
        const callback = (isDrag: boolean) => setIsOnDrag(isDrag);
        editorInstance.dragDropManager.onDragStateChange(callback);
        return () => {
            editorInstance.dragDropManager.offDragStateChange(callback);
        };
    }, [editorInstance]);
    return isOnDrag;
};
