import {
  useEffect,
  useRef,
  useCallback,
  forwardRef,
  useImperativeHandle,
  useState,
} from "react";
import { Box, Typography, Fade } from "@mui/material";
import * as fabric from "fabric";
import type {
  AprtTemplate,
  AprtElement,
  PageSize,
  PageOrientation,
} from "../../types/report";
import {
  getPageDimensions,
  mmToPx,
  pxToMm,
  defaultStyle,
} from "../../types/report";
import type { SnapOptions } from "./EditorToolbar";

// Extend fabric types to include data property
interface FabricObjectWithData extends fabric.FabricObject {
  data?: Record<string, unknown>;
}

export interface ContextMenuEvent {
  position: { x: number; y: number };
  elementId: string | null;
}

// Remote cursor info
export interface RemoteCursor {
  x: number;
  y: number;
  pageId: string | null;
  color: string;
  userName: string;
}

interface EditorCanvasProps {
  template: AprtTemplate;
  selectedElementIds: string[];
  onSelectElement: (ids: string[]) => void;
  onUpdateElement: (id: string, updates: Partial<AprtElement>) => void;
  /** Called when a drag/resize operation completes - use to commit to history */
  onUpdateElementComplete?: () => void;
  zoom: number;
  showGrid: boolean;
  gridSize: number;
  snapOptions: SnapOptions;
  onContextMenu?: (event: ContextMenuEvent) => void;
  /** Callback when cursor moves on canvas (for collaboration) */
  onCursorMove?: (x: number, y: number) => void;
  /** Remote cursors from other collaborators */
  remoteCursors?: Map<string, RemoteCursor>;
  /** Remote selections from other collaborators (connectionId -> elementId) */
  remoteSelections?: Map<string, string | null>;
  /** Map of connectionId -> collaborator info for colors */
  collaboratorColors?: Map<string, { color: string; userName: string }>;
  /** Current page ID for filtering remote cursors */
  currentPageId?: string;
}

export interface EditorCanvasRef {
  getCanvas: () => fabric.Canvas | null;
  addElement: (element: AprtElement) => void;
  /** Check if a text element is currently being edited */
  isTextEditing: () => boolean;
}

// Snap configuration
const SNAP_THRESHOLD = 6;
const GUIDE_COLOR = "#e91e63";

