/**
 * mapmap-react-native — THE bridge contract (CONTRACTS.md §2).
 *
 * This file is the single source of truth for the JS↔native surface.
 * Builders implement exactly these shapes on both platforms. If the native
 * SDK cannot honour something here, STOP and report — do not redesign.
 * Everything crossing the bridge is JSON-serialisable.
 */

export type LatLng = { latitude: number; longitude: number };

/* ---------- errors ---------- */

/** Stable machine-readable codes carried on every rejected promise. */
export type BridgeErrorCode =
  | "E_NOT_INITIALISED" // method called before init() resolved
  | "E_INIT_FAILED" // core failed to load
  | "E_NETWORK" // transport failure talking to the gateway
  | "E_AUTH" // key rejected (401/403)
  | "E_TERRITORY_ALLOWANCE" // download allowance exhausted (402)
  | "E_TERRITORY_VERIFY" // signature/verification failure
  | "E_TERRITORY_NOT_FOUND" // unknown territory id
  | "E_NO_TERRITORY" // routing/search attempted with none active
  | "E_NO_ROUTE" // engine found no route between the points
  | "E_GUIDANCE_ACTIVE" // startGuidance while a session is running
  | "E_NO_GUIDANCE" // stop/mutate with no session running
  | "E_REPLAY_CORPUS" // corpus file missing or malformed
  | "E_INTERNAL"; // anything else (message carries detail)

export type BridgeError = {
  code: BridgeErrorCode;
  message: string;
};

/* ---------- territories ---------- */

export type TerritorySummary = {
  id: string; // e.g. "uk"
  name: string; // e.g. "United Kingdom"
  sizeBytes: number; // total download size
  version: string; // channel version string
};

export type TerritoryInfo = {
  id: string;
  name: string;
  version: string;
  sizeOnDiskBytes: number;
  active: boolean;
};

export type TerritoryPhase =
  | "downloading"
  | "verifying"
  | "activating"
  | "done"
  | "failed";

export type TerritoryProgressEvent = {
  territoryId: string;
  phase: TerritoryPhase;
  /** Bytes fetched so far; only meaningful while downloading. */
  bytesDone: number;
  /** Total bytes to fetch; 0 until known. */
  bytesTotal: number;
  /** Set only when phase === "failed". */
  error?: BridgeError;
};

/* ---------- search ---------- */

export type SearchResult = {
  name: string;
  /** Secondary line: locality / address; empty string when unknown. */
  address: string;
  position: LatLng;
  /** Straight-line metres from `near` when it was provided. */
  distanceMeters?: number;
};

/* ---------- routing ---------- */

export type RouteProfile = "car" | "bicycle" | "walking" | "truck";

/** Lorry dimensions + dangerous-goods declaration for truck routing. */
export type TruckOptions = {
  heightM?: number;
  weightT?: number;
  lengthM?: number;
  widthM?: number;
  /** Carrying ADR dangerous goods. */
  hazmat?: boolean;
  /** ADR tunnel restriction category ("B".."E") the load must respect. */
  tunnelCode?: string;
};

/** Route-preference exclusions (motorised profiles). */
export type RouteAvoid = {
  tolls?: boolean;
  motorways?: boolean;
  ferries?: boolean;
};

export type RouteRequest = {
  profile: RouteProfile;
  origin: LatLng;
  destination: LatLng;
  /** Intermediate stops, visited in order (add-a-stop). */
  waypoints?: LatLng[];
  /** Extra full routes wanted beyond the primary (0–3). Default 2. */
  alternatives?: number;
  /** Truck profile only: dimensions + ADR declaration. */
  truck?: TruckOptions;
  /** Route-preference exclusions. */
  avoid?: RouteAvoid;
};

export type ManeuverType =
  | "depart"
  | "turn-left"
  | "turn-right"
  | "slight-left"
  | "slight-right"
  | "sharp-left"
  | "sharp-right"
  | "uturn"
  | "straight"
  | "merge"
  | "on-ramp"
  | "off-ramp"
  | "fork-left"
  | "fork-right"
  | "roundabout" // exitNumber set when known
  | "arrive";

export type Maneuver = {
  type: ManeuverType;
  instruction: string; // human text from the SDK, verbatim
  distanceMeters: number; // length of the leg this maneuver begins
  position: LatLng;
  roundaboutExit?: number;
  /** Name(s) of the road travelled along DURING this maneuver's leg — the road
   * the driver is on until the next turn. Absent for unnamed roads. This is the
   * authoritative "current road" source (map-tile labels are unreliable). */
  roadName?: string;
};

