import {
  EVV_GPS_MODE,
  MOCK_EVV_REFERENCE_LAT,
  MOCK_EVV_REFERENCE_LNG,
  type EvvGpsMode,
} from '../constants';

/**
 * `ILocationProvider` — the one client seam this phase introduces. It wraps the browser Geolocation API
 * for EVV GPS capture so the check-in/out flow is testable without a device and so the
 * denied/unavailable path can be exercised deterministically.
 *
 * `getCurrentPosition` **never rejects** — a denied/unavailable/timed-out fix resolves to `null`. The
 * product rule is that a GPS problem is **advisory, never a hard stop**: the caller submits the check-in
 * with `null` coordinates (flagged server-side) rather than blocking the visit. Selection between the
 * real and mock implementations is by `EVV_GPS_MODE` (`NEXT_PUBLIC_EVV_MOCK_GPS`), never scattered checks.
 *
 * Registered in `dev/shared-working-context/reports/mocks-registry.md`. Server-side GPS/address-match
 * math lives behind the backend's geocoding seam — this seam only *captures* the position.
 */
export interface GeoPosition {
  latitude: number;
  longitude: number;
}

export interface ILocationProvider {
  /** Resolves the current position, or `null` when it can't be obtained (denied/unavailable/timeout). */
  getCurrentPosition(): Promise<GeoPosition | null>;
}

const GEOLOCATION_TIMEOUT_MS = 10_000;

/** Real provider — `navigator.geolocation.getCurrentPosition`, resolving `null` on any failure. */
const realLocationProvider: ILocationProvider = {
  getCurrentPosition: () =>
    new Promise<GeoPosition | null>((resolve) => {
      if (typeof navigator === 'undefined' || !navigator.geolocation) {
        resolve(null);
        return;
      }
      navigator.geolocation.getCurrentPosition(
        (pos) => resolve({ latitude: pos.coords.latitude, longitude: pos.coords.longitude }),
        () => resolve(null), // denied / unavailable / timeout — advisory, never a throw
        { enableHighAccuracy: true, timeout: GEOLOCATION_TIMEOUT_MS, maximumAge: 0 },
      );
    }),
};

const MOCK_LATENCY_MS = 500;

// ~5 km offset from the reference — comfortably outside any sane tolerance (advisory mismatch).
const OUT_OF_RANGE_DEGREE_OFFSET = 0.05;

/** Mock provider — canned coordinates per mode, with a small latency to exercise the "acquiring…" state. */
function makeMockLocationProvider(mode: Exclude<EvvGpsMode, 'off'>): ILocationProvider {
  return {
    getCurrentPosition: () =>
      new Promise<GeoPosition | null>((resolve) => {
        setTimeout(() => {
          if (mode === 'denied') {
            resolve(null);
          } else if (mode === 'out_of_range') {
            resolve({
              latitude: MOCK_EVV_REFERENCE_LAT + OUT_OF_RANGE_DEGREE_OFFSET,
              longitude: MOCK_EVV_REFERENCE_LNG + OUT_OF_RANGE_DEGREE_OFFSET,
            });
          } else {
            resolve({ latitude: MOCK_EVV_REFERENCE_LAT, longitude: MOCK_EVV_REFERENCE_LNG });
          }
        }, MOCK_LATENCY_MS);
      }),
  };
}

/** The selected provider — the single seam the EVV controller imports. */
export const locationProvider: ILocationProvider =
  EVV_GPS_MODE === 'off' ? realLocationProvider : makeMockLocationProvider(EVV_GPS_MODE);
