import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import { getItemByName, markBought } from "../api/list";
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 {
  LocationMapLoadErrorState,
  LocationMapMessageState,
} from "../components/maps/LocationMapStateViews";
import LocationMapToolbar from "../components/maps/LocationMapToolbar";
import LocationMapTopbar from "../components/maps/LocationMapTopbar";
import ConfirmBuyModal from "../components/modals/ConfirmBuyModal";
import ConfirmSlideModal from "../components/modals/ConfirmSlideModal";
import useActionToast from "../hooks/useActionToast";
import useLocationMapDraftActions from "../hooks/useLocationMapDraftActions";
import useLocationMapDraftState from "../hooks/useLocationMapDraftState";
import useLocationMapObjectActions from "../hooks/useLocationMapObjectActions";
import useLocationMapViewControls from "../hooks/useLocationMapViewControls";
import useUnsavedMapLeaveGuard from "../hooks/useUnsavedMapLeaveGuard";
import {
  DEFAULT_MAP_SIZE,
  getMapObjectDisplayLabel,
  getMapStatus,
  getObjectKey,
  itemMatchesMapFilters,
  itemsForZone,
  mapForMode,
  unmappedItems,
} from "../lib/locationMapUtils";
import getApiErrorMessage from "../lib/getApiErrorMessage";
import "../styles/pages/LocationMapManager.css";

const EMPTY_MAP_ITEMS = [];

function getNextMapBuyItem(items, currentIndex, excludedItemId) {
  const remainingItems = items.filter((item) => item.id !== excludedItemId);
  if (!remainingItems.length) return null;
  return remainingItems[Math.min(Math.max(currentIndex, 0), remainingItems.length - 1)];
}

function getLocallyReducedMapItem(item, quantity) {
  const currentQuantity = Number(item.quantity || 1);
  const boughtQuantity = Math.max(1, Number(quantity || 1));
  return {
    ...item,
    quantity: Math.max(1, currentQuantity - boughtQuantity),
  };
}

