// Module-scoped registry the in-game shell uses to expose live
// renderer state to the DEV-only `__galaxyDebug` surface defined in
// `routes/__debug/store/+page.svelte`. Tests open the debug route
// once to grab the surface, then drive the in-game routes; the
// registered providers stay alive across SvelteKit navigations and
// surface the current map state without forcing the renderer to
// know about the debug API directly.
//
// Providers are functions, not snapshots: the surface invokes them
// lazily on every read so the returned data always reflects the
// current frame, not the value at registration time.

import type { Primitive, PrimitiveID } from "../map/world";

/** Snapshot returned by `getMapPrimitives()`. The renderer applies
 * pick-mode dimming via the underlying `Graphics.alpha`, so the
 * `alpha` field captures what is actually drawn (1.0 normally,
 * `PICK_OVERLAY_STYLE.dimAlpha` while a pick session is active).
 * Style colours come straight from the primitive style (no theme
 * fallback) so e2e specs can assert exact colour identity. `x` and
 * `y` are populated for `point` primitives (single anchor); other
 * kinds leave them `null`. */
export interface MapPrimitiveSnapshot {
	readonly id: PrimitiveID;
	readonly kind: Primitive["kind"];
	readonly priority: number;
	readonly alpha: number;
	readonly fillColor: number | null;
	readonly strokeColor: number | null;
	readonly x: number | null;
	readonly y: number | null;
}

/** Snapshot returned by `getMapCamera()`. Mirrors the renderer's
 * `getCamera` / `getViewport` plus a bounding-rect snapshot of the
 * underlying canvas, so e2e specs can project a known world-space
 * coordinate to a click target without rebuilding the projection
 * maths themselves. */
export interface MapCameraSnapshot {
	readonly camera: { readonly centerX: number; readonly centerY: number; readonly scale: number };
	readonly viewport: { readonly widthPx: number; readonly heightPx: number };
	readonly canvasOrigin: { readonly x: number; readonly y: number };
}

/** Snapshot returned by `getMapPickState()`. */
export interface MapPickStateSnapshot {
	readonly active: boolean;
	readonly sourcePlanetNumber: number | null;
	readonly reachableIds: readonly number[];
	readonly hoveredId: number | null;
}

type PrimitivesProvider = () => readonly MapPrimitiveSnapshot[];
type PickStateProvider = () => MapPickStateSnapshot;
type CameraProvider = () => MapCameraSnapshot | null;

let primitivesProvider: PrimitivesProvider | null = null;
let pickStateProvider: PickStateProvider | null = null;
let cameraProvider: CameraProvider | null = null;

/**
 * registerMapPrimitivesProvider attaches a provider that yields the
 * current `Primitive` snapshots. Idempotent — a previously-bound
 * provider is replaced. Returns a deregister function the caller
 * runs on dispose.
 */
export function registerMapPrimitivesProvider(
	provider: PrimitivesProvider,
): () => void {
	primitivesProvider = provider;
	return () => {
		if (primitivesProvider === provider) primitivesProvider = null;
	};
}

/**
 * registerMapPickStateProvider attaches a provider for the current
 * pick-mode state. Same idempotent semantics as the primitives
 * provider.
 */
export function registerMapPickStateProvider(
	provider: PickStateProvider,
): () => void {
	pickStateProvider = provider;
	return () => {
		if (pickStateProvider === provider) pickStateProvider = null;
	};
}

/**
 * registerMapCameraProvider attaches a provider for the current
 * camera + viewport + canvas-origin snapshot. Same idempotent
 * semantics as the other providers.
 */
export function registerMapCameraProvider(
	provider: CameraProvider,
): () => void {
	cameraProvider = provider;
	return () => {
		if (cameraProvider === provider) cameraProvider = null;
	};
}

const EMPTY_PICK_STATE: MapPickStateSnapshot = {
	active: false,
	sourcePlanetNumber: null,
	reachableIds: [],
	hoveredId: null,
};

/** Pulls the current snapshot. Returns an empty array when no map
 * view is mounted. */
export function getMapPrimitives(): readonly MapPrimitiveSnapshot[] {
	return primitivesProvider?.() ?? [];
}

/** Pulls the current pick state. Returns the inactive sentinel
 * snapshot when no map view is mounted. */
export function getMapPickState(): MapPickStateSnapshot {
	return pickStateProvider?.() ?? EMPTY_PICK_STATE;
}

/** Pulls the current camera + viewport snapshot, or `null` when
 * no map view is mounted. */
export function getMapCamera(): MapCameraSnapshot | null {
	return cameraProvider?.() ?? null;
}

interface RendererDebugWindow {
	__galaxyDebug?: {
		getMapPrimitives?: () => readonly MapPrimitiveSnapshot[];
		getMapPickState?: () => MapPickStateSnapshot;
		getMapCamera?: () => MapCameraSnapshot | null;
		[key: string]: unknown;
	};
}

/**
 * installRendererDebugSurface stitches the renderer accessors onto
 * `window.__galaxyDebug`. The DEV-only `/__debug/store` route
 * already registers the keystore / order helpers; navigating to
 * `/games/...` resets the window-bound surface, so the in-game
 * shell calls this on map mount to keep the renderer state
 * accessible to e2e specs that drive the map. Idempotent — repeated
 * calls override the same three methods.
 */
export function installRendererDebugSurface(): () => void {
	if (typeof window === "undefined") return () => {};
	const win = window as unknown as RendererDebugWindow;
	const existing = win.__galaxyDebug ?? {};
	const surface = {
		...existing,
		getMapPrimitives,
		getMapPickState,
		getMapCamera,
	};
	win.__galaxyDebug = surface;
	return (): void => {
		// Detach only the renderer-owned methods; preserve any
		// keystore / order surface the debug route may have
		// installed earlier in the session.
		const current = win.__galaxyDebug;
		if (current === undefined) return;
		if (current.getMapPrimitives === getMapPrimitives) {
			delete current.getMapPrimitives;
		}
		if (current.getMapPickState === getMapPickState) {
			delete current.getMapPickState;
		}
		if (current.getMapCamera === getMapCamera) {
			delete current.getMapCamera;
		}
	};
}
