import { useCallback, useContext, useEffect, useRef, useState } from "react";
import { useBeforeUnload, useLocation, useNavigate, useParams } from "react-router-dom";
import {
  createBlankLocationMap,
  createLocationMapFromZones,
  getLocationMap,
  publishLocationMapDraft,
  saveLocationMapDraft,
} from "../api/locationMaps";
import { AuthContext } from "../context/AuthContext";
import { HouseholdContext } from "../context/HouseholdContext";
import { StoreContext } from "../context/StoreContext";
import LocationMapBottomSheet from "../components/maps/LocationMapBottomSheet";
import LocationMapCanvas from "../components/maps/LocationMapCanvas";
import LocationMapSetupPanel from "../components/maps/LocationMapSetupPanel";
import LocationMapToolbar from "../components/maps/LocationMapToolbar";
import LocationMapTopbar from "../components/maps/LocationMapTopbar";
import ConfirmSlideModal from "../components/modals/ConfirmSlideModal";
import useActionToast from "../hooks/useActionToast";
import getApiErrorMessage from "../lib/getApiErrorMessage";
import {
  DEFAULT_MAP_FILTERS,
  DEFAULT_MAP_SIZE,
  clampMapZoom,
  clampObjectToMap,
  createClientObject,
  getMapStatus,
  getObjectKey,
  itemsForZone,
  mapForMode,
  normalizeMapObject,
  objectZoneId,
  objectsForMode,
  prepareObjectsForSave,
  unmappedItems,
} from "../lib/locationMapUtils";
import "../styles/pages/LocationMapManager.css";

