import { useCallback, useEffect, useRef } from "react";
import { Group, Rect, Circle, RegularPolygon, Star } from "react-konva";
import Konva from "konva";
import type { EditorObject } from "../../store/editorStore";
import { getNodeTransformDimensions } from "../../utils/transformCommit";
import {
    RectangleShape,
    CircleShape,
    TriangleShape,
    AbstractShape,
    TextShape,
    LineShape,
    ArrowShape,
    ImageObject,
    LinkShape,
    VideoShape,
    AudioShape,
    CustomShape,
    PencilShape,
} from "../tools";
import EditorTable from "@/components/EditorTable";

type ObjectRendererProps = {
    obj: EditorObject;
    isSelected: boolean;
    transformerRef: React.RefObject<Konva.Transformer | null>;
    layerRef: React.RefObject<Konva.Layer | null>;
    onSelect: (id: string, node: Konva.Node, e?: Konva.KonvaEventObject<MouseEvent>) => void;
    onUpdate: (id: string, updates: Partial<EditorObject>) => void;
    draggable: boolean;
    onCellClick?: (tableId: string, cellId: string) => void;
    onCellDblClick?: (tableId: string, cellId: string, x: number, y: number, width: number, height: number, text: string) => void;
    selectedCellId?: string | null;
    allObjects?: EditorObject[];
    stageWidth?: number;
    stageHeight?: number;
};

