import { useMemo, useRef, useState } from "react";
import {
  clientPointToMap,
  getObjectKey,
  itemMatchesMapFilters,
} from "../../lib/locationMapUtils";
import LocationMapObject from "./LocationMapObject";

const DRAG_CHANGE_THRESHOLD = 2;
const NUDGE_KEYS = new Set(["ArrowUp", "ArrowRight", "ArrowDown", "ArrowLeft"]);

export default function LocationMapCanvas({
  mode,
  editorTool,
  objects,
  filters,
  mapSize,
  zoom,
  selectedObjectKey,
  mapItems,
  username,
  svgRef,
  dragState,
  editingLocked = false,
  hiddenAreaCount = 0,
  onAddObject,
  onShowZones,
  onShowHiddenAreas,
  setDragState,
  setSelectedObjectKey,
  remember,
  updateObjects,
}) {
  const canEditObjects = !editingLocked && mode === "edit" && editorTool === "edit";
  const canDragPan = mode === "view" || editorTool === "pan" || canEditObjects;
  const hiddenByZonesLayer = objects.length > 0 && !filters.showZones;
  const visibleObjectCount = filters.showZones
    ? objects.filter((object) => object.visible).length
    : 0;
  const hasNoVisibleObjects = objects.length > 0 && filters.showZones && visibleObjectCount === 0;
  const canAddFirstArea =
    mode === "edit" && objects.length === 0 && typeof onAddObject === "function";
  const canRecoverHiddenAreas =
    mode === "edit" && hasNoVisibleObjects && hiddenAreaCount > 0 && typeof onShowHiddenAreas === "function";
  const hasEmptyCanvasAction = canAddFirstArea || hiddenByZonesLayer || canRecoverHiddenAreas;
  const emptyCanvasMessage = objects.length === 0
    ? "No map areas yet"
    : hiddenByZonesLayer
      ? "Zones hidden in Layers"
      : hasNoVisibleObjects
        ? "No visible map areas"
        : "";
  const scrollRef = useRef(null);
  const panDragRef = useRef(null);
  const objectDragHistoryCapturedRef = useRef(false);
  const suppressPanClickRef = useRef(false);
  const [isPanning, setIsPanning] = useState(false);
  const orderedObjects = useMemo(() => {
    if (!selectedObjectKey) return objects;

    const selectedObjects = [];
    const remainingObjects = [];
    objects.forEach((object) => {
      if (getObjectKey(object) === selectedObjectKey) {
        selectedObjects.push(object);
        return;
      }
      remainingObjects.push(object);
    });

    return [...remainingObjects, ...selectedObjects];
  }, [objects, selectedObjectKey]);
  const zoneItemSummary = useMemo(() => {
    const assignedCounts = new Map();
    const visibleItemsByZone = new Map();

    (mapItems || []).forEach((item) => {
      const zoneKey = String(item.zone_id || "");
      if (!zoneKey) return;

      assignedCounts.set(zoneKey, (assignedCounts.get(zoneKey) || 0) + 1);
      if (!itemMatchesMapFilters(item, filters, username)) return;

      const existingItems = visibleItemsByZone.get(zoneKey) || [];
      existingItems.push(item);
      visibleItemsByZone.set(zoneKey, existingItems);
    });

    return { assignedCounts, visibleItemsByZone };
  }, [filters, mapItems, username]);

  const startDrag = (event, object, type) => {
    if (!canEditObjects || object.locked || !svgRef.current) return;
    event.stopPropagation();
    event.preventDefault();
    objectDragHistoryCapturedRef.current = false;
    const point = clientPointToMap(event, svgRef.current, mapSize);
    event.currentTarget.setPointerCapture?.(event.pointerId);
    setSelectedObjectKey(getObjectKey(object));
    setDragState({
      type,
      key: getObjectKey(object),
      startPoint: point,
      startObject: { ...object },
    });
  };

  const handlePointerMove = (event) => {
    if (!dragState || !svgRef.current) return;
    event.preventDefault();
    const point = clientPointToMap(event, svgRef.current, mapSize);
    const deltaX = point.x - dragState.startPoint.x;
    const deltaY = point.y - dragState.startPoint.y;
    const hasMeaningfulChange =
      Math.abs(deltaX) >= DRAG_CHANGE_THRESHOLD ||
      Math.abs(deltaY) >= DRAG_CHANGE_THRESHOLD;

    if (!hasMeaningfulChange) return;
    if (!objectDragHistoryCapturedRef.current) {
      remember();
      objectDragHistoryCapturedRef.current = true;
    }

    updateObjects((currentObjects) =>
      currentObjects.map((object) => {
        if (getObjectKey(object) !== dragState.key) return object;
        if (dragState.type === "resize") {
          return {
            ...object,
            width: Math.max(40, dragState.startObject.width + deltaX),
            height: Math.max(40, dragState.startObject.height + deltaY),
          };
        }
        return {
          ...object,
          x: dragState.startObject.x + deltaX,
          y: dragState.startObject.y + deltaY,
        };
      })
    );
  };

  const handlePointerUp = () => {
    objectDragHistoryCapturedRef.current = false;
    setDragState(null);
  };

  const selectObject = (object) => {
    if (mode === "view" || canEditObjects) {
      setSelectedObjectKey(getObjectKey(object));
    }
  };

  const handleObjectClick = (event, object) => {
    event.stopPropagation();
    if (suppressPanClickRef.current) {
      suppressPanClickRef.current = false;
      return;
    }
    selectObject(object);
  };

  const handleObjectKeyDown = (event, object) => {
    if (event.key === "Escape" && (mode === "view" || canEditObjects)) {
      event.stopPropagation();
      event.preventDefault();
      setSelectedObjectKey(null);
      return;
    }

    if (["Enter", " ", "Spacebar"].includes(event.key)) {
      event.stopPropagation();
      event.preventDefault();
      selectObject(object);
      return;
    }

    if (!canEditObjects || object.locked || !NUDGE_KEYS.has(event.key)) return;

    event.stopPropagation();
    event.preventDefault();
    const nudgeAmount = event.shiftKey ? 25 : 10;
    const deltaX = event.key === "ArrowLeft" ? -nudgeAmount : event.key === "ArrowRight" ? nudgeAmount : 0;
    const deltaY = event.key === "ArrowUp" ? -nudgeAmount : event.key === "ArrowDown" ? nudgeAmount : 0;

    remember();
    setSelectedObjectKey(getObjectKey(object));
    updateObjects((currentObjects) =>
      currentObjects.map((candidate) =>
        getObjectKey(candidate) === getObjectKey(object)
          ? {
            ...candidate,
            x: candidate.x + deltaX,
            y: candidate.y + deltaY,
          }
          : candidate
      )
    );
  };

  const handleShowZones = (event) => {
    event.stopPropagation();
    onShowZones?.();
  };

  const handleAddObject = (event) => {
    event.stopPropagation();
    if (editingLocked) return;
    onAddObject?.();
  };

  const handleShowHiddenAreas = (event) => {
    event.stopPropagation();
    if (editingLocked) return;
    onShowHiddenAreas?.();
  };

  const clearSelection = () => {
    if (suppressPanClickRef.current) {
      suppressPanClickRef.current = false;
      return;
    }
    if (!dragState) {
      setSelectedObjectKey(null);
    }
  };

  const startPan = (event) => {
    if (!canDragPan || !scrollRef.current || event.button !== 0) return;
    const objectElement = event.target.closest?.(".location-map-object");
    if (canEditObjects && objectElement) return;
    event.currentTarget.setPointerCapture?.(event.pointerId);
    panDragRef.current = {
      startX: event.clientX,
      startY: event.clientY,
      scrollLeft: scrollRef.current.scrollLeft,
      scrollTop: scrollRef.current.scrollTop,
      objectKey: objectElement?.getAttribute("data-object-key") || null,
      moved: false,
    };
    setIsPanning(true);
  };

  const movePan = (event) => {
    if (!panDragRef.current || !scrollRef.current) return;
    const deltaX = event.clientX - panDragRef.current.startX;
    const deltaY = event.clientY - panDragRef.current.startY;
    if (Math.abs(deltaX) > 4 || Math.abs(deltaY) > 4) {
      panDragRef.current.moved = true;
      event.preventDefault();
    }
    scrollRef.current.scrollLeft = panDragRef.current.scrollLeft - deltaX;
    scrollRef.current.scrollTop = panDragRef.current.scrollTop - deltaY;
  };

  const endPan = (event) => {
    if (!panDragRef.current) return;
    const wasMoved = panDragRef.current.moved;
    const objectKey = panDragRef.current.objectKey;
    if (!wasMoved && mode === "view" && objectKey) {
      setSelectedObjectKey(objectKey);
      suppressPanClickRef.current = true;
    } else {
      suppressPanClickRef.current = wasMoved;
    }
    panDragRef.current = null;
    setIsPanning(false);
    event.currentTarget.releasePointerCapture?.(event.pointerId);
  };

  const renderMapObject = (object, index) => {
    const key = getObjectKey(object);
    const zoneKey = String(object.zone_id || "");
    const zoneItems = zoneKey ? zoneItemSummary.visibleItemsByZone.get(zoneKey) || [] : [];
    const assignedZoneItemCount = zoneKey ? zoneItemSummary.assignedCounts.get(zoneKey) || 0 : 0;
    return (
      <LocationMapObject
        key={key}
        object={object}
        index={index}
        filters={filters}
        zoneItems={zoneItems}
        assignedZoneItemCount={assignedZoneItemCount}
        isSelected={key === selectedObjectKey}
        canEditObjects={canEditObjects}
        canSelect={mode === "view" || canEditObjects}
        zoom={zoom}
        mapSize={mapSize}
        onStartDrag={startDrag}
        onObjectClick={handleObjectClick}
        onObjectKeyDown={handleObjectKeyDown}
      />
    );
  };

  return (
    <section
      className={[
        "location-map-canvas-shell",
        mode === "edit" ? "is-edit-mode" : "is-view-mode",
        editorTool === "edit" ? "is-object-tool" : "is-pan-tool",
        isPanning ? "is-panning" : "",
      ].filter(Boolean).join(" ")}
    >
      <div
        ref={scrollRef}
        className="location-map-scroll"
        onPointerDown={startPan}
        onPointerMove={movePan}
        onPointerUp={endPan}
        onPointerCancel={endPan}
        onPointerLeave={endPan}
        onClick={clearSelection}
      >
        <svg
          ref={svgRef}
          className={[
            "location-map-svg",
            canEditObjects ? "is-editable" : "",
          ].filter(Boolean).join(" ")}
          style={{
            width: `${mapSize.width * zoom}px`,
            height: `${mapSize.height * zoom}px`,
          }}
          viewBox={`0 0 ${mapSize.width} ${mapSize.height}`}
          role="img"
          aria-label="Store location map"
          onPointerMove={handlePointerMove}
          onPointerUp={handlePointerUp}
          onPointerLeave={handlePointerUp}
          onClick={clearSelection}
        >
          <defs>
            <pattern id="location-map-grid" width="40" height="40" patternUnits="userSpaceOnUse">
              <path d="M 40 0 L 0 0 0 40" fill="none" />
            </pattern>
          </defs>
          <rect className="location-map-background" width={mapSize.width} height={mapSize.height} />
          {mode === "edit" ? (
            <rect className="location-map-grid" width={mapSize.width} height={mapSize.height} />
          ) : null}
          {orderedObjects.map(renderMapObject)}
        </svg>
        {emptyCanvasMessage ? (
          <div
            className={[
              "location-map-empty-canvas",
              hasEmptyCanvasAction ? "has-action" : "",
            ].filter(Boolean).join(" ")}
          >
            <span>{emptyCanvasMessage}</span>
            {canAddFirstArea ? (
              <button
                type="button"
                disabled={editingLocked}
                onPointerDown={(event) => event.stopPropagation()}
                onClick={handleAddObject}
              >
                Add Area
              </button>
            ) : hiddenByZonesLayer ? (
              <button
                type="button"
                onPointerDown={(event) => event.stopPropagation()}
                onClick={handleShowZones}
              >
                Show Zones
              </button>
            ) : canRecoverHiddenAreas ? (
              <button
                type="button"
                disabled={editingLocked}
                onPointerDown={(event) => event.stopPropagation()}
                onClick={handleShowHiddenAreas}
              >
                Show Hidden
              </button>
            ) : null}
          </div>
        ) : null}
      </div>
    </section>
  );
}