export type Route = {
  /** Index into the RouteResult.routes array; stable for the session. */
  index: number;
  /** Full geometry, ordered, ready for a MapLibre LineString. */
  geometry: LatLng[];
  distanceMeters: number;
  durationSeconds: number;
  maneuvers: Maneuver[];
  /** The engine's encoded polyline6 for this route, when it came from the
   * hosted gateway. Used to fetch per-edge attributes (e.g. posted speed
   * limits via /trace_attributes) without re-encoding the geometry. Absent for
   * synthetic/mock routes. */
  encodedShape?: string;
};

export type RouteResult = {
  routes: Route[]; // primary first; alternatives after
};

/* ---------- guidance ---------- */

export type LocationSource =
  | { kind: "live" }
  | {
      kind: "replay";
      /** Absolute path to a .drive.jsonl corpus on device storage. */
      corpusPath: string;
      /** Playback speed; 1 = real time. Default 1. */
      speedMultiplier?: number;
    };

export type GuidanceOptions = {
  /** Which route from the last computeRoute() to follow. */
  routeIndex: number;
  source: LocationSource;
};

/** A single lane's arrow set — combined lanes (e.g. "through or right") carry
 * more than one direction. */
export type LaneDirection = "left" | "slight-left" | "through" | "slight-right" | "right" | "uturn";

export type LaneInfo = {
  directions: LaneDirection[];
  /** Whether taking this lane keeps the driver on the upcoming manoeuvre. */
  active: boolean;
};

export type BannerInstruction = {
  primary: string; // e.g. "Turn left onto High Street"
  secondary?: string;
  maneuverType: ManeuverType;
  roundaboutExit?: number;
  /** The instruction after this one, when imminent; for the "Then ↰" chip. */
  thenPrimary?: string;
  thenManeuverType?: ManeuverType;
  /** Lane guidance for the upcoming manoeuvre — left-to-right lane order,
   * populated only when close enough to the turn to matter (SDKs typically
   * surface this within ~150–300 m); absent otherwise. Optional so nothing
   * consuming BannerInstruction today breaks. */
  lanes?: LaneInfo[];
};

export type GuidanceEvent = {
  state: "navigating" | "rerouting" | "arrived";
  /** Snapped position for the puck + chase camera. */
  location: LatLng & { bearingDegrees: number; speedMps: number };
  banner?: BannerInstruction; // absent momentarily during reroute
  distanceToManeuverMeters: number;
  remainingDistanceMeters: number;
  remainingDurationSeconds: number;
  /** Unix ms arrival estimate, recomputed continuously. */
  etaEpochMs: number;
  isOffRoute: boolean;
  /** Name of the road currently being driven, from the route's own data.
   * Absent when the current leg has no named road. */
  currentRoadName?: string;
};

export type VoiceStateEvent = {
  muted: boolean;
  volume: number; // 0..1
  speaking: boolean;
};

/* ---------- the module surface ---------- */

export interface MapmapNavModule {
  /**
   * Load the native core and remember the API key + gateway base.
   * Idempotent. Everything else rejects E_NOT_INITIALISED before this.
   */
  init(apiKey: string, gatewayBaseUrl?: string): Promise<void>;

  /* territories */
  listAvailableTerritories(): Promise<TerritorySummary[]>;
  listInstalledTerritories(): Promise<TerritoryInfo[]>;
  /** Resolves when active; emits territoryProgress throughout. */
  installTerritory(territoryId: string): Promise<void>;
  removeTerritory(territoryId: string): Promise<void>;
  /**
   * Complete MapLibre style JSON (string) for the installed territory,
   * every source pointing at LOCAL data — renders with radios off.
   */
  getTerritoryStyle(territoryId: string, theme: "light" | "dark"): Promise<string>;

  /* search — on-device against the active territory */
  search(query: string, near?: LatLng, limit?: number): Promise<SearchResult[]>;

  /* routing — on-device */
  computeRoute(request: RouteRequest): Promise<RouteResult>;

  /* guidance */
  startGuidance(options: GuidanceOptions): Promise<void>;
  stopGuidance(): Promise<void>;

  /* voice */
  setVoiceMuted(muted: boolean): Promise<void>;
  setVoiceVolume(volume: number): Promise<void>;
}

/** Event name → payload map (Expo Modules event emitter). */
export type MapmapNavEvents = {
  territoryProgress: (event: TerritoryProgressEvent) => void;
  guidance: (event: GuidanceEvent) => void;
  voiceState: (event: VoiceStateEvent) => void;
  bridgeError: (event: BridgeError) => void;
};
