"use client";

import { createContext, useContext, useEffect, useRef, useState } from "react";
import { apiBaseUrl, apiHeaders, apiPath } from "@/lib/api-url";

export type PublicLocation = {
  id: number;
  name: string;
  additionalCostPercentage: number;
  discountPercentage: number;
  isActive: boolean;
};

type LocationState = "default" | "detecting" | "available" | "unavailable" | "denied";

export const userLocationUpdatedEvent = "ride-n-revive-location-updated";

type LocationContextValue = {
  locations: PublicLocation[];
  location: PublicLocation | null;
  resolvedName: string | null;
  state: LocationState;
  requestLocation: () => void;
  selectLocation: (locationId: number | null, displayName?: string) => void;
};

const LocationContext = createContext<LocationContextValue | null>(null);

const storageKey = "ride-n-revive-location-id";

export function LocationProvider({
  children,
  suppressAutoDetect = false,
}: {
  children: React.ReactNode;
  suppressAutoDetect?: boolean;
}) {
  const [locations, setLocations] = useState<PublicLocation[]>([]);
  const [location, setLocation] = useState<PublicLocation | null>(null);
  const [resolvedName, setResolvedName] = useState<string | null>(null);
  const [state, setState] = useState<LocationState>("default");
  const [locationsLoaded, setLocationsLoaded] = useState(false);
  const autoDetectAttemptedRef = useRef(false);

  useEffect(() => {
    fetch(`${apiBaseUrl}${apiPath("/api/locations")}`, {
      headers: apiHeaders(),
    })
      .then((response) => (response.ok ? response.json() : Promise.reject()))
      .then((data: { locations: PublicLocation[] }) => {
        setLocations(data.locations);

        if (suppressAutoDetect) {
          autoDetectAttemptedRef.current = true;
          setLocation(null);
          setResolvedName(null);
          setState("default");
          return;
        }

        const savedId = Number(window.localStorage.getItem(storageKey));
        const savedLocation = data.locations.find((item) => item.id === savedId);

        if (savedLocation) {
          setLocation(savedLocation);
          setState("available");
          autoDetectAttemptedRef.current = true;
        } else if (savedId) {
          window.localStorage.removeItem(storageKey);
        }
      })
      .catch(() => setLocations([]))
      .finally(() => setLocationsLoaded(true));
  }, [suppressAutoDetect]);

  useEffect(() => {
    if (
      suppressAutoDetect ||
      !locationsLoaded ||
      autoDetectAttemptedRef.current ||
      state !== "default"
    ) {
      return;
    }

    autoDetectAttemptedRef.current = true;
    requestLocation();
  }, [locationsLoaded, state, suppressAutoDetect]);

  useEffect(() => {
    window.dispatchEvent(
      new CustomEvent(userLocationUpdatedEvent, {
        detail: {
          location,
          resolvedName,
          state,
        },
      }),
    );
  }, [location, resolvedName, state]);

  function selectLocation(locationId: number | null, displayName?: string) {
    if (!locationId) {
      window.localStorage.removeItem(storageKey);
      setLocation(null);
      setResolvedName(null);
      setState("default");
      return;
    }

    const selectedLocation = locations.find((item) => item.id === locationId) ?? null;
    const displayedLocation =
      selectedLocation && displayName
        ? { ...selectedLocation, name: displayName }
        : selectedLocation;

    setLocation(displayedLocation);
    setResolvedName(displayName ?? selectedLocation?.name ?? null);
    setState(selectedLocation ? "available" : "unavailable");
    window.localStorage.setItem(storageKey, String(locationId));
  }

  function requestLocation() {
    if (!navigator.geolocation) {
      setState("default");
      return;
    }

    setState("detecting");
    navigator.geolocation.getCurrentPosition(
      async (position) => {
        try {
          const response = await fetch(`${apiBaseUrl}${apiPath("/api/locations/resolve")}`, {
            method: "POST",
            headers: apiHeaders({
              "Content-Type": "application/json",
            }),
            body: JSON.stringify({
              lat: position.coords.latitude,
              lng: position.coords.longitude,
            }),
          });

          if (!response.ok) {
            throw new Error("Could not resolve location");
          }

          const data = (await response.json()) as {
            location: PublicLocation | null;
            resolvedName: string | null;
          };

          setLocation(data.location);
          setResolvedName(data.resolvedName);
          setState(data.location ? "available" : "unavailable");

          if (data.location) {
            window.localStorage.setItem(storageKey, String(data.location.id));
          }
        } catch {
          setState("default");
        }
      },
      () => {
        setState("denied");
      },
      {
        enableHighAccuracy: false,
        timeout: 8000,
        maximumAge: 1000 * 60 * 20,
      },
    );
  }

  const value = {
    locations,
    location,
    resolvedName,
    state,
    requestLocation,
    selectLocation,
  };

  return <LocationContext.Provider value={value}>{children}</LocationContext.Provider>;
}

export function useUserLocation() {
  const context = useContext(LocationContext);

  if (!context) {
    throw new Error("useUserLocation must be used within LocationProvider");
  }

  return context;
}