const EditorCanvas = forwardRef<EditorCanvasRef, EditorCanvasProps>(
  (
    {
      template,
      selectedElementIds,
      onSelectElement,
      onUpdateElement,
      onUpdateElementComplete,
      zoom,
      showGrid,
      gridSize,
      snapOptions,
      onContextMenu,
      onCursorMove,
      // These props are reserved for future remote cursor rendering on canvas
      // remoteCursors,
      // remoteSelections,
      // collaboratorColors,
      // currentPageId,
    },
    ref,
  ) => {
    const canvasRef = useRef<HTMLCanvasElement>(null);
    const fabricRef = useRef<fabric.Canvas | null>(null);
    const elementsMapRef = useRef<Map<string, FabricObjectWithData>>(new Map());
    const isUpdatingRef = useRef(false);
    const isTransformingRef = useRef(false); // True while user is actively dragging/scaling
    const guideLinesRef = useRef<fabric.Line[]>([]);
    const loadedImagesRef = useRef<Map<string, string>>(new Map()); // elementId -> loaded src
    const loadingImagesRef = useRef<Set<string>>(new Set()); // elementIds currently being loaded
    const templateElementsRef = useRef(template.elements); // Ref for accessing current elements in callbacks

    // Custom multi-selection state
    const isDrawingSelectionRef = useRef(false);
    const selectionStartRef = useRef<{ x: number; y: number } | null>(null);
    const selectionRectRef = useRef<fabric.Rect | null>(null);

    // Track selected objects for multi-move
    const multiSelectedObjectsRef = useRef<FabricObjectWithData[]>([]);
    const isDraggingMultiRef = useRef(false);
    const dragStartPosRef = useRef<{ x: number; y: number } | null>(null);

    // Refs to access current values in event handlers (avoids stale closures)
    const selectedElementIdsRef = useRef(selectedElementIds);
    selectedElementIdsRef.current = selectedElementIds;
    const onSelectElementRef = useRef(onSelectElement);
    onSelectElementRef.current = onSelectElement;
    const onUpdateElementRef = useRef(onUpdateElement);
    onUpdateElementRef.current = onUpdateElement;
    const onUpdateElementCompleteRef = useRef(onUpdateElementComplete);
    onUpdateElementCompleteRef.current = onUpdateElementComplete;
    const zoomRef = useRef(zoom);
    zoomRef.current = zoom;

    // Keep templateElementsRef in sync
    templateElementsRef.current = template.elements;

    // UI State
    const [cursorPosition, setCursorPosition] = useState({ x: 0, y: 0 });

    // Calculate canvas dimensions
    const pageDims = getPageDimensions(
      template.meta.pageSize as PageSize,
      template.meta.orientation as PageOrientation,
    );
    const canvasWidth = mmToPx(pageDims.width);
    const canvasHeight = mmToPx(pageDims.height);

    // Expose methods to parent
    useImperativeHandle(ref, () => ({
      getCanvas: () => fabricRef.current,
      addElement: (element: AprtElement) => {
        if (!fabricRef.current) return;
        const obj = createFabricObject(element, zoom);
        if (obj) {
          fabricRef.current.add(obj);
          elementsMapRef.current.set(element.id, obj as FabricObjectWithData);
          fabricRef.current.setActiveObject(obj);
          fabricRef.current.renderAll();
        }
      },
      isTextEditing: () => {
        if (!fabricRef.current) return false;
        const activeObj = fabricRef.current.getActiveObject();
        if (!activeObj) return false;
        return !!(activeObj as fabric.Textbox).isEditing;
      },
    }));

    // Clear guide lines
    const clearGuideLines = useCallback(() => {
      if (!fabricRef.current) return;
      guideLinesRef.current.forEach((line) => {
        fabricRef.current?.remove(line);
      });
      guideLinesRef.current = [];
    }, []);

    // Add a guide line
    const addGuideLine = useCallback(
      (x1: number, y1: number, x2: number, y2: number) => {
        if (!fabricRef.current) return;
        const line = new fabric.Line([x1, y1, x2, y2], {
          stroke: GUIDE_COLOR,
          strokeWidth: 1,
          strokeDashArray: [4, 4],
          selectable: false,
          evented: false,
          excludeFromExport: true,
        });
        (line as FabricObjectWithData).data = { isGuide: true };
        fabricRef.current.add(line);
        fabricRef.current.bringObjectToFront(line);
        guideLinesRef.current.push(line);
      },
      [],
    );

    // Event handlers
    const handleSelectionCreated = useCallback(
      (e: { selected: fabric.FabricObject[] }) => {
        // Ignore if we're handling multi-selection manually
        if (isDraggingMultiRef.current || isDrawingSelectionRef.current) {
          return;
        }
        // Ignore if we already have multi-selection (don't let Fabric override it)
        if (selectedElementIdsRef.current.length > 1) {
          return;
        }

        const selectedIds: string[] = [];
        if (e.selected) {
          for (const obj of e.selected) {
            const objData = (obj as FabricObjectWithData).data;
            if (
              objData?.id &&
              !objData.isGrid &&
              !objData.isMargin &&
              !objData.isGuide
            ) {
              selectedIds.push(objData.id as string);
            }
          }
        }
        if (selectedIds.length > 0) {
          onSelectElementRef.current(selectedIds);
        }
      },
      [],
    );

    const handleSelectionCleared = useCallback(() => {
      // Ignore if we're handling multi-selection manually
      if (isDraggingMultiRef.current || isDrawingSelectionRef.current) {
        return;
      }
      // Ignore if we have multi-selection (don't let Fabric clear it)
      if (selectedElementIdsRef.current.length > 1) {
        return;
      }

      onSelectElementRef.current([]);
      clearGuideLines();
    }, [clearGuideLines]);

    const handleObjectMoving = useCallback(
      (e: fabric.TEvent<MouseEvent> & { target?: fabric.FabricObject }) => {
        const obj = e.target;
        if (!obj || !fabricRef.current) return;

        // Mark that user is actively transforming
        isTransformingRef.current = true;

        clearGuideLines();

        // Check if any snap is enabled
        const anySnapEnabled = Object.values(snapOptions).some(Boolean);
        if (!anySnapEnabled) {
          // No snap - free movement
          return;
        }

        // Get current position
        let left = obj.left ?? 0;
        let top = obj.top ?? 0;
        const objWidth = (obj.width ?? 0) * (obj.scaleX ?? 1);
        const objHeight = (obj.height ?? 0) * (obj.scaleY ?? 1);

        // Calculate centers
        const objCenterX = left + objWidth / 2;
        const objCenterY = top + objHeight / 2;
        const objRight = left + objWidth;
        const objBottom = top + objHeight;

        // Canvas dimensions in pixels
        const cWidth = canvasWidth * zoom;
        const cHeight = canvasHeight * zoom;
        const centerX = cWidth / 2;
        const centerY = cHeight / 2;

        // Margins in pixels
        const marginLeft = mmToPx(template.meta.margins.left) * zoom;
        const marginRight = cWidth - mmToPx(template.meta.margins.right) * zoom;
        const marginTop = mmToPx(template.meta.margins.top) * zoom;
        const marginBottom =
          cHeight - mmToPx(template.meta.margins.bottom) * zoom;

        // Grid snap (takes priority over other snaps)
        if (snapOptions.grid && gridSize > 0) {
          const gridPx = mmToPx(gridSize) * zoom;
          left = Math.round(left / gridPx) * gridPx;
          top = Math.round(top / gridPx) * gridPx;
          obj.set({ left, top });
          return;
        }

        // Smart snap to guides
        let snappedX = false;
        let snappedY = false;

        // Snap to canvas center (when center snap is enabled)
        if (snapOptions.center) {
          // Snap to canvas center X
          if (Math.abs(objCenterX - centerX) < SNAP_THRESHOLD) {
            left = centerX - objWidth / 2;
            snappedX = true;
            addGuideLine(centerX, 0, centerX, cHeight);
          }
          // Snap to canvas center Y
          if (Math.abs(objCenterY - centerY) < SNAP_THRESHOLD) {
            top = centerY - objHeight / 2;
            snappedY = true;
            addGuideLine(0, centerY, cWidth, centerY);
          }
        }

        // Snap to borders/margins (when borders snap is enabled)
        if (snapOptions.borders) {
          // Snap to left margin
          if (!snappedX && Math.abs(left - marginLeft) < SNAP_THRESHOLD) {
            left = marginLeft;
            snappedX = true;
            addGuideLine(marginLeft, 0, marginLeft, cHeight);
          }
          // Snap to right margin
          if (!snappedX && Math.abs(objRight - marginRight) < SNAP_THRESHOLD) {
            left = marginRight - objWidth;
            snappedX = true;
            addGuideLine(marginRight, 0, marginRight, cHeight);
          }
          // Snap to top margin
          if (!snappedY && Math.abs(top - marginTop) < SNAP_THRESHOLD) {
            top = marginTop;
            snappedY = true;
            addGuideLine(0, marginTop, cWidth, marginTop);
          }
          // Snap to bottom margin
          if (
            !snappedY &&
            Math.abs(objBottom - marginBottom) < SNAP_THRESHOLD
          ) {
            top = marginBottom - objHeight;
            snappedY = true;
            addGuideLine(0, marginBottom, cWidth, marginBottom);
          }
        }

        // Snap to other objects (when objects or center or tangent snap is enabled)
        if (snapOptions.objects || snapOptions.center || snapOptions.tangent) {
          const objects = fabricRef.current.getObjects().filter((o) => {
            const d = (o as FabricObjectWithData).data;
            return o !== obj && !d?.isGrid && !d?.isMargin && !d?.isGuide;
          });

          for (const other of objects) {
            if (snappedX && snappedY) break;

            const otherLeft = other.left ?? 0;
            const otherTop = other.top ?? 0;
            const otherWidth = (other.width ?? 0) * (other.scaleX ?? 1);
            const otherHeight = (other.height ?? 0) * (other.scaleY ?? 1);
            const otherRight = otherLeft + otherWidth;
            const otherBottom = otherTop + otherHeight;
            const otherCenterX = otherLeft + otherWidth / 2;
            const otherCenterY = otherTop + otherHeight / 2;

            // Object alignment snapping (left-to-left, right-to-right, top-to-top, bottom-to-bottom)
            if (snapOptions.objects && !snappedX) {
              // Left to left
              if (Math.abs(left - otherLeft) < SNAP_THRESHOLD) {
                left = otherLeft;
                snappedX = true;
                addGuideLine(
                  otherLeft,
                  Math.min(top, otherTop),
                  otherLeft,
                  Math.max(objBottom, otherBottom),
                );
              }
              // Right to right
              else if (Math.abs(objRight - otherRight) < SNAP_THRESHOLD) {
                left = otherRight - objWidth;
                snappedX = true;
                addGuideLine(
                  otherRight,
                  Math.min(top, otherTop),
                  otherRight,
                  Math.max(objBottom, otherBottom),
                );
              }
            }

            if (snapOptions.objects && !snappedY) {
              // Top to top
              if (Math.abs(top - otherTop) < SNAP_THRESHOLD) {
                top = otherTop;
                snappedY = true;
                addGuideLine(
                  Math.min(left, otherLeft),
                  otherTop,
                  Math.max(objRight, otherRight),
                  otherTop,
                );
              }
              // Bottom to bottom
              else if (Math.abs(objBottom - otherBottom) < SNAP_THRESHOLD) {
                top = otherBottom - objHeight;
                snappedY = true;
                addGuideLine(
                  Math.min(left, otherLeft),
                  otherBottom,
                  Math.max(objRight, otherRight),
                  otherBottom,
                );
              }
            }

            // Center to center snapping (object centers)
            if (snapOptions.center) {
              if (
                !snappedX &&
                Math.abs(objCenterX - otherCenterX) < SNAP_THRESHOLD
              ) {
                left = otherCenterX - objWidth / 2;
                snappedX = true;
                addGuideLine(
                  otherCenterX,
                  Math.min(top, otherTop),
                  otherCenterX,
                  Math.max(objBottom, otherBottom),
                );
              }
              if (
                !snappedY &&
                Math.abs(objCenterY - otherCenterY) < SNAP_THRESHOLD
              ) {
                top = otherCenterY - objHeight / 2;
                snappedY = true;
                addGuideLine(
                  Math.min(left, otherLeft),
                  otherCenterY,
                  Math.max(objRight, otherRight),
                  otherCenterY,
                );
              }
            }

            // Tangent snapping (adjacent edges: left-to-right, right-to-left, top-to-bottom, bottom-to-top)
            if (snapOptions.tangent) {
              if (!snappedX) {
                // Left to right (adjacent)
                if (Math.abs(left - otherRight) < SNAP_THRESHOLD) {
                  left = otherRight;
                  snappedX = true;
                  addGuideLine(
                    otherRight,
                    Math.min(top, otherTop),
                    otherRight,
                    Math.max(objBottom, otherBottom),
                  );
                }
                // Right to left (adjacent)
                else if (Math.abs(objRight - otherLeft) < SNAP_THRESHOLD) {
                  left = otherLeft - objWidth;
                  snappedX = true;
                  addGuideLine(
                    otherLeft,
                    Math.min(top, otherTop),
                    otherLeft,
                    Math.max(objBottom, otherBottom),
                  );
                }
              }
              if (!snappedY) {
                // Top to bottom (adjacent)
                if (Math.abs(top - otherBottom) < SNAP_THRESHOLD) {
                  top = otherBottom;
                  snappedY = true;
                  addGuideLine(
                    Math.min(left, otherLeft),
                    otherBottom,
                    Math.max(objRight, otherRight),
                    otherBottom,
                  );
                }
                // Bottom to top (adjacent)
                else if (Math.abs(objBottom - otherTop) < SNAP_THRESHOLD) {
                  top = otherTop - objHeight;
                  snappedY = true;
                  addGuideLine(
                    Math.min(left, otherLeft),
                    otherTop,
                    Math.max(objRight, otherRight),
                    otherTop,
                  );
                }
              }
            }
          }
        }

        // Apply snapped position
        if (snappedX || snappedY) {
          obj.set({
            left: snappedX ? left : obj.left,
            top: snappedY ? top : obj.top,
          });
        }
      },
      [
        zoom,
        canvasWidth,
        canvasHeight,
        template.meta.margins,
        snapOptions,
        gridSize,
        clearGuideLines,
        addGuideLine,
      ],
    );

    const handleObjectModified = useCallback(
      (e: { target?: fabric.FabricObject }) => {
        // Mark transform as complete
        isTransformingRef.current = false;

        if (isUpdatingRef.current) return;

        clearGuideLines();

        const obj = e.target as FabricObjectWithData | undefined;
        if (!obj?.data?.id) return;

        // Save element id before any type narrowing
        const elementId = obj.data.id as string;

        const scaleX = obj.scaleX || 1;
        const scaleY = obj.scaleY || 1;

        // Calculate final dimensions including scale
        const finalWidth = (obj.width || 0) * scaleX;
        const finalHeight = (obj.height || 0) * scaleY;

        const updates: Partial<AprtElement> = {
          position: {
            x: pxToMm(obj.left || 0) / zoom,
            y: pxToMm(obj.top || 0) / zoom,
            width: pxToMm(finalWidth) / zoom,
            height: pxToMm(finalHeight) / zoom,
            rotation: obj.angle || 0,
          },
        };

        // For FabricImage, DON'T reset scale - the scale IS how we size images
        // (width/height in FabricImage are the native image dimensions)
        // For other objects, reset scale to 1 after applying to width/height
        if (!(obj instanceof fabric.FabricImage)) {
          obj.set({
            width: finalWidth,
            height: finalHeight,
            scaleX: 1,
            scaleY: 1,
          });
        }
        obj.setCoords();

        onUpdateElement(elementId, updates);

        // Commit to history after drag/resize operation completes
        onUpdateElementComplete?.();
      },
      [onUpdateElement, onUpdateElementComplete, zoom, clearGuideLines],
    );

    const handleObjectScaling = useCallback(
      (e: {
        target?: fabric.FabricObject;
        transform?: { corner?: string };
      }) => {
        const obj = e.target;
        if (!obj) return;

        // Mark that user is actively transforming
        isTransformingRef.current = true;

        const scaleX = obj.scaleX || 1;
        const scaleY = obj.scaleY || 1;
        const objData = (obj as FabricObjectWithData).data;

        // For textboxes, only update width (height auto-adjusts)
        if (obj instanceof fabric.Textbox) {
          obj.set({
            width: (obj.width || 100) * scaleX,
            scaleX: 1,
            scaleY: 1,
          });
        }
        // For shapes (Rect) that are NOT image placeholders - free resize
        else if (obj instanceof fabric.Rect && objData?.type !== "image") {
          obj.set({
            width: (obj.width || 100) * scaleX,
            height: (obj.height || 100) * scaleY,
            scaleX: 1,
            scaleY: 1,
          });
        }
        // For image placeholders (Rect with type="image") - always free resize
        else if (obj instanceof fabric.Rect && objData?.type === "image") {
          obj.set({
            width: (obj.width || 100) * scaleX,
            height: (obj.height || 100) * scaleY,
            scaleX: 1,
            scaleY: 1,
          });
        }
        // For images (FabricImage) - check maintainAspectRatio setting
        else if (obj instanceof fabric.FabricImage) {
          const elementId = objData?.id as string;
          // Use ref to get current elements (avoids stale closure issue)
          const element = templateElementsRef.current.find(
            (el) => el.id === elementId,
          );

          // Check if maintainAspectRatio is explicitly set to false
          // Default (undefined) = maintain aspect ratio
          // true = maintain aspect ratio
          // false = free resize
          const maintainAspectRatio =
            element?.imageSettings?.maintainAspectRatio;

          if (maintainAspectRatio === false) {
            // Explicitly disabled - allow completely free resize
            // Don't modify scaleX/scaleY, let Fabric.js handle naturally
          } else {
            // True or undefined (default) - force uniform scaling
            const uniformScale = Math.max(scaleX, scaleY);
            obj.set({
              scaleX: uniformScale,
              scaleY: uniformScale,
            });
          }
        }
        // For lines, update length
        else if (obj instanceof fabric.Line) {
          const x1 = obj.x1 || 0;
          const y1 = obj.y1 || 0;
          const x2 = obj.x2 || 0;
          const y2 = obj.y2 || 0;
          obj.set({
            x2: x1 + (x2 - x1) * scaleX,
            y2: y1 + (y2 - y1) * scaleY,
            scaleX: 1,
            scaleY: 1,
          });
        }
        // For groups (tables), scale the group
        else if (obj instanceof fabric.Group) {
          obj.set({
            width: (obj.width || 100) * scaleX,
            height: (obj.height || 100) * scaleY,
            scaleX: 1,
            scaleY: 1,
          });
        }

        obj.setCoords();
      },
      [], // Uses templateElementsRef for current data
    );

    const handleTextChanged = useCallback(
      (e: { target: fabric.FabricObject }) => {
        const obj = e.target as FabricObjectWithData;
        if (!(obj instanceof fabric.Textbox)) return;
        const objData = (obj as FabricObjectWithData).data;
        if (!objData?.id) return;

        onUpdateElement(objData.id as string, {
          content: {
            type: "static",
            value: obj.text || "",
          },
        });
      },
      [onUpdateElement],
    );

    const handleMouseOver = useCallback(
      (e: { target?: fabric.FabricObject }) => {
        const obj = e.target as FabricObjectWithData | undefined;
        const objData = obj?.data;
        if (
          objData?.id &&
          !objData.isGrid &&
          !objData.isMargin &&
          !objData.isGuide
        ) {
          if (obj && fabricRef.current?.getActiveObject() !== obj) {
            obj.set({
              shadow: new fabric.Shadow({
                color: "rgba(25, 118, 210, 0.25)",
                blur: 8,
                offsetX: 0,
                offsetY: 0,
              }),
            });
            fabricRef.current?.renderAll();
          }
        }
      },
      [],
    );

    const handleMouseOut = useCallback(
      (e: { target?: fabric.FabricObject }) => {
        const obj = e.target as FabricObjectWithData | undefined;
        if (obj) {
          if (fabricRef.current?.getActiveObject() !== obj) {
            obj.set({ shadow: undefined });
            fabricRef.current?.renderAll();
          }
        }
      },
      [],
    );

    // Custom multi-selection: mouse down handler
    const handleMouseDown = useCallback(
      (e: { e: MouseEvent; target?: fabric.FabricObject }) => {
        if (!fabricRef.current) return;
        const canvas = fabricRef.current;
        const pointer = canvas.getScenePoint(e.e);

        // If clicking on an object, handle normal selection or multi-drag
        if (e.target) {
          const objData = (e.target as FabricObjectWithData).data;
          if (
            objData?.id &&
            !objData.isGrid &&
            !objData.isMargin &&
            !objData.isGuide
          ) {
            const currentSelectedIds = selectedElementIdsRef.current;

            // Check if this object is already in multi-selection
            if (
              currentSelectedIds.length > 1 &&
              currentSelectedIds.includes(objData.id as string)
            ) {
              // Start dragging all selected objects
              isDraggingMultiRef.current = true;
              dragStartPosRef.current = { x: pointer.x, y: pointer.y };

              // Store references to all selected objects
              multiSelectedObjectsRef.current = [];
              for (const id of currentSelectedIds) {
                const obj = elementsMapRef.current.get(id);
                if (obj) {
                  multiSelectedObjectsRef.current.push(obj);
                }
              }
              return;
            }

            // Shift+click to add/remove from selection
            if (e.e.shiftKey) {
              if (currentSelectedIds.includes(objData.id as string)) {
                // Remove from selection
                onSelectElementRef.current(
                  currentSelectedIds.filter((id) => id !== objData.id),
                );
              } else {
                // Add to selection
                onSelectElementRef.current([
                  ...currentSelectedIds,
                  objData.id as string,
                ]);
              }
              return;
            }

            // Normal click on object - select just this one
            onSelectElementRef.current([objData.id as string]);
          }
          return;
        }

        // Clicking on empty canvas - clear selection and start drawing selection rectangle
        onSelectElementRef.current([]);
        isDrawingSelectionRef.current = true;
        selectionStartRef.current = { x: pointer.x, y: pointer.y };

        // Create selection rectangle
        const selectionRect = new fabric.Rect({
          left: pointer.x,
          top: pointer.y,
          width: 0,
          height: 0,
          fill: "rgba(25, 118, 210, 0.1)",
          stroke: "#1976d2",
          strokeWidth: 1,
          strokeDashArray: [4, 4],
          selectable: false,
          evented: false,
          excludeFromExport: true,
        });
        (selectionRect as FabricObjectWithData).data = {
          isSelectionRect: true,
        };
        selectionRectRef.current = selectionRect;
        canvas.add(selectionRect);
        canvas.renderAll();
      },
      [], // No dependencies - uses refs
    );

    const handleMouseMove = useCallback(
      (e: { e: MouseEvent }) => {
        if (!fabricRef.current) return;
        const canvas = fabricRef.current;
        const pointer = canvas.getScenePoint(e.e);
        const xMm = Math.round((pxToMm(pointer.x) / zoom) * 10) / 10;
        const yMm = Math.round((pxToMm(pointer.y) / zoom) * 10) / 10;
        setCursorPosition({ x: xMm, y: yMm });

        // Send cursor position for collaboration
        if (onCursorMove) {
          onCursorMove(xMm, yMm);
        }

        // Handle multi-drag
        if (isDraggingMultiRef.current && dragStartPosRef.current) {
          const dx = pointer.x - dragStartPosRef.current.x;
          const dy = pointer.y - dragStartPosRef.current.y;

          for (const obj of multiSelectedObjectsRef.current) {
            obj.set({
              left: (obj.left || 0) + dx,
              top: (obj.top || 0) + dy,
            });
            obj.setCoords();
          }

          dragStartPosRef.current = { x: pointer.x, y: pointer.y };
          canvas.renderAll();
          return;
        }

        // Handle selection rectangle drawing
        if (
          isDrawingSelectionRef.current &&
          selectionStartRef.current &&
          selectionRectRef.current
        ) {
          const startX = selectionStartRef.current.x;
          const startY = selectionStartRef.current.y;

          const left = Math.min(startX, pointer.x);
          const top = Math.min(startY, pointer.y);
          const width = Math.abs(pointer.x - startX);
          const height = Math.abs(pointer.y - startY);

          selectionRectRef.current.set({
            left,
            top,
            width,
            height,
          });
          canvas.renderAll();
        }
      },
      [zoom, onCursorMove],
    );

    // Custom multi-selection: mouse up handler
    const handleMouseUp = useCallback(() => {
      if (!fabricRef.current) return;
      const canvas = fabricRef.current;

      // Handle end of multi-drag
      if (isDraggingMultiRef.current) {
        isDraggingMultiRef.current = false;
        dragStartPosRef.current = null;

        // Update all moved objects in the template
        for (const obj of multiSelectedObjectsRef.current) {
          const objData = obj.data;
          if (objData?.id) {
            const scaleX = obj.scaleX || 1;
            const scaleY = obj.scaleY || 1;
            const finalWidth = (obj.width || 0) * scaleX;
            const finalHeight = (obj.height || 0) * scaleY;

            onUpdateElementRef.current(objData.id as string, {
              position: {
                x: pxToMm(obj.left || 0) / zoomRef.current,
                y: pxToMm(obj.top || 0) / zoomRef.current,
                width: pxToMm(finalWidth) / zoomRef.current,
                height: pxToMm(finalHeight) / zoomRef.current,
                rotation: obj.angle || 0,
              },
            });
          }
        }

        // Commit to history
        onUpdateElementCompleteRef.current?.();
        multiSelectedObjectsRef.current = [];
        return;
      }

      // Handle end of selection rectangle
      if (isDrawingSelectionRef.current && selectionRectRef.current) {
        const selRect = selectionRectRef.current;
        const rectLeft = selRect.left || 0;
        const rectTop = selRect.top || 0;
        const rectRight = rectLeft + (selRect.width || 0);
        const rectBottom = rectTop + (selRect.height || 0);

        // Find all objects within the selection rectangle
        const selectedIds: string[] = [];
        const objects = canvas.getObjects();

        for (const obj of objects) {
          const objData = (obj as FabricObjectWithData).data;
          if (
            objData?.id &&
            !objData.isGrid &&
            !objData.isMargin &&
            !objData.isGuide &&
            !objData.isSelectionRect
          ) {
            const objLeft = obj.left || 0;
            const objTop = obj.top || 0;
            const objRight = objLeft + (obj.width || 0) * (obj.scaleX || 1);
            const objBottom = objTop + (obj.height || 0) * (obj.scaleY || 1);

            // Check if object intersects with selection rectangle
            if (
              objLeft < rectRight &&
              objRight > rectLeft &&
              objTop < rectBottom &&
              objBottom > rectTop
            ) {
              selectedIds.push(objData.id as string);
            }
          }
        }

        // Remove selection rectangle
        canvas.remove(selRect);
        selectionRectRef.current = null;
        isDrawingSelectionRef.current = false;
        selectionStartRef.current = null;

        // Update selection
        onSelectElementRef.current(selectedIds);

        canvas.renderAll();
      }
    }, []); // No dependencies - uses refs

    // Keyboard navigation
    const handleKeyDown = useCallback(
      (e: KeyboardEvent) => {
        if (!fabricRef.current) return;
        const activeObj = fabricRef.current.getActiveObject();
        if (!activeObj) return;

        if ((activeObj as fabric.Textbox).isEditing) return;

        const MOVE_STEP = e.shiftKey ? 10 : 1;
        let handled = false;

        switch (e.key) {
          case "ArrowUp":
            activeObj.set("top", (activeObj.top || 0) - MOVE_STEP * zoom);
            handled = true;
            break;
          case "ArrowDown":
            activeObj.set("top", (activeObj.top || 0) + MOVE_STEP * zoom);
            handled = true;
            break;
          case "ArrowLeft":
            activeObj.set("left", (activeObj.left || 0) - MOVE_STEP * zoom);
            handled = true;
            break;
          case "ArrowRight":
            activeObj.set("left", (activeObj.left || 0) + MOVE_STEP * zoom);
            handled = true;
            break;
        }

        if (handled) {
          e.preventDefault();
          activeObj.setCoords();
          fabricRef.current.renderAll();

          const objData = (activeObj as FabricObjectWithData).data;
          if (objData?.id) {
            const updates: Partial<AprtElement> = {
              position: {
                x: pxToMm(activeObj.left || 0) / zoom,
                y: pxToMm(activeObj.top || 0) / zoom,
                width: pxToMm(activeObj.width || 0) / zoom,
                height: pxToMm(activeObj.height || 0) / zoom,
                rotation: activeObj.angle || 0,
              },
            };
            onUpdateElement(objData.id as string, updates);
            // Commit to history after keyboard movement
            onUpdateElementComplete?.();
          }
        }
      },
      [zoom, onUpdateElement, onUpdateElementComplete],
    );

    // Initialize Fabric canvas
    useEffect(() => {
      if (!canvasRef.current || fabricRef.current) return;

      const canvas = new fabric.Canvas(canvasRef.current, {
        width: canvasWidth * zoom,
        height: canvasHeight * zoom,
        backgroundColor: "#ffffff",
        selection: false, // Disable native multi-selection (causes object repositioning issues)
        preserveObjectStacking: true,
        uniformScaling: false, // Allow free resize from corners (not locked to aspect ratio)
      });

      // Configure default controls
      fabric.FabricObject.prototype.set({
        transparentCorners: false,
        cornerColor: "#ffffff",
        cornerStrokeColor: "#1976d2",
        borderColor: "#1976d2",
        cornerSize: 10,
        cornerStyle: "circle",
        borderScaleFactor: 1.5,
        centeredScaling: false, // Scale from the corner, not center
      });

      fabricRef.current = canvas;

      // Events
      canvas.on("selection:created", handleSelectionCreated as any);
      canvas.on("selection:updated", handleSelectionCreated as any);
      canvas.on("selection:cleared", handleSelectionCleared);
      canvas.on("object:moving", handleObjectMoving as any);
      canvas.on("object:modified", handleObjectModified as any);
      canvas.on("object:scaling", handleObjectScaling as any);
      canvas.on("mouse:over", handleMouseOver as any);
      canvas.on("mouse:out", handleMouseOut as any);
      canvas.on("mouse:move", handleMouseMove as any);
      canvas.on("mouse:down", handleMouseDown as any);
      canvas.on("mouse:up", handleMouseUp as any);
      canvas.on("text:changed", handleTextChanged as any);

      window.addEventListener("keydown", handleKeyDown);

      return () => {
        canvas.off("selection:created");
        canvas.off("selection:updated");
        canvas.off("selection:cleared");
        canvas.off("object:moving");
        canvas.off("object:modified");
        canvas.off("object:scaling");
        canvas.off("mouse:over");
        canvas.off("mouse:out");
        canvas.off("mouse:move");
        canvas.off("mouse:down");
        canvas.off("mouse:up");
        canvas.off("text:changed");
        window.removeEventListener("keydown", handleKeyDown);
        canvas.dispose();
        fabricRef.current = null;
        elementsMapRef.current.clear();
      };
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    // Update canvas size on zoom change
    useEffect(() => {
      if (!fabricRef.current) return;

      fabricRef.current.setDimensions({
        width: canvasWidth * zoom,
        height: canvasHeight * zoom,
      });

      fabricRef.current.setZoom(zoom);
      fabricRef.current.renderAll();
    }, [zoom, canvasWidth, canvasHeight]);

    // Render grid
    useEffect(() => {
      if (!fabricRef.current) return;

      // Remove existing grid
      const existingGrid = fabricRef.current
        .getObjects()
        .filter((o) => (o as FabricObjectWithData).data?.isGrid);
      existingGrid.forEach((o) => fabricRef.current?.remove(o));

      if (showGrid) {
        const gridPx = mmToPx(gridSize);

        for (let x = 0; x <= canvasWidth; x += gridPx) {
          const line = new fabric.Line(
            [x * zoom, 0, x * zoom, canvasHeight * zoom],
            {
              stroke: "#e0e0e0",
              strokeWidth: 1,
              selectable: false,
              evented: false,
              excludeFromExport: true,
            },
          );
          (line as FabricObjectWithData).data = { isGrid: true };
          fabricRef.current.add(line);
          fabricRef.current.sendObjectToBack(line);
        }

        for (let y = 0; y <= canvasHeight; y += gridPx) {
          const line = new fabric.Line(
            [0, y * zoom, canvasWidth * zoom, y * zoom],
            {
              stroke: "#e0e0e0",
              strokeWidth: 1,
              selectable: false,
              evented: false,
              excludeFromExport: true,
            },
          );
          (line as FabricObjectWithData).data = { isGrid: true };
          fabricRef.current.add(line);
          fabricRef.current.sendObjectToBack(line);
        }
      }

      fabricRef.current.renderAll();
    }, [showGrid, gridSize, zoom, canvasWidth, canvasHeight]);

    // Render margins
    useEffect(() => {
      if (!fabricRef.current) return;

      const existingMargins = fabricRef.current
        .getObjects()
        .filter((o) => (o as FabricObjectWithData).data?.isMargin);
      existingMargins.forEach((o) => fabricRef.current?.remove(o));

      const margins = template.meta.margins;
      const marginConfig = {
        stroke: "rgba(33, 150, 243, 0.4)",
        strokeWidth: 1,
        strokeDashArray: [5, 5] as number[],
        selectable: false,
        evented: false,
        excludeFromExport: true,
      };

      const lines = [
        new fabric.Line(
          [
            0,
            mmToPx(margins.top) * zoom,
            canvasWidth * zoom,
            mmToPx(margins.top) * zoom,
          ],
          marginConfig,
        ),
        new fabric.Line(
          [
            0,
            (canvasHeight - mmToPx(margins.bottom)) * zoom,
            canvasWidth * zoom,
            (canvasHeight - mmToPx(margins.bottom)) * zoom,
          ],
          marginConfig,
        ),
        new fabric.Line(
          [
            mmToPx(margins.left) * zoom,
            0,
            mmToPx(margins.left) * zoom,
            canvasHeight * zoom,
          ],
          marginConfig,
        ),
        new fabric.Line(
          [
            (canvasWidth - mmToPx(margins.right)) * zoom,
            0,
            (canvasWidth - mmToPx(margins.right)) * zoom,
            canvasHeight * zoom,
          ],
          marginConfig,
        ),
      ];

      lines.forEach((line) => {
        (line as FabricObjectWithData).data = { isMargin: true };
        fabricRef.current?.add(line);
        fabricRef.current?.sendObjectToBack(line);
      });

      fabricRef.current.renderAll();
    }, [template.meta.margins, zoom, canvasWidth, canvasHeight]);

    // Sync elements from template to canvas
    useEffect(() => {
      if (!fabricRef.current) return;

      // Don't sync while user is actively transforming (dragging/scaling)
      // This prevents the canvas from fighting with user input
      if (isTransformingRef.current) return;

      isUpdatingRef.current = true;

      const canvas = fabricRef.current;
      const currentElementIds = new Set(template.elements.map((e) => e.id));
      const renderedElementIds = new Set(elementsMapRef.current.keys());

      // Remove deleted elements
      renderedElementIds.forEach((id) => {
        if (!currentElementIds.has(id)) {
          const obj = elementsMapRef.current.get(id);
          if (obj) {
            canvas.remove(obj);
            elementsMapRef.current.delete(id);
          }
        }
      });

      // Add or update elements
      template.elements.forEach((element) => {
        if (!element.visible) {
          const obj = elementsMapRef.current.get(element.id);
          if (obj) {
            canvas.remove(obj);
            elementsMapRef.current.delete(element.id);
          }
          return;
        }

        const existingObj = elementsMapRef.current.get(element.id);

        if (existingObj) {
          updateFabricObject(existingObj, element, zoom);
        } else {
          const obj = createFabricObject(element, zoom);
          if (obj) {
            canvas.add(obj);
            elementsMapRef.current.set(element.id, obj as FabricObjectWithData);
          }
        }
      });

      // Reorder objects on canvas to match template.elements order
      // Elements at the end of the array should be on top (higher z-index)
      // Get non-helper objects count (grid, margins, guides are at the bottom)
      const helperObjects = canvas.getObjects().filter((o) => {
        const d = (o as FabricObjectWithData).data;
        return d?.isGrid || d?.isMargin || d?.isGuide;
      }).length;

      template.elements.forEach((element, index) => {
        const obj = elementsMapRef.current.get(element.id);
        if (obj) {
          // Move to correct position - account for grid/margin lines at the bottom
          const targetIndex = helperObjects + index;
          const currentIndex = canvas.getObjects().indexOf(obj);
          if (currentIndex !== targetIndex && currentIndex !== -1) {
            // Remove and re-add at correct position
            canvas.remove(obj);
            canvas.insertAt(targetIndex, obj);
          }
        }
      });

      canvas.renderAll();
      isUpdatingRef.current = false;
    }, [template.elements, zoom]);

    // Load images asynchronously and replace placeholders
    useEffect(() => {
      if (!fabricRef.current) return;

      // Don't update images while user is actively transforming
      if (isTransformingRef.current) return;

      const canvas = fabricRef.current;

      // Clean up loadedImagesRef for elements that no longer exist
      const currentElementIds = new Set(template.elements.map((e) => e.id));
      for (const elementId of loadedImagesRef.current.keys()) {
        if (!currentElementIds.has(elementId)) {
          loadedImagesRef.current.delete(elementId);
          loadingImagesRef.current.delete(elementId);
        }
      }

      // Find image elements that need loading
      template.elements.forEach((element) => {
        if (element.type !== "image" || !element.imageSettings?.src) return;

        const currentSrc = element.imageSettings.src;
        const loadedSrc = loadedImagesRef.current.get(element.id);

        // Skip if already loaded with same src AND object exists as FabricImage
        const existingObj = elementsMapRef.current.get(element.id);
        if (
          loadedSrc === currentSrc &&
          existingObj instanceof fabric.FabricImage
        ) {
          // Image already loaded - just update position/size if needed
          const pos = element.position;
          const targetWidth = mmToPx(pos.width) * zoom;
          const targetHeight = mmToPx(pos.height) * zoom;
          const targetLeft = mmToPx(pos.x) * zoom;
          const targetTop = mmToPx(pos.y) * zoom;

          // Only update if significantly different (avoid infinite loops)
          const currentWidth =
            (existingObj.width || 0) * (existingObj.scaleX || 1);
          const currentHeight =
            (existingObj.height || 0) * (existingObj.scaleY || 1);

          if (
            Math.abs(currentWidth - targetWidth) > 1 ||
            Math.abs(currentHeight - targetHeight) > 1 ||
            Math.abs((existingObj.left || 0) - targetLeft) > 1 ||
            Math.abs((existingObj.top || 0) - targetTop) > 1
          ) {
            const imgNaturalWidth = existingObj.width || 100;
            const imgNaturalHeight = existingObj.height || 100;

            existingObj.set({
              left: targetLeft,
              top: targetTop,
              scaleX: targetWidth / imgNaturalWidth,
              scaleY: targetHeight / imgNaturalHeight,
              angle: pos.rotation || 0,
            });
            existingObj.setCoords();
            canvas.renderAll();
          }
          return;
        }

        // Skip if this image is already being loaded
        if (loadingImagesRef.current.has(element.id)) {
          return;
        }

        if (!existingObj) return;

        // Mark as loading to prevent duplicate loads
        loadingImagesRef.current.add(element.id);

        // Capture element data for async callback
        const elementId = element.id;
        const elementType = element.type;

        // Load the image
        const imgElement = new Image();
        imgElement.crossOrigin = "anonymous";
        imgElement.onload = () => {
          // Remove from loading set
          loadingImagesRef.current.delete(elementId);

          if (!fabricRef.current) return;

          // Re-fetch current element data from template (might have changed during load)
          const currentElement = templateElementsRef.current.find(
            (el) => el.id === elementId,
          );
          if (!currentElement || currentElement.type !== "image") return;

          // Re-fetch the current object from the map (might have been recreated)
          const currentObj = elementsMapRef.current.get(elementId);
          if (!currentObj) return;

          // Skip if already a FabricImage with correct src (another load completed first)
          if (
            currentObj instanceof fabric.FabricImage &&
            loadedImagesRef.current.get(elementId) === currentSrc
          ) {
            return;
          }

          const pos = currentElement.position;
          const imgSettings = currentElement.imageSettings!;
          const targetWidth = mmToPx(pos.width) * zoom;
          const targetHeight = mmToPx(pos.height) * zoom;
          const imgNaturalWidth = imgElement.naturalWidth;
          const imgNaturalHeight = imgElement.naturalHeight;

          // Simple approach: scale image to fit the target dimensions
          const scaleX = targetWidth / imgNaturalWidth;
          const scaleY = targetHeight / imgNaturalHeight;

          const fabricImage = new fabric.FabricImage(imgElement, {
            left: mmToPx(pos.x) * zoom,
            top: mmToPx(pos.y) * zoom,
            angle: pos.rotation || 0,
            scaleX: scaleX,
            scaleY: scaleY,
            opacity: imgSettings.opacity ?? 1,
            flipX: imgSettings.flipHorizontal || false,
            flipY: imgSettings.flipVertical || false,
            hasControls: !currentElement.locked,
            hasBorders: !currentElement.locked,
            lockMovementX: currentElement.locked,
            lockMovementY: currentElement.locked,
            lockRotation: currentElement.locked,
            lockScalingX: currentElement.locked,
            lockScalingY: currentElement.locked,
          });

          (fabricImage as FabricObjectWithData).data = {
            id: elementId,
            type: elementType,
          };

          // Remove the current placeholder/old image and add the new one
          canvas.remove(currentObj);
          canvas.add(fabricImage);
          elementsMapRef.current.set(
            elementId,
            fabricImage as FabricObjectWithData,
          );
          loadedImagesRef.current.set(elementId, currentSrc);

          canvas.renderAll();
        };
        imgElement.onerror = () => {
          // Remove from loading set on error too
          loadingImagesRef.current.delete(elementId);
          console.error(`Failed to load image for element ${elementId}`);
        };
        imgElement.src = currentSrc;
      });
    }, [template.elements, zoom]);

    // Update visual selection feedback when selectedElementIds changes
    useEffect(() => {
      if (!fabricRef.current) return;

      const canvas = fabricRef.current;

      // Clear all selection styling first
      const allObjects = canvas.getObjects();
      for (const obj of allObjects) {
        const objData = (obj as FabricObjectWithData).data;
        if (
          objData?.id &&
          !objData.isGrid &&
          !objData.isMargin &&
          !objData.isGuide &&
          !objData.isSelectionRect
        ) {
          // Reset to default styling
          obj.set({
            strokeWidth:
              obj.type === "line"
                ? obj.strokeWidth || 1
                : (objData.originalStrokeWidth ?? 0),
            stroke:
              objData.originalStroke ?? (obj.type === "line" ? obj.stroke : ""),
            shadow: undefined,
          });
        }
      }

      if (selectedElementIds.length === 0) {
        canvas.discardActiveObject();
        canvas.renderAll();
        return;
      }

      if (selectedElementIds.length === 1) {
        // Single selection - use Fabric's native selection (with controls)
        const obj = elementsMapRef.current.get(selectedElementIds[0]);
        if (obj && canvas.getActiveObject() !== obj) {
          canvas.setActiveObject(obj);
        }
      } else {
        // Multi-selection - show visual feedback on each selected object
        canvas.discardActiveObject(); // Clear native selection

        for (const id of selectedElementIds) {
          const obj = elementsMapRef.current.get(id);
          if (obj) {
            const objData = obj.data;
            // Store original values if not already stored
            if (objData && objData.originalStrokeWidth === undefined) {
              objData.originalStrokeWidth = obj.strokeWidth || 0;
              objData.originalStroke = obj.stroke || "";
            }

            // Apply selection styling - blue border and shadow
            obj.set({
              stroke: "#1976d2",
              strokeWidth: 2,
              shadow: new fabric.Shadow({
                color: "rgba(25, 118, 210, 0.4)",
                blur: 10,
                offsetX: 0,
                offsetY: 0,
              }),
            });
          }
        }
      }

      canvas.renderAll();
    }, [selectedElementIds]);

    return (
      <Box
        sx={{
          flex: 1,
          overflow: "auto",
          width: "100%",
          display: "flex",
          justifyContent: "center",
          alignItems: "flex-start",
          bgcolor: "#f5f5f5",
          p: 3,
          position: "relative",
        }}
      >
        {/* Cursor position indicator */}
        <Fade in={true}>
          <Box
            sx={{
              position: "fixed",
              bottom: 16,
              left: "50%",
              transform: "translateX(-50%)",
              bgcolor: "rgba(0,0,0,0.7)",
              color: "white",
              px: 2,
              py: 0.5,
              borderRadius: 2,
              fontSize: "0.75rem",
              fontFamily: "monospace",
              zIndex: 1000,
              display: "flex",
              gap: 2,
              alignItems: "center",
            }}
          >
            <span>X: {cursorPosition.x} mm</span>
            <span>Y: {cursorPosition.y} mm</span>
            <span style={{ opacity: 0.6 }}>|</span>
            <span>Zoom: {Math.round(zoom * 100)}%</span>
          </Box>
        </Fade>

        {/* Canvas container */}
        <Box
          onContextMenu={(e) => {
            e.preventDefault();
            e.stopPropagation();

            if (!onContextMenu || !fabricRef.current) return;

            const canvas = fabricRef.current;
            const canvasEl = canvas.getElement();

            // Create a fake event for findTarget
            const fakeEvent = {
              clientX: e.clientX,
              clientY: e.clientY,
              target: canvasEl,
            } as unknown as MouseEvent;

            // Find object at pointer position
            const obj = canvas.findTarget(fakeEvent) as
              | FabricObjectWithData
              | undefined;
            const objData = obj?.data;

            // If right-clicked on an element, select it first
            if (
              objData?.id &&
              !objData.isGrid &&
              !objData.isMargin &&
              !objData.isGuide
            ) {
              if (canvas.getActiveObject() !== obj) {
                canvas.setActiveObject(obj!);
                canvas.renderAll();
              }
              onContextMenu({
                position: { x: e.clientX, y: e.clientY },
                elementId: objData.id as string,
              });
            } else {
              // Right-clicked on empty canvas
              onContextMenu({
                position: { x: e.clientX, y: e.clientY },
                elementId: null,
              });
            }
          }}
          sx={{
            boxShadow: "0 8px 32px rgba(0,0,0,0.12)",
            bgcolor: "white",
            position: "relative",
            borderRadius: "2px",
          }}
        >
          <canvas ref={canvasRef} />

          {/* Page size label */}
          <Typography
            variant="caption"
            sx={{
              position: "absolute",
              bottom: -24,
              left: "50%",
              transform: "translateX(-50%)",
              color: "text.secondary",
              fontSize: "0.7rem",
              whiteSpace: "nowrap",
            }}
          >
            {template.meta.pageSize}{" "}
            {template.meta.orientation === "landscape"
              ? "Orizzontale"
              : "Verticale"}{" "}
            • {pageDims.width} × {pageDims.height} mm
          </Typography>
        </Box>
      </Box>
    );
  },
);

function createFabricObject(
  element: AprtElement,
  zoom: number = 1,
): fabric.FabricObject | null {
  const pos = element.position;
  const style = { ...defaultStyle, ...element.style };

  const baseProps = {
    left: mmToPx(pos.x) * zoom,
    top: mmToPx(pos.y) * zoom,
    angle: pos.rotation || 0,
    hasControls: !element.locked,
    hasBorders: !element.locked,
    lockMovementX: element.locked,
    lockMovementY: element.locked,
    lockRotation: element.locked,
    lockScalingX: element.locked,
    lockScalingY: element.locked,
  };

  let obj: fabric.FabricObject | null = null;

  switch (element.type) {
    case "text": {
      const text =
        element.content?.value ||
        element.content?.expression ||
        "Doppio click per modificare";
      obj = new fabric.Textbox(text, {
        ...baseProps,
        width: mmToPx(pos.width) * zoom,
        fontSize: style.fontSize * zoom,
        fontFamily: style.fontFamily,
        fontWeight: style.fontWeight === "bold" ? "bold" : "normal",
        fontStyle: style.fontStyle === "italic" ? "italic" : "normal",
        fill: style.color,
        backgroundColor: style.backgroundColor || "",
        textAlign: style.textAlign as any,
        lineHeight: style.lineHeight,
        opacity: style.opacity,
        editable: true,
        splitByGrapheme: false,
      });
      break;
    }

    case "shape": {
      obj = new fabric.Rect({
        ...baseProps,
        width: mmToPx(pos.width) * zoom,
        height: mmToPx(pos.height) * zoom,
        fill: style.backgroundColor || "#e0e0e0",
        stroke: style.borderWidth > 0 ? style.borderColor : "",
        strokeWidth: style.borderWidth * zoom,
        opacity: style.opacity,
        rx: 0,
        ry: 0,
      });
      break;
    }

    case "line": {
      obj = new fabric.Line([0, 0, mmToPx(pos.width) * zoom, 0], {
        ...baseProps,
        stroke: style.color || "#000000",
        strokeWidth: Math.max(1, (style.borderWidth || 1) * zoom),
        opacity: style.opacity,
      });
      break;
    }

    case "image": {
      // Always create a placeholder initially - image loading handled separately
      obj = new fabric.Rect({
        ...baseProps,
        width: mmToPx(pos.width) * zoom,
        height: mmToPx(pos.height) * zoom,
        fill: element.imageSettings?.src ? "#f0f0f0" : "#f5f5f5",
        stroke: "#bdbdbd",
        strokeWidth: 1,
        strokeDashArray: element.imageSettings?.src ? undefined : [5, 5],
      });
      break;
    }

    case "table": {
      obj = createTablePlaceholder(element, zoom, baseProps);
      break;
    }

    default:
      return null;
  }

  if (obj) {
    (obj as FabricObjectWithData).data = { id: element.id, type: element.type };
  }

  return obj;
}

function createTablePlaceholder(
  element: AprtElement,
  zoom: number,
  baseProps: Record<string, unknown>,
): fabric.Group {
  const pos = element.position;
  const tableWidth = mmToPx(pos.width) * zoom;
  const tableHeight = mmToPx(pos.height) * zoom;
  const cols = element.columns?.length || 3;
  const rows = 4;
  const colWidth = tableWidth / cols;
  const rowHeight = tableHeight / rows;

  const objects: fabric.FabricObject[] = [];

  // Background
  objects.push(
    new fabric.Rect({
      left: 0,
      top: 0,
      width: tableWidth,
      height: tableHeight,
      fill: "#ffffff",
      stroke: "#424242",
      strokeWidth: 1,
    }),
  );

  // Header background
  objects.push(
    new fabric.Rect({
      left: 0,
      top: 0,
      width: tableWidth,
      height: rowHeight,
      fill: "#e3f2fd",
    }),
  );

  // Column lines
  for (let i = 1; i < cols; i++) {
    objects.push(
      new fabric.Line([colWidth * i, 0, colWidth * i, tableHeight], {
        stroke: "#bdbdbd",
        strokeWidth: 1,
      }),
    );
  }

  // Row lines
  for (let i = 1; i < rows; i++) {
    objects.push(
      new fabric.Line([0, rowHeight * i, tableWidth, rowHeight * i], {
        stroke: "#bdbdbd",
        strokeWidth: 1,
      }),
    );
  }

  // Header text
  element.columns?.forEach((col, idx) => {
    objects.push(
      new fabric.Text(col.header || `Col ${idx + 1}`, {
        left: colWidth * idx + 5,
        top: 5,
        fontSize: 11 * zoom,
        fontWeight: "bold",
        fill: "#1565c0",
      }),
    );
  });

  const group = new fabric.Group(objects, {
    ...baseProps,
    subTargetCheck: false,
  } as any);

  return group;
}

function updateFabricObject(
  obj: FabricObjectWithData,
  element: AprtElement,
  zoom: number,
): void {
  const pos = element.position;
  const style = { ...defaultStyle, ...element.style };

  obj.set({
    left: mmToPx(pos.x) * zoom,
    top: mmToPx(pos.y) * zoom,
    angle: pos.rotation || 0,
    hasControls: !element.locked,
    hasBorders: !element.locked,
    lockMovementX: element.locked,
    lockMovementY: element.locked,
    lockRotation: element.locked,
    lockScalingX: element.locked,
    lockScalingY: element.locked,
  });

  if (obj instanceof fabric.Textbox) {
    const newText = element.content?.value || element.content?.expression || "";
    if (obj.text !== newText && !obj.isEditing) {
      obj.set("text", newText);
    }
    obj.set({
      width: mmToPx(pos.width) * zoom,
      fontSize: style.fontSize * zoom,
      fontFamily: style.fontFamily,
      fontWeight: style.fontWeight === "bold" ? "bold" : "normal",
      fontStyle: style.fontStyle === "italic" ? "italic" : "normal",
      fill: style.color,
      backgroundColor: style.backgroundColor || "",
      textAlign: style.textAlign as any,
      opacity: style.opacity,
    });
  } else if (obj instanceof fabric.Rect && element.type === "shape") {
    obj.set({
      width: mmToPx(pos.width) * zoom,
      height: mmToPx(pos.height) * zoom,
      fill: style.backgroundColor || "#e0e0e0",
      stroke: style.borderWidth > 0 ? style.borderColor : "",
      strokeWidth: style.borderWidth * zoom,
      opacity: style.opacity,
    });
  } else if (obj instanceof fabric.FabricImage) {
    // For FabricImage, use scale to set dimensions (width/height are native image size)
    const targetWidth = mmToPx(pos.width) * zoom;
    const targetHeight = mmToPx(pos.height) * zoom;
    const imgNaturalWidth = obj.width || 100;
    const imgNaturalHeight = obj.height || 100;

    obj.set({
      scaleX: targetWidth / imgNaturalWidth,
      scaleY: targetHeight / imgNaturalHeight,
      opacity: element.imageSettings?.opacity ?? 1,
      flipX: element.imageSettings?.flipHorizontal || false,
      flipY: element.imageSettings?.flipVertical || false,
    });
  }

  obj.setCoords();
}

EditorCanvas.displayName = "EditorCanvas";

export default EditorCanvas;