const ObjectRenderer = ({
    obj,
    isSelected,
    transformerRef,
    layerRef,
    onSelect,
    onUpdate,
    draggable,
    onCellClick,
    onCellDblClick,
    selectedCellId,
    allObjects = [],
    stageWidth,
    stageHeight,
}: ObjectRendererProps) => {
    const groupRef = useRef<Konva.Group>(null);

    // Find mask for this object
    const maskShape = obj.maskId ? allObjects.find((m) => m.id === obj.maskId) : null;
    const shouldApplyMask = maskShape && !obj.isMask;

    // Whether the object and its mask share a groupId (move together as one unit).
    const isGroupedWithMask = !!(obj.groupId && maskShape && maskShape.groupId === obj.groupId);

    // For GROUPED masks the relative offset is always constant — no need to track it.
    // For NON-GROUPED masks we must recache when the user repositions just the mask.
    // Round to integer to suppress floating-point noise that accumulates when
    // handleObjectUpdate adds a delta to every group member on each drag pixel:
    //   e.g. 281.6692917998996 vs 281.6692917998995 would otherwise fire the effect.
    const maskRelXDep = (shouldApplyMask && !isGroupedWithMask) ? Math.round((maskShape?.x || 0) - (obj.x || 0)) : 0;
    const maskRelYDep = (shouldApplyMask && !isGroupedWithMask) ? Math.round((maskShape?.y || 0) - (obj.y || 0)) : 0;

    useEffect(() => {
        const groupNode = groupRef.current;
        if (!groupNode || !shouldApplyMask) return;

        let rafId1: number;
        const rafId2 = requestAnimationFrame(() => {
            rafId1 = requestAnimationFrame(() => {
                if (!groupRef.current) return;
                groupRef.current.clearCache();
                groupRef.current.cache();
                groupRef.current.getLayer()?.batchDraw();
            });
        });

        return () => {
            cancelAnimationFrame(rafId2);
            if (rafId1) cancelAnimationFrame(rafId1);
            groupNode?.clearCache();
        };
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [shouldApplyMask, isSelected, maskRelXDep, maskRelYDep, maskShape?.width, maskShape?.height, maskShape?.rotation, obj.width, obj.height, obj.rotation, obj.maskInvert, obj.strokeWidth, obj.stroke, obj.fill, obj.fillType, obj.gradient, obj.blur, obj.borderRadius]);

    // Refresh cache after transformer is attached (when isSelected changes)
    useEffect(() => {
        if (!groupRef.current || !shouldApplyMask || !isSelected) return;

        const timeoutId = setTimeout(() => {
            if (!groupRef.current) return;
            groupRef.current.clearCache();
            groupRef.current.cache();
            groupRef.current.getLayer()?.batchDraw();
        }, 150);

        return () => {
            clearTimeout(timeoutId);
        };
    }, [shouldApplyMask, isSelected, transformerRef]);

    // Called by async shapes (images) once their content is fully decoded and painted.
    const handleImageReady = useCallback(() => {
        if (!groupRef.current || !shouldApplyMask) return;
        requestAnimationFrame(() => {
            if (!groupRef.current) return;
            groupRef.current.clearCache();
            groupRef.current.cache();
            groupRef.current.getLayer()?.batchDraw();
        });
    }, [shouldApplyMask]);

    if (obj.visible === false) {
        return null;
    }

    // برای objectهای با mask، یک obj جدید با موقعیت (0, 0) ایجاد کن
    const shapeObj = shouldApplyMask
        ? { ...obj, x: 0, y: 0 }
        : obj;

    const commonProps = {
        obj: shapeObj,
        isSelected,
        transformerRef,
        onSelect,
        onUpdate,
        // اگر mask اعمال شده، shape را غیر draggable کن (Group drag می‌شود)
        draggable: shouldApplyMask ? false : draggable,
        stageWidth,
        stageHeight,
    };

    const renderMaskShape = (mask: EditorObject) => {
        const maskProps = {
            listening: false,
            perfectDrawEnabled: false,
            fill: "black",
        };

        // موقعیت mask نسبت به object - برای overlap صحیح
        // mask در موقعیت مطلق است، پس باید آن را relative به object کنیم
        // چون shape در موقعیت (0, 0) است، باید موقعیت mask را نسبت به shape محاسبه کنم
        const relativeX = (mask.x || 0) - (obj.x || 0);
        const relativeY = (mask.y || 0) - (obj.y || 0);

        if (mask.type === "rectangle" && mask.shapeType === "circle") {
            return (
                <Circle
                    {...maskProps}
                    x={relativeX}
                    y={relativeY}
                    radius={(mask.width || 50) / 2}
                    rotation={mask.rotation || 0}
                />
            );
        }

        if (mask.type === "rectangle" && mask.shapeType === "triangle") {
            const radius = Math.min((mask.width || 100) / 2, (mask.height || 100) / 2);
            return (
                <RegularPolygon
                    {...maskProps}
                    x={relativeX + (mask.width || 100) / 2}
                    y={relativeY + (mask.height || 100) / 2}
                    sides={3}
                    radius={radius}
                    rotation={mask.rotation || 0}
                />
            );
        }

        if (mask.type === "rectangle" && mask.shapeType === "abstract") {
            const baseRadius = Math.min((mask.width || 100) / 2, (mask.height || 100) / 2);
            const abstractScale = 0.85;
            const radius = baseRadius * abstractScale;
            const innerRadius = radius * 0.5;

            return (
                <Star
                    {...maskProps}
                    x={relativeX + (mask.width || 100) / 2}
                    y={relativeY + (mask.height || 100) / 2}
                    numPoints={5}
                    innerRadius={innerRadius}
                    outerRadius={radius}
                    rotation={mask.rotation || 0}
                />
            );
        }

        return (
            <Rect
                {...maskProps}
                x={relativeX}
                y={relativeY}
                width={mask.width || 100}
                height={mask.height || 100}
                cornerRadius={Math.max(0, mask.borderRadius ?? 0)}
                rotation={mask.rotation || 0}
            />
        );
    };

    let shapeElement: React.ReactNode = null;

    switch (obj.type) {
        case "rectangle":
            if (obj.shapeType === "circle") {
                shapeElement = <CircleShape key={obj.id} {...commonProps} />;
            } else if (obj.shapeType === "triangle") {
                shapeElement = <TriangleShape key={obj.id} {...commonProps} />;
            } else if (obj.shapeType === "abstract") {
                shapeElement = <AbstractShape key={obj.id} {...commonProps} />;
            } else {
                shapeElement = <RectangleShape key={obj.id} {...commonProps} />;
            }
            break;
        case "text":
            shapeElement = <TextShape key={obj.id} {...commonProps} layerRef={layerRef} />;
            break;
        case "line":
            shapeElement = <LineShape key={obj.id} {...commonProps} />;
            break;
        case "arrow":
            shapeElement = <ArrowShape key={obj.id} {...commonProps} />;
            break;
        case "image":
            shapeElement = <ImageObject key={obj.id} {...commonProps} onImageReady={handleImageReady} />;
            break;
        case "link":
            shapeElement = <LinkShape key={obj.id} {...commonProps} />;
            break;
        case "video":
            shapeElement = <VideoShape key={obj.id} {...commonProps} />;
            break;

        case "audio":
            shapeElement = (
                <AudioShape
                    key={`${obj.id}-${obj.audioName ?? ""}`}
                    {...commonProps}
                />
            );
            break;
        case "document":
            shapeElement = <ImageObject key={obj.id} {...commonProps} onImageReady={handleImageReady} />;
            break;
        case "sticker":
            shapeElement = <ImageObject key={obj.id} {...commonProps} onImageReady={handleImageReady} />;
            break;
        case "grid":
            shapeElement = (
                <EditorTable
                    key={obj.id}
                    obj={obj}
                    selectedCellId={selectedCellId || null}
                    onCellClick={(tableId, cellId, node, e) => {
                        onSelect(tableId, node, e);
                        onCellClick?.(tableId, cellId);
                    }}
                    onCellDblClick={(tableId, cellId, x, y, width, height, text) => {
                        onCellDblClick?.(tableId, cellId, x, y, width, height, text);
                    }}
                    onDragEnd={(tableId, x, y) => {
                        onUpdate(tableId, { x, y });
                    }}
                    draggable={draggable}
                />
            );
            break;
        case "custom-shape":
            shapeElement = <CustomShape key={obj.id} {...commonProps} />;
            break;
        case "pencil":
            shapeElement = <PencilShape key={obj.id} {...commonProps} />;
            break;
        case "group":
            // Render group as a simple selection box
            // Objects inside group are rendered separately in EditorCanvas
            shapeElement = (
                <Group
                    key={obj.id}
                    id={obj.id}
                    name="group-container"
                    x={obj.x}
                    y={obj.y}
                    draggable={draggable}
                    onClick={(e) => {
                        const groupNode = e.currentTarget;
                        if (groupNode) {
                            onSelect(obj.id, groupNode, e);
                        }
                    }}
                    onDragMove={(e) => {
                        // Snapping is handled at Stage level for smoother dragging.
                        e.target.getLayer()?.batchDraw();
                    }}
                    onDragEnd={(e) => {
                        const node = e.target;
                        onUpdate(obj.id, {
                            x: node.x(),
                            y: node.y(),
                        });
                    }}
                >
                    <Rect
                        x={0}
                        y={0}
                        width={obj.width || 100}
                        height={obj.height || 100}
                        fill="transparent"
                        stroke={isSelected ? "#3b82f6" : "#e5e7eb"}
                        strokeWidth={isSelected ? 2 : 1}
                        dash={[5, 5]}
                        listening={true}
                    />
                </Group>
            );
            break;
        default:
            return null;
    }

    // If no mask, render normally
    if (!shouldApplyMask || !maskShape) {
        return shapeElement;
    }

    // Apply masking with Group + destination-in or destination-out
    // IMPORTANT: The shape inside keeps its normal event handlers
    // The mask layer has listening={false} to not interfere with selection
    // Default: destination-in (نمایش فقط داخل ماسک — جاهایی که ماسک هست نمایش می‌دهد)
    const compositeOp = obj.maskInvert === false ? "destination-out" : "destination-in";

    const handleGroupDragEnd = (e: Konva.KonvaEventObject<DragEvent>) => {
        const node = e.target;
        onUpdate(obj.id, {
            x: node.x(),
            y: node.y(),
        });
    };

    const handleGroupDragMove = (_e: Konva.KonvaEventObject<DragEvent>) => {
        // Intentionally empty — see comment above.
    };

    const handleGroupTransformEnd = () => {
        if (!groupRef.current || !maskShape) return;

        const node = groupRef.current;
        const scaleX = node.scaleX();
        const scaleY = node.scaleY();

        // محاسبه موقعیت نسبی mask نسبت به object (قبل از resize)
        // این موقعیت نسبی در داخل Group استفاده می‌شود

        // reset scale و offset
        node.scaleX(1);
        node.scaleY(1);
        node.offsetX(0);
        node.offsetY(0);

        // محاسبه موقعیت جدید object
        const newX = node.x();
        const newY = node.y();

        // Handle special shapes that use radius (Circle, Triangle, Abstract)
        let newWidth: number;
        let newHeight: number;
        let finalX = newX;
        let finalY = newY;

        if (obj.type === "rectangle") {
            if (obj.shapeType === "circle") {
                // Circle: maintain aspect ratio and use average scale
                const avgScale = (scaleX + scaleY) / 2;
                const newRadius = Math.max(5, ((obj.width || 50) / 2) * avgScale);
                const newSize = newRadius * 2;
                newWidth = newSize;
                newHeight = newSize;
            } else if (obj.shapeType === "triangle" || obj.shapeType === "abstract") {
                // Triangle and Abstract: maintain aspect ratio and use average scale
                const avgScale = (scaleX + scaleY) / 2;
                const baseRadius = Math.min((obj.width || 100) / 2, (obj.height || 100) / 2);
                const newRadius = Math.max(5, baseRadius * avgScale);
                const newSize = newRadius * 2;
                newWidth = newSize;
                newHeight = newSize;
                // Adjust position for centered shapes
                finalX = newX - newSize / 2;
                finalY = newY - newSize / 2;
            } else {
                // Regular rectangle
                newWidth = Math.max(5, (obj.width || 100) * scaleX);
                newHeight = Math.max(5, (obj.height || 100) * scaleY);
            }
        } else if (obj.type === "line" || obj.type === "arrow") {
            // Line and Arrow: scale the end point
            const startX = obj.x || 0;
            const startY = obj.y || 0;
            const endX = obj.width || 0;
            const endY = obj.height || 0;
            const dx = (endX - startX) * scaleX;
            const dy = (endY - startY) * scaleY;
            newWidth = newX + dx;
            newHeight = newY + dy;
        } else if (obj.type === "text" || obj.type === "link") {
            // Text and Link: use scale directly
            newWidth = Math.max(5, (obj.width || 100) * scaleX);
            newHeight = Math.max(5, (obj.height || 100) * scaleY);
        } else {
            const { width: baseWidth, height: baseHeight } =
                getNodeTransformDimensions(node);
            newWidth = Math.max(5, baseWidth * scaleX);
            newHeight = Math.max(5, baseHeight * scaleY);
        }

        // به‌روزرسانی object
        onUpdate(obj.id, {
            x: finalX,
            y: finalY,
            rotation: node.rotation(),
            width: newWidth,
            height: newHeight,
        });
    };

    return (
        <Group
            ref={groupRef}
            id={`masked-${obj.id}`}
            name="masked-group"
            x={obj.x}
            y={obj.y}
            draggable={draggable}
            onClick={(e) => {
                if (groupRef.current) {
                    onSelect(obj.id, groupRef.current, e);
                }
            }}
            onDragEnd={handleGroupDragEnd}
            onDragMove={handleGroupDragMove}
            {...(!isGroupedWithMask && { onTransformEnd: handleGroupTransformEnd })}
        >
            {shapeElement}
            <Group globalCompositeOperation={compositeOp} listening={false}>
                {renderMaskShape(maskShape)}
            </Group>
        </Group>
    );
};

export default ObjectRenderer;

