import React, {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
} from "react";
import { readJSON, storageKeys, writeJSON } from "../storage";

/** How the map is sourced: hosted vector tiles (no download) or a signed,
 * downloaded offline territory. */
export type MapSource = "online" | "offline";

type PersistedTerritoryState = {
  activeTerritoryId: string | null;
  mapSource: MapSource;
};

const EMPTY_STATE: PersistedTerritoryState = {
  activeTerritoryId: null,
  mapSource: "online",
};

type TerritoryContextValue = {
  activeTerritoryId: string | null;
  mapSource: MapSource;
  /** False until the persisted value has been read at least once — the
   * onboarding gate in App.tsx must wait for this before deciding. */
  ready: boolean;
  hasActiveTerritory: boolean;
  /** True once the app can show a map: either an offline territory is active
   * or the user chose online maps. The onboarding gate keys on this. */
  hasMap: boolean;
  setActiveTerritory: (territoryId: string | null) => void;
  /** Choose online maps (no download) or offline; online clears any pending
   * offline requirement so the app can enter immediately. */
  setMapSource: (source: MapSource) => void;
};

const TerritoryContext = createContext<TerritoryContextValue | undefined>(
  undefined
);

export function TerritoryProvider({
  children,
}: {
  children: React.ReactNode;
}): React.JSX.Element {
  const [activeTerritoryId, setActiveTerritoryId] = useState<string | null>(
    null
  );
  const [mapSource, setMapSourceState] = useState<MapSource>("online");
  const [ready, setReady] = useState(false);

  useEffect(() => {
    let cancelled = false;
    readJSON(storageKeys.territory, EMPTY_STATE).then((loaded) => {
      if (cancelled) return;
      setActiveTerritoryId(loaded.activeTerritoryId);
      setMapSourceState(loaded.mapSource ?? "online");
      setReady(true);
    });
    return () => {
      cancelled = true;
    };
  }, []);

  const persist = useCallback(
    (next: PersistedTerritoryState) => {
      void writeJSON<PersistedTerritoryState>(storageKeys.territory, next);
    },
    []
  );

  const setActiveTerritory = useCallback(
    (territoryId: string | null) => {
      setActiveTerritoryId(territoryId);
      // Activating an offline territory implies the offline source.
      const nextSource: MapSource = territoryId ? "offline" : mapSource;
      setMapSourceState(nextSource);
      persist({ activeTerritoryId: territoryId, mapSource: nextSource });
    },
    [mapSource, persist]
  );

  const setMapSource = useCallback(
    (source: MapSource) => {
      setMapSourceState(source);
      persist({ activeTerritoryId, mapSource: source });
    },
    [activeTerritoryId, persist]
  );

  const value = useMemo(
    () => ({
      activeTerritoryId,
      mapSource,
      ready,
      hasActiveTerritory: activeTerritoryId !== null,
      hasMap: activeTerritoryId !== null || mapSource === "online",
      setActiveTerritory,
      setMapSource,
    }),
    [activeTerritoryId, mapSource, ready, setActiveTerritory, setMapSource]
  );

  return (
    <TerritoryContext.Provider value={value}>
      {children}
    </TerritoryContext.Provider>
  );
}

export function useTerritory(): TerritoryContextValue {
  const ctx = useContext(TerritoryContext);
  if (!ctx) {
    throw new Error("useTerritory must be used within a TerritoryProvider");
  }
  return ctx;
}
