// Per-game selection state: which on-map object the user is
// currently inspecting. Phase 13 modelled planets only; Phase 19
// widened the union to ship groups (own / foreign / incoming /
// unidentified).
//
// The store is in-memory only: lifetime matches the in-game shell
// layout instance, which itself is preserved across active-view
// switches inside `/games/:id/*`. Persisting selection across
// reloads is intentionally out of scope — the Phase 13 acceptance
// criterion calls out "across view switches", and survival across a
// reload would be a surprising contrast with the empty-state copy
// users see on first load.
//
// Like `GameStateStore` and `OrderDraftStore`, the store is
// instantiated by the layout and shared with descendants through
// Svelte context. The map view pushes selection events into it; the
// inspector tab and the mobile bottom-sheet read from it.
//
// The store deliberately carries no Svelte component imports so it
// can be tested directly without rendering any UI.

/**
 * ShipGroupRef identifies a ship group inside the current report.
 * `local` groups carry a stable engine UUID (passed through
 * `report.localGroup.id` and used by the upcoming Phase 20 order
 * envelopes). The remaining variants do not — they are addressed by
 * their position in the matching report array, which is fine for
 * the read-only inspector: a new report load reseeds the store and
 * any stale index resolves to a missing entry on lookup, collapsing
 * the inspector cleanly.
 */
export type ShipGroupRef =
	| { variant: "local"; id: string }
	| { variant: "other"; index: number }
	| { variant: "incoming"; index: number }
	| { variant: "unidentified"; index: number };

/**
 * Selected describes the currently selected map object. The
 * discriminated union is closed: every map-clickable surface maps
 * to one of these variants. Future phases (e.g. fleet selection)
 * extend by adding a new branch — extension is purely additive.
 */
export type Selected =
	| { kind: "planet"; id: number }
	| { kind: "shipGroup"; ref: ShipGroupRef };

/**
 * SELECTION_CONTEXT_KEY is the Svelte context key the in-game shell
 * layout uses to expose its `SelectionStore` instance to descendants.
 * Map view, inspector tab, and the mobile bottom-sheet resolve the
 * store via `getContext(SELECTION_CONTEXT_KEY)`.
 */
export const SELECTION_CONTEXT_KEY = Symbol("selection");

export class SelectionStore {
	selected: Selected | null = $state(null);

	// pendingFocus is a transient one-shot request to centre the map
	// camera on a selection. F8-10 tables raise it together with the
	// view switch to map; map.svelte consumes it once on mount (see
	// `consumePendingFocus`) and clears it. Held in memory only — not
	// persisted, so an F5 after a click-through does not re-centre.
	#pendingFocus: Selected | null = $state(null);

	// pendingCenter is the coord-only sibling of pendingFocus: it
	// asks map.svelte to centre on a world point without touching
	// the selection. F8-10 uses it for in-space fleet rows (the
	// `Selected` union has no "fleet" variant, but the user still
	// expects the camera to find them). Also one-shot and transient.
	#pendingCenter: { x: number; y: number } | null = $state(null);

	private destroyed = false;

	/**
	 * selectPlanet sets the active selection to the planet identified
	 * by its engine `number`. A no-op once the store has been disposed.
	 */
	selectPlanet(id: number): void {
		if (this.destroyed) return;
		this.selected = { kind: "planet", id };
	}

	/**
	 * selectShipGroup sets the active selection to a ship group. The
	 * `ref` discriminator carries the variant + the right id shape for
	 * lookup against the current report.
	 */
	selectShipGroup(ref: ShipGroupRef): void {
		if (this.destroyed) return;
		this.selected = { kind: "shipGroup", ref };
	}

	/**
	 * focus sets the active selection to `target` and queues a one-shot
	 * camera-centre request for whichever next mount of the map view
	 * picks it up via `consumePendingFocus`. Used by the F8-10 tables to
	 * navigate from a row click to the map. A no-op once the store has
	 * been disposed.
	 */
	focus(target: Selected): void {
		if (this.destroyed) return;
		this.selected = target;
		this.#pendingFocus = target;
	}

	/**
	 * consumePendingFocus returns the queued focus target (if any) and
	 * clears it in the same call. Designed for `map.svelte` to invoke
	 * once after the renderer mounts.
	 */
	consumePendingFocus(): Selected | null {
		const target = this.#pendingFocus;
		this.#pendingFocus = null;
		return target;
	}

	/**
	 * focusPoint queues a one-shot camera-centre on a free-form world
	 * coordinate, without altering selection. Used by the fleets table
	 * when the user clicks an in-space fleet (there is no `"fleet"`
	 * variant in `Selected`, but the camera should still find it).
	 * A no-op once the store has been disposed.
	 */
	focusPoint(x: number, y: number): void {
		if (this.destroyed) return;
		this.#pendingCenter = { x, y };
	}

	/**
	 * consumePendingCenter returns the queued centre point (if any)
	 * and clears it.
	 */
	consumePendingCenter(): { x: number; y: number } | null {
		const point = this.#pendingCenter;
		this.#pendingCenter = null;
		return point;
	}

	/**
	 * clear drops the current selection. The mobile sheet's close
	 * button calls this; otherwise selection persists across active-
	 * view switches. Does not affect any queued pending focus — that
	 * is a transient delivery channel, not selection state.
	 */
	clear(): void {
		if (this.destroyed) return;
		this.selected = null;
	}

	dispose(): void {
		this.destroyed = true;
		this.selected = null;
		this.#pendingFocus = null;
		this.#pendingCenter = null;
	}
}
