// The single GatewayClient the app uses. In `mock` mode (pnpm start) it is the
// in-memory fake; otherwise it is the real Connect/FlatBuffers transport. MODE is a
// build-time constant, so a production build tree-shakes the mock away.

import type { GatewayClient } from './client';
import { MockGateway } from './mock/client';
import { createTransport } from './transport';
import { setForcedSeed } from './localgame/id';
import { reportOffline, reportOnline } from './connection.svelte';
import { emit } from './netstate.svelte';
import { clearMaintenance, maintenanceRecovered, reportMaintenance } from './maintenance.svelte';
import { reportUpdateRecommended, reportUpdateRequired } from './update.svelte';

const isMock = import.meta.env.MODE === 'mock';

export const gateway: GatewayClient = isMock
  ? new MockGateway()
  : createTransport(import.meta.env.VITE_GATEWAY_URL ?? '');

// Mock-mode test hook (tree-shaken from a production build, where MODE !== 'mock'): the mock
// transport never exercises the connectivity indicator, so the Playwright e2e drives it directly.
if (isMock && typeof window !== 'undefined') {
  (window as unknown as { __conn?: { offline(): void; online(): void } }).__conn = {
    offline: reportOffline,
    online: reportOnline,
  };
  // The mock never produces a real 503, so the e2e drives the maintenance overlay directly
  // (the store's poll is inert in mock — no probe is registered — so `off` clears it).
  (
    window as unknown as { __maint?: { on(retryAfterMs?: number): void; off(): void; recover(): void } }
  ).__maint = {
    on: (retryAfterMs = 15000) => reportMaintenance(retryAfterMs),
    off: clearMaintenance,
    recover: maintenanceRecovered,
  };
  // The mock never receives a real version signal from the edge, so the e2e drives them directly:
  // __update.on() raises the hard-tier notice (netState.versionLocked), __update.recommend() raises the
  // soft-tier nudge (versionRecommended → the setNudge latch, from online).
  (window as unknown as { __update?: { on(): void; recommend(): void } }).__update = {
    on: reportUpdateRequired,
    recommend: reportUpdateRecommended,
  };
  // The mock has no real transport, so the net-state store's probe watcher is inert; the e2e drives the
  // machine directly. __net.offline() rides past the connecting hysteresis (k failures) into the offline
  // lobby; __net.online() heals back to online (the "back online" toast fires from the offline state).
  (window as unknown as { __net?: { offline(): void; online(): void } }).__net = {
    offline: () => {
      emit('callFailed');
      emit('probeFailed');
      emit('probeFailed');
      emit('probeFailed');
    },
    online: () => emit('callOk'),
  };
  // Drive the auto-match opponent join deterministically from the e2e (the mock otherwise
  // attaches a robot on a timer).
  (
    window as unknown as {
      __mock?: {
        joinOpponent(): void;
        joinOpponentSilently(): void;
        adminReply(): void;
        setGameLimits(l: { vsAi: number; random: number; friends: number }): void;
        clearEmail(): void;
        confirmEmailOutOfBand(email: string): void;
        setLocalSeed(seed: string): void;
      };
    }
  ).__mock = {
    joinOpponent: () => (gateway as MockGateway).joinPendingOpponent(),
    joinOpponentSilently: () => (gateway as MockGateway).joinPendingOpponentSilently(),
    adminReply: () => (gateway as MockGateway).mockAdminReply(),
    setGameLimits: (l: { vsAi: number; random: number; friends: number }) => (gateway as MockGateway).setGameLimits(l),
    clearEmail: () => (gateway as MockGateway).mockClearEmail(),
    confirmEmailOutOfBand: (email: string) => (gateway as MockGateway).mockConfirmEmailOutOfBand(email),
    // Pin the next local game's bag seed, so the offline e2e deals a known rack (a bigint as a
    // string — window hooks marshal strings cleanly).
    setLocalSeed: (seed: string) => setForcedSeed(BigInt(seed)),
  };
}