export default function LocationMapManager() {
  const { storeId, locationId } = useParams();
  const navigate = useNavigate();
  const routeLocation = useLocation();
  const returnPath = routeLocation.state?.returnTo;
  const toast = useActionToast();
  const { username } = useContext(AuthContext);
  const { activeHousehold, loading: householdLoading, hasLoaded } = useContext(HouseholdContext);
  const { stores, activeStore, setActiveStore } = useContext(StoreContext);

  const [mapState, setMapState] = useState(null);
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [savingAction, setSavingAction] = useState(null);
  const [mode, setMode] = useState("setup");
  const [editorTool, setEditorTool] = useState("pan");
  const [previewDraft, setPreviewDraft] = useState(false);
  const [mapDraft, setMapDraft] = useState({
    name: "Store Map",
    width: DEFAULT_MAP_SIZE.width,
    height: DEFAULT_MAP_SIZE.height,
  });
  const [objects, setObjects] = useState([]);
  const [selectedObjectKey, setSelectedObjectKey] = useState(null);
  const [filters, setFilters] = useState(DEFAULT_MAP_FILTERS);
  const [layersOpen, setLayersOpen] = useState(false);
  const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
  const [zoom, setZoom] = useState(0.75);
  const [history, setHistory] = useState([]);
  const [future, setFuture] = useState([]);
  const [dragState, setDragState] = useState(null);
  const [pendingDeleteObject, setPendingDeleteObject] = useState(null);
  const [pendingLeave, setPendingLeave] = useState(false);
  const svgRef = useRef(null);
  const toastRef = useRef(toast);

  const location = mapState?.location || stores.find((store) => String(store.id) === String(locationId));
  const canManage = Boolean(mapState?.can_manage ?? ["owner", "admin"].includes(activeHousehold?.role));
  const activeMap = mapForMode(mapState, mode, previewDraft);
  const selectedObject = objects.find((object) => getObjectKey(object) === selectedObjectKey) || null;
  const selectedZoneItems = selectedObject?.zone_id
    ? itemsForZone(mapState?.items || [], selectedObject.zone_id, filters, username)
    : [];
  const visibleUnmappedItems = unmappedItems(mapState?.items || [], filters, username);
  const shouldGuardLeave = canManage && hasUnsavedChanges;

  const mapSize = {
    width: mapDraft.width || activeMap?.width || DEFAULT_MAP_SIZE.width,
    height: mapDraft.height || activeMap?.height || DEFAULT_MAP_SIZE.height,
  };

  useBeforeUnload(
    useCallback((event) => {
      if (!shouldGuardLeave) return;
      event.preventDefault();
      event.returnValue = "";
    }, [shouldGuardLeave]),
    { capture: true }
  );

  const syncMapDraftFromState = useCallback((nextState, nextMode, nextPreviewDraft) => {
    const nextMap = mapForMode(nextState, nextMode, nextPreviewDraft);
    const nextObjects = objectsForMode(nextState, nextMode, nextPreviewDraft);
    if (!nextMap) return;

    setMapDraft({
      name: nextMap.name || "Store Map",
      width: nextMap.width || DEFAULT_MAP_SIZE.width,
      height: nextMap.height || DEFAULT_MAP_SIZE.height,
    });
    setObjects(nextObjects.map(normalizeMapObject));
    setSelectedObjectKey(null);
    setHistory([]);
    setFuture([]);
    setHasUnsavedChanges(false);
  }, []);

  const loadMap = useCallback(async () => {
    if (!activeHousehold?.id || !locationId) return;

    setLoading(true);
    try {
      const response = await getLocationMap(activeHousehold.id, locationId);
      const nextState = response.data;
      setMapState(nextState);

      if (!nextState.draft_map && !nextState.published_map) {
        setMode("setup");
        setEditorTool("pan");
        setPreviewDraft(false);
        setObjects([]);
        setHasUnsavedChanges(false);
        setMapDraft({
          name: "Store Map",
          width: DEFAULT_MAP_SIZE.width,
          height: DEFAULT_MAP_SIZE.height,
        });
        return;
      }

      if (nextState.published_map) {
        setMode("view");
        setEditorTool("pan");
        setPreviewDraft(false);
      } else if (nextState.draft_map) {
        setMode("setup");
        setEditorTool("pan");
        setPreviewDraft(true);
      }

      syncMapDraftFromState(
        nextState,
        nextState.published_map ? "view" : "edit",
        !nextState.published_map
      );
    } catch (error) {
      const message = getApiErrorMessage(error, "Failed to load map");
      toastRef.current.error("Load map failed", message);
      setMapState(null);
    } finally {
      setLoading(false);
    }
  }, [activeHousehold?.id, locationId, syncMapDraftFromState]);

  useEffect(() => {
    toastRef.current = toast;
  }, [toast]);

  useEffect(() => {
    if (!householdLoading && hasLoaded) {
      loadMap();
    }
  }, [hasLoaded, householdLoading, loadMap]);

  useEffect(() => {
    const matchingLocation = stores.find((store) => String(store.id) === String(locationId));
    if (matchingLocation && String(activeStore?.id) !== String(matchingLocation.id)) {
      setActiveStore(matchingLocation);
    }
  }, [activeStore?.id, locationId, setActiveStore, stores]);

  useEffect(() => {
    if (mode !== "edit") {
      setEditorTool("pan");
      return;
    }

    if (editorTool === "pan") {
      setSelectedObjectKey(null);
    }
  }, [editorTool, mode]);

  const remember = (currentObjects = objects) => {
    setHistory((previous) => [...previous.slice(-19), currentObjects.map((object) => ({ ...object }))]);
    setFuture([]);
  };

  const updateObjects = (updater) => {
    setHasUnsavedChanges(true);
    setObjects((currentObjects) => {
      const nextObjects = updater(currentObjects).map((object) => clampObjectToMap(object, mapSize));
      return nextObjects;
    });
  };

  const beginSaving = (action) => {
    setSavingAction(action);
    setSaving(true);
  };

  const endSaving = () => {
    setSaving(false);
    setSavingAction(null);
  };

  const handleCreateBlank = async () => {
    if (!activeHousehold?.id || !locationId) return;
    beginSaving("create-blank");
    try {
      const response = await createBlankLocationMap(activeHousehold.id, locationId, mapDraft);
      setMapState(response.data);
      setMode("edit");
      setEditorTool("edit");
      setPreviewDraft(true);
      syncMapDraftFromState(response.data, "edit", true);
      toast.success("Created map", "Blank draft map created");
    } catch (error) {
      toast.error("Create map failed", getApiErrorMessage(error, "Failed to create map"));
    } finally {
      endSaving();
    }
  };

  const handleCreateFromZones = async () => {
    if (!activeHousehold?.id || !locationId) return;
    beginSaving("create-zones");
    try {
      const response = await createLocationMapFromZones(activeHousehold.id, locationId, mapDraft);
      setMapState(response.data);
      setMode("edit");
      setEditorTool("edit");
      setPreviewDraft(true);
      syncMapDraftFromState(response.data, "edit", true);
      toast.success("Created starter map", "Zones were added as editable rectangles");
    } catch (error) {
      toast.error("Create map failed", getApiErrorMessage(error, "Failed to create map from zones"));
    } finally {
      endSaving();
    }
  };

  const handleSaveDraft = async () => {
    if (!activeHousehold?.id || !locationId) return;
    beginSaving("save");
    try {
      const response = await saveLocationMapDraft(activeHousehold.id, locationId, {
        map: mapDraft,
        objects: prepareObjectsForSave(objects),
      });
      setMapState(response.data);
      setMode("edit");
      setPreviewDraft(true);
      syncMapDraftFromState(response.data, "edit", true);
      toast.success("Saved draft", "Map draft saved");
    } catch (error) {
      toast.error("Save draft failed", getApiErrorMessage(error, "Failed to save map draft"));
    } finally {
      endSaving();
    }
  };

  const handlePublish = async () => {
    if (!activeHousehold?.id || !locationId) return;
    beginSaving("publish");
    try {
      if (hasUnsavedChanges) {
        await saveLocationMapDraft(activeHousehold.id, locationId, {
          map: mapDraft,
          objects: prepareObjectsForSave(objects),
        });
      }

      const response = await publishLocationMapDraft(activeHousehold.id, locationId);
      setMapState(response.data);
      setMode("view");
      setEditorTool("pan");
      setPreviewDraft(false);
      syncMapDraftFromState(response.data, "view", false);
      toast.success(
        "Published map",
        hasUnsavedChanges
          ? "Latest edits were saved and published"
          : "Map is now visible to household members"
      );
    } catch (error) {
      toast.error("Publish failed", getApiErrorMessage(error, "Failed to publish map"));
    } finally {
      endSaving();
    }
  };

  const handleAddObject = () => {
    if (!canManage) return;
    remember();
    const usedZoneIds = new Set(objects.map((object) => objectZoneId(object)).filter(Boolean));
    const nextZone = (mapState?.zones || []).find((zone) => !usedZoneIds.has(String(zone.id))) || mapState?.zones?.[0];
    const nextObject = createClientObject(nextZone, objects.length);
    setObjects((currentObjects) => [...currentObjects, nextObject]);
    setSelectedObjectKey(getObjectKey(nextObject));
    setEditorTool("edit");
    setHasUnsavedChanges(true);
  };

  const handleDuplicateObject = () => {
    if (!selectedObject) return;
    remember();
    const nextObject = {
      ...selectedObject,
      id: undefined,
      client_id: `copy-${Date.now()}`,
      label: `${selectedObject.label || "Area"} Copy`,
      x: selectedObject.x + 28,
      y: selectedObject.y + 28,
      sort_order: objects.length + 1,
    };
    setObjects((currentObjects) => [...currentObjects, clampObjectToMap(nextObject, mapSize)]);
    setSelectedObjectKey(getObjectKey(nextObject));
    setEditorTool("edit");
    setHasUnsavedChanges(true);
  };

  const requestDeleteObject = () => {
    if (!selectedObject) return;
    setPendingDeleteObject(selectedObject);
  };

  const handleDeleteObject = () => {
    if (!pendingDeleteObject) return;
    remember();
    setObjects((currentObjects) =>
      currentObjects.filter((object) => getObjectKey(object) !== getObjectKey(pendingDeleteObject))
    );
    setSelectedObjectKey(null);
    setHasUnsavedChanges(true);
    toast.success(
      "Deleted map area",
      "Save the draft to keep this change."
    );
    setPendingDeleteObject(null);
  };

  const handleObjectField = (field, value) => {
    if (!selectedObject) return;
    remember();
    updateObjects((currentObjects) =>
      currentObjects.map((object) =>
        getObjectKey(object) === getObjectKey(selectedObject)
          ? { ...object, [field]: value }
          : object
      )
    );
  };

  const handleZoneLinkChange = (zoneId) => {
    const zone = (mapState?.zones || []).find((candidate) => String(candidate.id) === String(zoneId));
    handleObjectField("zone_id", zone?.id || null);
    setObjects((currentObjects) =>
      currentObjects.map((object) =>
        getObjectKey(object) === selectedObjectKey
          ? {
            ...object,
            zone_name: zone?.name || null,
            label: object.label || zone?.name || "",
          }
          : object
      )
    );
  };

  const handleUndo = () => {
    setHistory((previous) => {
      if (previous.length === 0) return previous;
      const priorObjects = previous[previous.length - 1];
      setFuture((nextFuture) => [objects.map((object) => ({ ...object })), ...nextFuture.slice(0, 19)]);
      setObjects(priorObjects);
      setHasUnsavedChanges(true);
      return previous.slice(0, -1);
    });
  };

  const handleRedo = () => {
    setFuture((previous) => {
      if (previous.length === 0) return previous;
      const nextObjects = previous[0];
      setHistory((nextHistory) => [...nextHistory.slice(-19), objects.map((object) => ({ ...object }))]);
      setObjects(nextObjects);
      setHasUnsavedChanges(true);
      return previous.slice(1);
    });
  };

  const handlePreviewDraft = () => {
    setMode("view");
    setEditorTool("pan");
    setPreviewDraft(true);
    setSelectedObjectKey(null);
  };

  const handleViewMode = () => {
    setMode("view");
    setEditorTool("pan");
    if (hasUnsavedChanges) {
      setPreviewDraft(true);
      setSelectedObjectKey(null);
      return;
    }
    setPreviewDraft(false);
    if (mapState) {
      syncMapDraftFromState(mapState, "view", false);
    }
  };

  const handleEditMode = () => {
    setMode("edit");
    setEditorTool("pan");
    setPreviewDraft(true);
    if (!previewDraft && mapState && !hasUnsavedChanges) {
      syncMapDraftFromState(mapState, "edit", true);
    }
  };

  const handleFitMap = () => {
    const scrollElement = svgRef.current?.closest(".location-map-scroll");
    const fallbackWidth = typeof window === "undefined" ? DEFAULT_MAP_SIZE.width : window.innerWidth;
    const fallbackHeight = typeof window === "undefined" ? DEFAULT_MAP_SIZE.height : window.innerHeight;
    const availableWidth = Math.max(280, (scrollElement?.clientWidth || fallbackWidth) - 24);
    const availableHeight = Math.max(220, (scrollElement?.clientHeight || fallbackHeight) - 24);
    const nextZoom = clampMapZoom(
      Math.min(availableWidth / mapSize.width, availableHeight / mapSize.height)
    );
    setZoom(Number(nextZoom.toFixed(2)));
  };

  const performBackNavigation = useCallback(() => {
    if (typeof returnPath === "string" && returnPath.startsWith("/")) {
      navigate(returnPath, { replace: true });
      return;
    }
    navigate("/");
  }, [navigate, returnPath]);

  const handleBack = useCallback(() => {
    if (shouldGuardLeave) {
      setPendingLeave(true);
      return;
    }
    performBackNavigation();
  }, [performBackNavigation, shouldGuardLeave]);

  if (!hasLoaded || householdLoading || loading) {
    return (
      <div className="location-map-page">
        <p className="location-map-loading">Loading map...</p>
      </div>
    );
  }

  if (!activeHousehold) {
    return (
      <div className="location-map-page">
        <p className="location-map-loading">Select a household to manage maps.</p>
      </div>
    );
  }

  const hasAnyMap = Boolean(mapState?.draft_map || mapState?.published_map);
  const status = getMapStatus(mapState, { mode, previewDraft, hasUnsavedChanges });

  return (
    <div className="location-map-page">
      <LocationMapTopbar location={location} status={status} onBack={handleBack} />

      <main className="location-map-workspace">
        {hasAnyMap && mode !== "setup" ? (
          <LocationMapToolbar
            mode={mode}
            editorTool={editorTool}
            hasAnyMap={hasAnyMap}
            canManage={canManage}
            saving={saving}
            zoom={zoom}
            setZoom={setZoom}
            onFit={handleFitMap}
            onView={handleViewMode}
            onEdit={handleEditMode}
            onPanMode={() => setEditorTool("pan")}
            onEditObjects={() => setEditorTool("edit")}
          />
        ) : null}

        {!hasAnyMap || (mode === "setup" && mapState?.draft_map && !mapState?.published_map) ? (
          <LocationMapSetupPanel
            hasAnyMap={hasAnyMap}
            canManage={canManage}
            zoneCount={mapState?.zones?.length || 0}
            saving={saving}
            savingAction={savingAction}
            onContinue={() => setMode("edit")}
            onPreview={handlePreviewDraft}
            onPublish={handlePublish}
            onCreateFromZones={handleCreateFromZones}
            onCreateBlank={handleCreateBlank}
          />
        ) : (
          <>
            <LocationMapCanvas
              mode={mode}
              editorTool={editorTool}
              objects={objects}
              filters={filters}
              mapSize={mapSize}
              zoom={zoom}
              selectedObjectKey={selectedObjectKey}
              mapItems={mapState?.items || []}
              username={username}
              svgRef={svgRef}
              dragState={dragState}
              editingLocked={saving}
              setDragState={setDragState}
              setSelectedObjectKey={setSelectedObjectKey}
              remember={remember}
              updateObjects={updateObjects}
              onShowZones={() =>
                setFilters((current) => ({
                  ...current,
                  showZones: true,
                }))
              }
            />
            <LocationMapBottomSheet
              mode={mode}
              editorTool={editorTool}
              canManage={canManage}
              filters={filters}
              setFilters={setFilters}
              layersOpen={layersOpen}
              setLayersOpen={setLayersOpen}
              selectedObject={selectedObject}
              selectedZoneItems={selectedZoneItems}
              visibleUnmappedItems={visibleUnmappedItems}
              history={history}
              future={future}
              saving={saving}
              savingAction={savingAction}
              hasUnsavedChanges={hasUnsavedChanges}
              mapState={mapState}
              mapSize={mapSize}
              onAddObject={handleAddObject}
              onUndo={handleUndo}
              onRedo={handleRedo}
              onSaveDraft={handleSaveDraft}
              onPublish={handlePublish}
              onPreviewDraft={handlePreviewDraft}
              onPanMode={() => setEditorTool("pan")}
              onEditObjects={() => setEditorTool("edit")}
              onEditMode={handleEditMode}
              onObjectField={handleObjectField}
              onZoneLinkChange={handleZoneLinkChange}
              onClearSelection={() => setSelectedObjectKey(null)}
              onDuplicateObject={handleDuplicateObject}
              onDeleteObject={requestDeleteObject}
            />
          </>
        )}
      </main>

      <ConfirmSlideModal
        isOpen={Boolean(pendingDeleteObject)}
        title={`Delete ${pendingDeleteObject?.label || "this map area"}?`}
        description="This removes the area from the draft map. Save the draft after deleting to keep the change."
        confirmLabel="Delete Area"
        onClose={() => setPendingDeleteObject(null)}
        onConfirm={handleDeleteObject}
      />
      <ConfirmSlideModal
        isOpen={pendingLeave}
        title="Leave with unsaved changes?"
        description="Your draft edits are only on this screen right now. Save the draft before leaving if you want to keep them."
        confirmLabel="Leave Map"
        onClose={() => setPendingLeave(false)}
        onConfirm={() => {
          setPendingLeave(false);
          performBackNavigation();
        }}
      />
    </div>
  );
}
