import React, { MouseEvent, PropsWithChildren, useCallback, useEffect, useRef, useState, WheelEvent } from 'react';
import { ReactZoomPanPinchRef, TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch';
import { IconArrowsMaximize } from '@tabler/icons';
import { useThrottle } from 'react-use';
import useInterval from '../../utils/useInterval';
import { PossibleCanvasElements } from '../../shared/types/project/canvas/CanvasSaveFile';
import { GRID_LINE_SIZE, GRID_SVG_SIZE } from './Canvas/Grid';
import { useProjectDispatch, useProjectSelector } from './ProjectHome/Store';
import { WorkspacePanelSelection } from './ProjectHome/Store/reducers/interactionToolbar.reducer';
import { updateCanvasElement } from './ProjectHome/Store/actions/canvas.actions';

export const PANEL_CANVAS_SIZE = 30_000;

export interface PanelCanvasRenderProps {
    zoom: number;
}

export interface PanelCanvasProps {
    render: (props: PanelCanvasRenderProps) => JSX.Element;
}

export const PanelCanvas = ({ render }: PanelCanvasProps) => {
    const transformContainerRef = useRef<HTMLElement>(null);
    const transformWrapperRef = useRef<ReactZoomPanPinchRef>(null);
    const transformContentRef = useRef<HTMLElement>(null);

    const [zoom, setZoom] = useState(1);
    const [, setOffsetX] = useState(0);
    const [, setOffsetY] = useState(0);

    useInterval(() => {
        if (transformWrapperRef.current) {
            setZoom(transformWrapperRef.current.state.scale);
            setOffsetX(transformWrapperRef.current.state.positionX);
            setOffsetY(transformWrapperRef.current.state.positionY);
        }
    }, 100);

    useEffect(() => {
        const element = document.querySelector('.react-transform-component') as HTMLElement;

        if (element) {
            transformContentRef.current = element;
        }
    }, []);

    const [currentDoubleClickMode, setCurrentDoubleClickMode] = useState<'zoomIn' | 'zoomOut' | 'reset'>('zoomIn');
    const [currentDoubleClickX, setCurrentDoubleClickX] = useState(0);
    const [currentDoubleClickY, setCurrentDoubleClickY] = useState(0);

    const doEmulateDoubleClick = useCallback(() => {
        if (transformContainerRef.current) {
            transformContainerRef.current.childNodes.forEach((node) => {
                const wheelEvent = new globalThis.MouseEvent('dblclick', { clientX: currentDoubleClickX, clientY: currentDoubleClickY });

                node.dispatchEvent(wheelEvent);
            });
        }
    }, [currentDoubleClickX, currentDoubleClickY]);

    useEffect(doEmulateDoubleClick, [doEmulateDoubleClick, currentDoubleClickMode]);

    const handleWheel = useCallback((event: WheelEvent) => {
        setCurrentDoubleClickX(event.clientX);
        setCurrentDoubleClickY(event.clientY);

        const newDoubleClickMode = event.deltaY > 0 ? 'zoomOut' : 'zoomIn';

        setCurrentDoubleClickMode(newDoubleClickMode);
        if (currentDoubleClickMode === newDoubleClickMode) {
            doEmulateDoubleClick();
        }

        event.stopPropagation();
    }, [currentDoubleClickMode, doEmulateDoubleClick]);

    return (
        <section className="w-full h-full bg-gray-900 overflow-hidden" ref={transformContainerRef} onWheel={handleWheel}>
            <TransformWrapper
                ref={transformWrapperRef}
                limitToBounds
                initialPositionX={-PANEL_CANVAS_SIZE / 2}
                initialPositionY={-PANEL_CANVAS_SIZE / 2}
                minScale={0.045}
                panning={{
                    velocityDisabled: true,
                }}
                doubleClick={{
                    mode: currentDoubleClickMode,
                    step: 0.4,
                }}
                wheel={{
                    disabled: true,
                }}
            >
                <TransformComponent
                    wrapperStyle={{
                        width: '100%',
                        height: '100%',
                    }}
                >
                    <div style={{
                        overflow: 'hidden',
                        width: `${PANEL_CANVAS_SIZE}px`,
                        height: `${PANEL_CANVAS_SIZE}px`,
                    }}
                    >
                        {render({ zoom })}
                    </div>
                </TransformComponent>
            </TransformWrapper>
        </section>
    );
};

export interface PanelCanvasElementProps<T extends PossibleCanvasElements> {
    element: T,
    title?: string;
    initialWidth: number,
    initialHeight: number,
    canvasZoom: number;
    resizingEnabled?: boolean,
    onResizeCompleted?: (width: number, height: number) => void,
    topBarContent?: JSX.Element;
    id?: string;
}

export const PanelCanvasElement = <T extends PossibleCanvasElements>({
    element,
    title,
    initialWidth,
    initialHeight,
    canvasZoom,
    resizingEnabled,
    onResizeCompleted,
    topBarContent,
    id,
    children,
}: PropsWithChildren<PanelCanvasElementProps<T>>) => {
    const projectDispatch = useProjectDispatch();

    const xResizeOriginalPos = useRef(0);
    const yResizeOriginalPos = useRef(0);
    const resizeMode = useRef<'x' | 'y' | 'xy' | null>(null);

    const [width, setWidth] = useState(initialWidth);
    const [height, setHeight] = useState(initialHeight);

    const [offsetX, setOffsetX] = useState(() => element.position.x);
    const [offsetY, setOffsetY] = useState(() => element.position.y);

    const throttledOffsetX = useThrottle(offsetX, 750);
    const throttledOffsetY = useThrottle(offsetY, 750);

    const TITLE_FONTSIZE = 18;

    const roundToGrid = (input: number): number => {
        const PROJECTED_GRID_CELL_SIZE = (PANEL_CANVAS_SIZE / GRID_SVG_SIZE) * GRID_LINE_SIZE;

        return Math.round(input / PROJECTED_GRID_CELL_SIZE) * PROJECTED_GRID_CELL_SIZE;
    };

    // Handle updating the saved element when the throttled position is updated
    useEffect(() => {
        projectDispatch(updateCanvasElement({ ...element, position: { x: throttledOffsetX, y: throttledOffsetY } }));
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [element.__kind, projectDispatch, throttledOffsetX, throttledOffsetY]);

    const [editPositionX, setEditPositionX] = useState(() => element.position.x);
    const [editPositionY, setEditPositionY] = useState(() => element.position.y);

    // Handle setting element position as edit movement changes
    useEffect(() => {
        setOffsetX(roundToGrid(editPositionX));
        setOffsetY(roundToGrid(editPositionY));
    }, [editPositionX, editPositionY]);

    const inEditMode = useProjectSelector((state) => state.interactionToolbar.panel === WorkspacePanelSelection.Edit);

    const canvasElementRef = useRef<HTMLDivElement>(null);

    const handlePanStart = (event: MouseEvent) => {
        document.body.addEventListener('mouseup', handlePanStop);
        document.body.addEventListener('mousemove', handlePanMouseMove);
        event.stopPropagation();
    };

    const handlePanStop = (event: globalThis.MouseEvent) => {
        document.body.removeEventListener('mouseup', handlePanStop);
        document.body.removeEventListener('mousemove', handlePanMouseMove);
        event.stopPropagation();
    };

    const handlePanMouseMove = (event: globalThis.MouseEvent) => {
        if (!canvasElementRef.current) {
            return;
        }

        setEditPositionX((old) => old + event.movementX / canvasZoom);
        setEditPositionY((old) => old + event.movementY / canvasZoom);

        event.stopPropagation();
    };

    const handleMouseDown = (e: React.MouseEvent) => {
        (e as any).canvasTarget = element;
    };

    const handleResizeMouseMove = useCallback((e: globalThis.PointerEvent) => {
        if (resizeMode.current.includes('x')) {
            const delta = (e.clientX / canvasZoom) - xResizeOriginalPos.current;

            setWidth(initialWidth + delta);
        }

        if (resizeMode.current.includes('y')) {
            const delta = (e.clientY / canvasZoom) - yResizeOriginalPos.current;

            setHeight(initialHeight + delta);
        }
    }, [canvasZoom, initialWidth, initialHeight]);

    const handleResizeStop = useCallback((event: globalThis.MouseEvent) => {
        document.body.removeEventListener('mouseup', handleResizeStop);
        document.body.removeEventListener('pointermove', handleResizeMouseMove);
        event.stopPropagation();

        // For some reason, width and height do not update fast enough
        onResizeCompleted(canvasElementRef.current.clientWidth, canvasElementRef.current.clientHeight);
    }, [handleResizeMouseMove, onResizeCompleted]);

    const handleResizeStart = useCallback((event: React.MouseEvent, newResizeMode: 'x' | 'y' | 'xy') => {
        document.body.addEventListener('mouseup', handleResizeStop);
        document.body.addEventListener('pointermove', handleResizeMouseMove);
        event.stopPropagation();

        if (newResizeMode.includes('x')) {
            xResizeOriginalPos.current = event.clientX / canvasZoom;
        }

        if (newResizeMode.includes('y')) {
            yResizeOriginalPos.current = event.clientY / canvasZoom;
        }

        resizeMode.current = newResizeMode;
    }, [canvasZoom, handleResizeMouseMove, handleResizeStop]);

    return (
        <span id={id} className="absolute" onMouseDown={handleMouseDown}>
            <span
                ref={canvasElementRef}
                className="shadow-md"
                style={{
                    width: `${width}px`,
                    height: `${height}px`,
                    position: 'absolute',
                    // transitionDuration: '0.1s',
                    transform: `translate(${(PANEL_CANVAS_SIZE / 2) + offsetX}px, ${(PANEL_CANVAS_SIZE / 2) + offsetY}px)`,
                }}
            >
                <span className="w-full absolute flex flex-row px-3 bg-gray-800 border-2 border-gray-700 rounded-t-md bg-opacity-80 h-10 -top-10 justify-between items-center">
                    <h1 style={{ fontSize: `${TITLE_FONTSIZE}px` }}>{title}</h1>

                    <div className="flex flex-row flex-grow ml-2">
                        <div className="w-full flex items-center gap-x-2 ml-2.5">
                            {topBarContent}
                        </div>

                        {inEditMode && (
                            <div className="flex flex-row ml-2">
                                <IconArrowsMaximize size={22} className="hover:text-purple-500 hover:cursor-pointer" onMouseDown={handlePanStart} />
                            </div>
                        )}
                    </div>

                </span>

                <span
                    className="block border border-[#00c2cc] hover:border-green-500 overflow-hidden"
                    style={{
                        width: `${width}px`,
                        height: `${height}px`,
                    }}
                >
                    {children}
                </span>

                {resizingEnabled && (
                    <>
                        <span className="absolute top-0 right-[-4px] flex flex-col justify-center" style={{ height: 'calc(100% + 4px)' }}>
                            <span className="absolute w-[4px] h-[40px] bg-green-500" style={{ cursor: 'ew-resize' }} onMouseDown={(e) => handleResizeStart(e, 'x')} />

                            <span className="mt-auto w-[4px] h-[40px] bg-green-500" style={{ cursor: 'nwse-resize' }} onMouseDown={(e) => handleResizeStart(e, 'xy')} />
                        </span>

                        <span className="absolute flex justify-center" style={{ width: 'calc(100% + 4px)' }}>
                            <span className="absolute w-[40px] h-[4px] bg-green-500" style={{ cursor: 'ns-resize' }} onMouseDown={(e) => handleResizeStart(e, 'y')} />

                            <span className="ml-auto w-[40px] h-[4px] bg-green-500" style={{ cursor: 'nwse-resize' }} onMouseDown={(e) => handleResizeStart(e, 'xy')} />
                        </span>
                    </>
                )}
            </span>
        </span>
    );
};