import React, { useState, useRef, useEffect } from "react";
import { dataModelManager } from "./data/dataModel";
import ControlPanel from "./ControlPanel";
import SwapModal from "./SwapModal";
import DataModelPanel from "./components/models/DataModelPanel";

import { useSceneContext } from "../modules/scene/sceneContext";
import { calculateMinBlockSize } from "./functions/block/calculateMinBlockSize";
import { handleElementDragStart, handleElementResizeStart, handleBlockResizeStart, handleSwapStart, handleSwapTarget, handleBlockClick, handleElementClick } from "./functions/eventHandlers";
import BlockGrid from "./components/block/BlockGrid";
import ElementDropdown from "./components/element/ElementDropdown";
import BlockEditor from "./components/block/BlockEditor";
import ElementEditor from "./components/element/ElementEditor";
import { handleBlockDragStart } from "./functions/block/handleBlockDragStart";

const DashboardEditor: React.FC = () => {
    const { simulationDashBoardStore } = useSceneContext();
    const {
        blocks,
        setBlocks,
        updateBlockPosition,
        updateElementPosition,
        updateBlockSize,
        updateElementSize,
        addBlock,
        addElement,
        updateBlockStyle,
        updateBlockPositionType,
        updateBlockZIndex,
        updateElementStyle,
        updateElementPositionType,
        updateElementZIndex,
        updateElementData,
        updateGraphData,
        updateGraphTitle,
        swapElements,
    } = simulationDashBoardStore();
    const [selectedBlock, setSelectedBlock] = useState<string | null>(null);
    const [selectedElement, setSelectedElement] = useState<string | null>(null);
    const [editMode, setEditMode] = useState(false);
    const [draggingElement, setDraggingElement] = useState<string | null>(null);
    const [resizingElement, setResizingElement] = useState<string | null>(null);
    const [resizingBlock, setResizingBlock] = useState<string | null>(null);
    const [resizeStart, setResizeStart] = useState<{
        x: number;
        y: number;
        width: number;
        height: number;
    } | null>(null);
    const [showSwapUI, setShowSwapUI] = useState(false);
    const [swapSource, setSwapSource] = useState<string | null>(null);
    const [dataModel, setDataModel] = useState<DataModel>(dataModelManager.getDataSnapshot());
    const [showDataModelPanel, setShowDataModelPanel] = useState(false);
    const [showElementDropdown, setShowElementDropdown] = useState<string | null>(null);
    const [dropDownPosition, setDropDownPosition] = useState({ top: 0, left: 0 });
    const [draggingBlock, setDraggingBlock] = useState<string | null>(null);
    const [elementDragOffset, setElementDragOffset] = useState<Position>({ x: 0, y: 0 });
    const [blockDragOffset, setBlockDragOffset] = useState<Position>({ x: 0, y: 0 });

    const editorRef = useRef<HTMLDivElement>(null);
    const blockEditorRef = useRef<HTMLDivElement>(null);
    const elementEditorRef = useRef<HTMLDivElement>(null);
    const dropdownRef = useRef<HTMLDivElement>(null);
    const blockRef = useRef<HTMLDivElement>(null);

    const currentBlock = blocks.find((b) => b.id === selectedBlock);
    const currentElement = currentBlock?.elements.find((el) => el.id === selectedElement);

    // Subscribe to data model changes
    useEffect(() => {
        const handleDataChange = (): void => {
            setDataModel(dataModelManager.getDataSnapshot());
        };

        const keys = dataModelManager.getAvailableKeys();
        const subscriptions: Array<[string, () => void]> = [];

        keys.forEach((key) => {
            const callback = () => handleDataChange();
            dataModelManager.subscribe(key, callback);
            subscriptions.push([key, callback]);
        });

        const interval = setInterval(() => {
            const currentKeys = dataModelManager.getAvailableKeys();
            const newKeys = currentKeys.filter((key) => !keys.includes(key));

            newKeys.forEach((key) => {
                const callback = () => handleDataChange();
                dataModelManager.subscribe(key, callback);
                subscriptions.push([key, callback]);
            });
        }, 1000);

        return () => {
            subscriptions.forEach(([key, callback]) => {
                dataModelManager.unsubscribe(key, callback);
            });
            clearInterval(interval);
        };
    }, []);

    // Click outside handler
    useEffect(() => {
        const handleClickOutside = (event: MouseEvent): void => {
            const target = event.target as Node;

            const isInsideBlockEditor = blockEditorRef.current?.contains(target);
            const isInsideElementEditor = elementEditorRef.current?.contains(target);
            const isInsideDropdown = dropdownRef.current?.contains(target);
            const isInsideEditor = editorRef.current?.contains(target);

            if (!isInsideEditor) {
                setSelectedBlock(null);
                setSelectedElement(null);
                setShowSwapUI(false);
                return;
            }

            if (isInsideEditor && !isInsideBlockEditor && !isInsideElementEditor && !isInsideDropdown) {
                const clickedElement = event.target as HTMLElement;
                const isBlock = clickedElement.closest("[data-block-id]");
                const isElement = clickedElement.closest("[data-element-id]");
                const isButton = clickedElement.closest("button");
                const isResizeHandle = clickedElement.closest(".resize-handle");

                if (!isBlock && !isElement && !isButton && !isResizeHandle) {
                    setSelectedBlock(null);
                    setSelectedElement(null);
                    setShowSwapUI(false);
                    setShowElementDropdown(null);
                }
            }
        };

        document.addEventListener("mousedown", handleClickOutside);
        return () => {
            document.removeEventListener("mousedown", handleClickOutside);
        };
    }, [selectedBlock, selectedElement]);

    // Drag and drop handler
    useEffect(() => {
        const handleMouseMove = (e: MouseEvent): void => {
            // Element dragging - use elementDragOffset
            if (draggingElement && selectedBlock && currentElement?.positionType === "absolute") {
                const blockElement = document.querySelector(`[data-block-id="${selectedBlock}"]`) as HTMLElement;
                if (blockElement) {
                    const blockRect = blockElement.getBoundingClientRect();
                    const newX = e.clientX - blockRect.left - elementDragOffset.x; // Use elementDragOffset
                    const newY = e.clientY - blockRect.top - elementDragOffset.y; // Use elementDragOffset

                    updateElementPosition(selectedBlock, draggingElement, {
                        x: Math.max(0, Math.min(blockRect.width - 50, newX)),
                        y: Math.max(0, Math.min(blockRect.height - 30, newY)),
                    });
                }
            }

            // Block dragging - use blockDragOffset
            if (draggingBlock && currentBlock?.positionType && (currentBlock.positionType === "absolute" || currentBlock.positionType === "fixed")) {
                const editorElement = editorRef.current;
                if (editorElement) {
                    const editorRect = editorElement.getBoundingClientRect();
                    const newX = e.clientX - editorRect.left - blockDragOffset.x; // Use blockDragOffset
                    const newY = e.clientY - editorRect.top - blockDragOffset.y; // Use blockDragOffset

                    updateBlockPosition(draggingBlock, {
                        x: Math.max(0, Math.min(editorRect.width - (currentBlock.size?.width || 400), newX)),
                        y: Math.max(0, Math.min(editorRect.height - (currentBlock.size?.height || 300), newY)),
                    });
                }
            }

            if ((resizingElement || resizingBlock) && resizeStart) {
                if (resizingElement && selectedBlock) {
                    const deltaX = e.clientX - resizeStart.x;
                    const deltaY = e.clientY - resizeStart.y;

                    const newWidth = Math.max(100, resizeStart.width + deltaX);
                    const newHeight = Math.max(50, resizeStart.height + deltaY);

                    updateElementSize(selectedBlock, resizingElement, {
                        width: newWidth,
                        height: newHeight,
                    });
                } else if (resizingBlock) {
                    const deltaX = e.clientX - resizeStart.x;
                    const deltaY = e.clientY - resizeStart.y;

                    const currentBlock = blocks.find((b) => b.id === resizingBlock);
                    const minSize = currentBlock ? calculateMinBlockSize(currentBlock) : { width: 300, height: 200 };

                    const newWidth = Math.max(minSize.width, resizeStart.width + deltaX);
                    const newHeight = Math.max(minSize.height, resizeStart.height + deltaY);

                    updateBlockSize(resizingBlock, {
                        width: newWidth,
                        height: newHeight,
                    });
                }
            }
        };

        const handleMouseUp = (): void => {
            setDraggingElement(null);
            setResizingElement(null);
            setDraggingBlock(null);
            setResizingBlock(null);
            setResizeStart(null);
        };

        if (draggingElement || draggingBlock || resizingElement || resizingBlock) {
            document.addEventListener("mousemove", handleMouseMove);
            document.addEventListener("mouseup", handleMouseUp);
        }

        return () => {
            document.removeEventListener("mousemove", handleMouseMove);
            document.removeEventListener("mouseup", handleMouseUp);
        };
    }, [draggingElement, resizingElement, draggingBlock, resizingBlock, elementDragOffset, blockDragOffset, selectedBlock, currentElement, resizeStart, currentBlock, blocks]);

    // Update dropdown position when showElementDropdown changes
    useEffect(() => {
        if (showElementDropdown && blockRef.current) {
            const rect = blockRef.current.getBoundingClientRect();
            setDropDownPosition({
                top: rect.bottom + window.scrollY,
                left: rect.left + window.scrollX,
            });
        }
    }, [showElementDropdown]);

    return (
        <div ref={editorRef} className="dashboard-editor">
            <ControlPanel editMode={editMode} setEditMode={setEditMode} addBlock={() => addBlock()} showDataModelPanel={showDataModelPanel} setShowDataModelPanel={setShowDataModelPanel} />

            <BlockGrid
                blocks={blocks}
                editMode={editMode}
                selectedBlock={selectedBlock}
                selectedElement={selectedElement}
                showSwapUI={showSwapUI}
                swapSource={swapSource}
                calculateMinBlockSize={calculateMinBlockSize}
                handleBlockClick={(blockId, event) => handleBlockClick(blockId, event, editMode, setSelectedBlock, setSelectedElement, setShowSwapUI)}
                handleElementClick={(blockId, elementId, event) => handleElementClick(blockId, elementId, event, editMode, setSelectedElement, setSelectedBlock, setShowSwapUI, setShowElementDropdown)}
                handleElementDragStart={(elementId, event) => handleElementDragStart(elementId, event, currentElement, setDraggingElement, setElementDragOffset)}
                handleElementResizeStart={(elementId, event) => handleElementResizeStart(elementId, event, setResizingElement, setResizeStart)}
                handleBlockResizeStart={(blockId, event) => handleBlockResizeStart(blockId, event, setResizingBlock, setResizeStart)}
                handleSwapStart={(elementId, event) => handleSwapStart(elementId, event, setSwapSource, setShowSwapUI)}
                handleSwapTarget={(elementId, event) => handleSwapTarget(elementId, event, swapSource, selectedBlock, swapElements)}
                handleBlockDragStart={(blockId, event) => handleBlockDragStart(blockId, event, setDraggingBlock, setBlockDragOffset)}
                setShowElementDropdown={setShowElementDropdown}
                showElementDropdown={showElementDropdown}
                blockRef={blockRef}
            />

            <ElementDropdown
                showElementDropdown={showElementDropdown}
                dropDownPosition={dropDownPosition}
                addElement={(blockId, type, graphType) => addElement(blockId, type as UIType, graphType as GraphTypes)}
                dropdownRef={dropdownRef}
            />

            {showDataModelPanel && editMode && <DataModelPanel dataModel={dataModel} dataModelManager={dataModelManager} />}

            {selectedBlock && editMode && !selectedElement && currentBlock && (
                <BlockEditor
                    blockEditorRef={blockEditorRef}
                    currentBlock={currentBlock}
                    selectedBlock={selectedBlock}
                    updateBlockStyle={(blockId, style) => updateBlockStyle(blockId, style)}
                    updateBlockSize={(blockId, size) => updateBlockSize(blockId, size)}
                    updateBlockPosition={(blockId, position) => updateBlockPosition(blockId, position)}
                    updateBlockPositionType={(blockId, positionType) => updateBlockPositionType(blockId, positionType)}
                    updateBlockZIndex={(blockId, zIndex) => updateBlockZIndex(blockId, zIndex)}
                />
            )}

            {selectedElement && editMode && selectedBlock && currentElement && (
                <ElementEditor
                    elementEditorRef={elementEditorRef}
                    currentElement={currentElement}
                    selectedBlock={selectedBlock}
                    selectedElement={selectedElement}
                    blocks={blocks}
                    setBlocks={setBlocks}
                    updateElementStyle={(blockId, elementId, style) => updateElementStyle(blockId, elementId, style)}
                    updateElementSize={(blockId, elementId, size) => updateElementSize(blockId, elementId, size)}
                    updateElementPosition={(blockId, elementId, position) => updateElementPosition(blockId, elementId, position)}
                    updateElementPositionType={(blockId, elementId, positionType) => updateElementPositionType(blockId, elementId, positionType)}
                    updateElementZIndex={(blockId, elementId, zIndex) => updateElementZIndex(blockId, elementId, zIndex)}
                    updateElementData={(blockId, elementId, updates) => updateElementData(blockId, elementId, updates)}
                    updateGraphData={(blockId, elementId, newData) => updateGraphData(blockId, elementId, newData)}
                    updateGraphTitle={(blockId, elementId, title) => updateGraphTitle(blockId, elementId, title)}
                    setSwapSource={setSwapSource}
                    setShowSwapUI={setShowSwapUI}
                    dataModelManager={dataModelManager}
                />
            )}

            {showSwapUI && <SwapModal setShowSwapUI={setShowSwapUI} setSwapSource={setSwapSource} />}
        </div>
    );
};

export default DashboardEditor;
