import { useMemo } from "react";
import { requireOptionalNativeModule } from "expo";
import { createMapmapNavMock } from "../../../modules/mapmap-react-native/src/mock";
import { fetchGatewayRoute } from "../../map/gatewayRoute";
import type { MapmapNavHandle } from "../../../modules/mapmap-react-native/src";
import type { NativeMapmapNavModule } from "../../../modules/mapmap-react-native/src/native";

export type { MapmapNavHandle } from "../../../modules/mapmap-react-native/src";

/** Native module name per CONTRACTS.md §2 and modules/.../src/native.ts. */
const NATIVE_MODULE_NAME = "MapmapNav";

let cachedHandle: MapmapNavHandle | null = null;
let usingMock = false;

/** True when the resolved handle is the JS mock (no native module / forced
 * demo) — consumers like useSpokenGuidance key off this so JS TTS never
 * doubles up with the native VoiceGuidance audio. */
export function isMockActive(): boolean {
  return usingMock;
}

function resolveMapmapNavHandle(): MapmapNavHandle {
  if (cachedHandle) return cachedHandle;

  // Deliberately NOT importing modules/mapmap-react-native/src's value
  // exports as a whole (its index.ts, or native.ts directly): native.ts
  // resolves the native module with the *throwing* `requireNativeModule`
  // at module-evaluation time, which would crash app boot in Expo Go or
  // any environment without a native prebuild. We probe with the *optional*
  // variant ourselves instead and only ever pull in the mock's runtime
  // code — `mock.ts` has zero dependency on expo/react-native by design
  // (see its doc comment) — everything else here is `import type`, which
  // TypeScript erases completely, so it can't trigger native.ts's throw.
  // Demo escape hatch: EXPO_PUBLIC_MAPMAP_MOCK=1 forces the JS mock even
  // when the native module is present (used for UI runs/recordings while
  // the hosted territory channel is down — see FINDINGS F2).
  const forceMock = process.env.EXPO_PUBLIC_MAPMAP_MOCK === "1";

  let native: NativeMapmapNavModule | null = null;
  if (!forceMock) {
    try {
      native = requireOptionalNativeModule<NativeMapmapNavModule>(
        NATIVE_MODULE_NAME
      );
    } catch {
      native = null;
    }
  }

  if (native) {
    usingMock = false;
    cachedHandle = native;
    return cachedHandle;
  }

  // Mock path (Expo Go / forced demo). Prime it with an installed, active
  // territory so search/computeRoute work even when the app itself runs on
  // online maps (no app-level territory active) — the mock's territory
  // state is internal to it and invisible to the territory context.
  // Real road-following routes for the demo via the hosted engine (falls
  // back to the synthetic dogleg when offline/unavailable). See FINDINGS F1
  // for why demo destinations live in Manchester.
  const apiKey = process.env.EXPO_PUBLIC_MAPMAP_API_KEY ?? "";
  const mock = createMapmapNavMock({
    routeProvider: (request) => fetchGatewayRoute(apiKey, request),
    // ~3Hz guidance ticks (not the 1Hz default): the chase camera and puck
    // re-position on every event, so a faster stream is the single biggest
    // win for smoothness. Real-world speed is unchanged — the mock scales its
    // per-tick step by the interval — this just interpolates the motion
    // finely enough that the camera glides instead of hopping once a second.
    tickIntervalMs: 300,
  });
  void mock
    .init("mock")
    .then(() => mock.installTerritory("uk"))
    .catch(() => {
      /* mock priming is best-effort */
    });
  usingMock = true;
  cachedHandle = mock;
  return cachedHandle;
}

/**
 * Returns the MapmapNav bridge handle: the real native module once a native
 * build has registered it, otherwise `modules/mapmap-react-native`'s own
 * mock (T3's `createMapmapNavMock`) — so the app runs fully in Expo Go. One
 * instance is memoised for the app's lifetime; the surface is stateful
 * (init() once, computeRoute() results are referenced by a later
 * startGuidance()) so every caller must share it, not create their own.
 */
export function useMapmapNav(): MapmapNavHandle {
  return useMemo(resolveMapmapNavHandle, []);
}