function getLocallyBoughtMapItem(item) {
  return {
    ...item,
    bought: true,
  };
}

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 [buyModalItem, setBuyModalItem] = useState(null);

  const {
    beginSaving,
    dragState,
    editorTool,
    endSaving,
    filters,
    future,
    hasUnsavedChanges,
    history,
    layersOpen,
    loadError,
    loadMap,
    loading,
    mapDraft,
    mapState,
    mode,
    objects,
    previewDraft,
    saving,
    savingAction,
    selectedObjectKey,
    setDragState,
    setEditorTool,
    setFilters,
    setFuture,
    setHasUnsavedChanges,
    setHistory,
    setLayersOpen,
    setMapState,
    setMode,
    setObjects,
    setPreviewDraft,
    setSelectedObjectKey,
    setZoom,
    syncMapDraftFromState,
    zoom,
  } = useLocationMapDraftState({
    activeHousehold,
    hasLoaded,
    householdLoading,
    locationId,
    toast,
  });
  const svgRef = useRef(null);

  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 = useMemo(
    () => mapForMode(mapState, mode, previewDraft),
    [mapState, mode, previewDraft]
  );
  const mapItems = mapState?.items || EMPTY_MAP_ITEMS;
  const selectedObject = useMemo(
    () => objects.find((object) => getObjectKey(object) === selectedObjectKey) || null,
    [objects, selectedObjectKey]
  );
  const selectedZoneItems = useMemo(
    () => selectedObject?.zone_id
      ? itemsForZone(mapItems, selectedObject.zone_id, filters, username)
      : EMPTY_MAP_ITEMS,
    [filters, mapItems, selectedObject?.zone_id, username]
  );
  const visibleUnmappedItems = useMemo(
    () => unmappedItems(mapItems, filters, username),
    [filters, mapItems, username]
  );
  const buyModalItems = useMemo(
    () => (selectedObject ? selectedZoneItems : visibleUnmappedItems).filter((item) => !item.bought),
    [selectedObject, selectedZoneItems, visibleUnmappedItems]
  );
  const { visibleAssignedItemCount, unmappedItemCount } = useMemo(() => {
    let nextVisibleAssignedItemCount = 0;
    let nextUnmappedItemCount = 0;

    mapItems.forEach((item) => {
      if (!item.zone_id) {
        nextUnmappedItemCount += 1;
        return;
      }
      if (itemMatchesMapFilters(item, filters, username)) {
        nextVisibleAssignedItemCount += 1;
      }
    });

    return {
      visibleAssignedItemCount: nextVisibleAssignedItemCount,
      unmappedItemCount: nextUnmappedItemCount,
    };
  }, [filters, mapItems, username]);
  const hiddenAreaCount = useMemo(
    () => objects.filter((object) => object.visible === false).length,
    [objects]
  );
  const shouldGuardLeave = canManage && hasUnsavedChanges;

  const updateMapItems = useCallback((updater) => {
    setMapState((currentState) => {
      if (!currentState) return currentState;
      const currentItems = currentState.items || EMPTY_MAP_ITEMS;
      return { ...currentState, items: updater(currentItems) };
    });
  }, [setMapState]);

  const handleMapBuyCancel = useCallback(() => {
    setBuyModalItem(null);
  }, []);

  const handleMapBuyNavigate = useCallback((item) => {
    setBuyModalItem(item);
  }, []);

  const handleMapBuyConfirm = useCallback(async (quantity) => {
    if (!activeHousehold?.id || !locationId || !buyModalItem) {
      setBuyModalItem(null);
      return;
    }

    const item = buyModalItems.find((candidate) => candidate.id === buyModalItem.id) || buyModalItem;

    try {
      const currentIndex = buyModalItems.findIndex((candidate) => candidate.id === item.id);
      const resolvedIndex = currentIndex >= 0 ? currentIndex : 0;

      await markBought(activeHousehold.id, locationId, item.item_name, quantity, true);

      let nextBuyModalItems = buyModalItems;
      if (quantity >= item.quantity) {
        nextBuyModalItems = buyModalItems.filter((candidate) => candidate.id !== item.id);
        updateMapItems((currentItems) =>
          currentItems.map((candidate) => (
            candidate.id === item.id ? getLocallyBoughtMapItem(candidate) : candidate
          ))
        );
      } else {
        let updatedItem;
        try {
          const response = await getItemByName(activeHousehold.id, locationId, item.item_name);
          updatedItem = response.data || getLocallyReducedMapItem(item, quantity);
        } catch {
          updatedItem = getLocallyReducedMapItem(item, quantity);
          toast.info("Updated item locally", "Latest quantity will sync when the map reloads.");
        }
        nextBuyModalItems = buyModalItems.map((candidate) => (candidate.id === item.id ? updatedItem : candidate));

        updateMapItems((currentItems) =>
          currentItems.map((candidate) => (candidate.id === item.id ? updatedItem : candidate))
        );
      }
      setBuyModalItem(getNextMapBuyItem(nextBuyModalItems, resolvedIndex, item.id));

      toast.success("Marked item bought", `Marked item ${item.item_name} as bought`);
    } catch (error) {
      const message = getApiErrorMessage(error, "Failed to mark item as bought");
      toast.error("Mark item bought failed", `Mark item bought failed: ${message}`);
    }
  }, [activeHousehold?.id, buyModalItem, buyModalItems, locationId, toast, updateMapItems]);

  const mapSize = useMemo(
    () => ({
      width: mapDraft.width || activeMap?.width || DEFAULT_MAP_SIZE.width,
      height: mapDraft.height || activeMap?.height || DEFAULT_MAP_SIZE.height,
    }),
    [activeMap?.height, activeMap?.width, mapDraft.height, mapDraft.width]
  );

  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]);

  const {
    handleCreateBlank,
    handleCreateFromZones,
    handlePublish,
    handleSaveDraft,
  } = useLocationMapDraftActions({
    activeHouseholdId: activeHousehold?.id,
    beginSaving,
    endSaving,
    hasUnsavedChanges,
    locationId,
    mapDraft,
    objects,
    selectedObjectKey,
    setEditorTool,
    setMapState,
    setMode,
    setPreviewDraft,
    syncMapDraftFromState,
    toast,
  });

  const {
    handleAddObject,
    handleDeleteObject,
    handleDuplicateObject,
    handleObjectField,
    handleShowHiddenAreas,
    handleZoneLinkChange,
    pendingDeleteObject,
    remember,
    requestDeleteObject,
    setPendingDeleteObject,
    updateObjects,
  } = useLocationMapObjectActions({
    canManage,
    hiddenAreaCount,
    mapSize,
    mapState,
    objects,
    selectedObject,
    selectedObjectKey,
    setEditorTool,
    setFuture,
    setHasUnsavedChanges,
    setHistory,
    setObjects,
    setSelectedObjectKey,
    toast,
  });

  const {
    handleEditMode,
    handleFitMap,
    handlePreviewDraft,
    handleRedo,
    handleUndo,
    handleViewMode,
  } = useLocationMapViewControls({
    editorTool,
    filters,
    hasUnsavedChanges,
    mapSize,
    mapState,
    mode,
    objects,
    previewDraft,
    selectedObjectKey,
    setEditorTool,
    setFuture,
    setHasUnsavedChanges,
    setHistory,
    setMode,
    setObjects,
    setPreviewDraft,
    setSelectedObjectKey,
    setZoom,
    svgRef,
    syncMapDraftFromState,
  });

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

  const {
    pendingLeaveTarget,
    requestBackNavigation,
    clearPendingLeave,
    confirmPendingLeave,
  } = useUnsavedMapLeaveGuard({
    shouldGuardLeave,
    navigate,
    performBackNavigation,
  });

  if (!hasLoaded || householdLoading || loading) {
    return <LocationMapMessageState message="Loading map..." />;
  }

  if (!activeHousehold) {
    return <LocationMapMessageState message="Select a household to manage maps." />;
  }

  if (loadError) {
    return (
      <LocationMapLoadErrorState
        location={location}
        loadError={loadError}
        onBack={requestBackNavigation}
        onRetry={loadMap}
      />
    );
  }

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

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

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

        {!hasAnyMap || (mode === "setup" && mapState?.draft_map && !mapState?.published_map) ? (
          <LocationMapSetupPanel
            hasAnyMap={hasVisibleOrManageableMap}
            canManage={canManage}
            zoneCount={mapState?.zones?.length || 0}
            saving={saving}
            savingAction={savingAction}
            onContinue={() => {
              setMode("edit");
              setEditorTool("edit");
              setPreviewDraft(true);
            }}
            onPreview={handlePreviewDraft}
            onPublish={handlePublish}
            onCreateFromZones={handleCreateFromZones}
            onCreateBlank={handleCreateBlank}
          />
        ) : (
          <>
            <LocationMapCanvas
              mode={mode}
              editorTool={editorTool}
              objects={objects}
              filters={filters}
              mapSize={mapSize}
              zoom={zoom}
              selectedObjectKey={selectedObjectKey}
              mapItems={mapItems}
              username={username}
              svgRef={svgRef}
              dragState={dragState}
              editingLocked={saving}
              hiddenAreaCount={hiddenAreaCount}
              onAddObject={handleAddObject}
              setDragState={setDragState}
              setSelectedObjectKey={setSelectedObjectKey}
              remember={remember}
              updateObjects={updateObjects}
              onShowZones={() =>
                setFilters((current) => ({
                  ...current,
                  showZones: true,
                }))
              }
              onShowHiddenAreas={handleShowHiddenAreas}
            />
            <LocationMapBottomSheet
              mode={mode}
              editorTool={editorTool}
              canManage={canManage}
              filters={filters}
              setFilters={setFilters}
              layersOpen={layersOpen}
              setLayersOpen={setLayersOpen}
              selectedObject={selectedObject}
              mapObjects={objects}
              selectedZoneItems={selectedZoneItems}
              visibleAssignedItemCount={visibleAssignedItemCount}
              visibleUnmappedItems={visibleUnmappedItems}
              unmappedItemCount={unmappedItemCount}
              hiddenAreaCount={hiddenAreaCount}
              saving={saving}
              savingAction={savingAction}
              hasUnsavedChanges={hasUnsavedChanges}
              mapState={mapState}
              onAddObject={handleAddObject}
              onSaveDraft={handleSaveDraft}
              onPublish={handlePublish}
              onPreviewDraft={handlePreviewDraft}
              onObjectField={handleObjectField}
              onZoneLinkChange={handleZoneLinkChange}
              onDuplicateObject={handleDuplicateObject}
              onDeleteObject={requestDeleteObject}
              onShowHiddenAreas={handleShowHiddenAreas}
              onSelectZoneItem={setBuyModalItem}
              onSelectUnmappedItem={setBuyModalItem}
            />
          </>
        )}
      </main>

      {buyModalItem ? (
        <ConfirmBuyModal
          item={buyModalItem}
          allItems={buyModalItems}
          onNavigate={handleMapBuyNavigate}
          onCancel={handleMapBuyCancel}
          onConfirm={handleMapBuyConfirm}
        />
      ) : null}

      <ConfirmSlideModal
        isOpen={Boolean(pendingDeleteObject)}
        title={`Delete ${getMapObjectDisplayLabel(pendingDeleteObject, "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={Boolean(pendingLeaveTarget)}
        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={clearPendingLeave}
        onConfirm={confirmPendingLeave}
      />
    </div>
  );
}
