// `WasmCore` is the browser-side `Core` implementation. It loads the
// TinyGo-built `core.wasm` module + the matching `wasm_exec.js` runtime
// shim and bridges the four exported functions on `globalThis.galaxyCore`
// to the typed `Core` interface.
//
// The same module also runs under Node/JSDOM (via Vitest) by reading
// the bundle from disk; the test loader lives in
// `tests/setup-wasm.ts`. The browser path is the production target
// served from `static/core.wasm`.

import type {
	BlockUpgradeCostInput,
	CargoCapacityInput,
	CarryingMassInput,
	Core,
	DriveEffectiveInput,
	EventSigningFields,
	FullMassInput,
	RequestSigningFields,
	ResponseSigningFields,
	ShipBlocksInput,
	SpeedInput,
	WeaponsBlockInput,
} from "./index";

/**
 * GalaxyCoreBridge is the shape Go installs on `globalThis.galaxyCore`.
 * It is purely an internal contract between `ui/wasm/main.go` and this
 * adapter; consumers see only the typed `Core` interface.
 */
interface GalaxyCoreBridge {
	signRequest(fields: BridgeRequestFields): Uint8Array;
	verifyResponse(
		publicKey: Uint8Array,
		signature: Uint8Array,
		fields: BridgeResponseFields,
	): boolean;
	verifyEvent(
		publicKey: Uint8Array,
		signature: Uint8Array,
		fields: BridgeEventFields,
	): boolean;
	verifyPayloadHash(
		payloadBytes: Uint8Array,
		payloadHash: Uint8Array,
	): boolean;
	driveEffective(input: DriveEffectiveInput): number;
	emptyMass(input: ShipBlocksInput): number | null;
	weaponsBlockMass(input: WeaponsBlockInput): number | null;
	fullMass(input: FullMassInput): number;
	speed(input: SpeedInput): number;
	cargoCapacity(input: CargoCapacityInput): number;
	carryingMass(input: CarryingMassInput): number;
	blockUpgradeCost(input: BlockUpgradeCostInput): number;
}

interface BridgeRequestFields {
	protocolVersion: string;
	deviceSessionId: string;
	messageType: string;
	timestampMs: number;
	requestId: string;
	payloadHash: Uint8Array;
}

interface BridgeResponseFields {
	protocolVersion: string;
	requestId: string;
	timestampMs: number;
	resultCode: string;
	payloadHash: Uint8Array;
}

interface BridgeEventFields {
	eventType: string;
	eventId: string;
	timestampMs: number;
	requestId: string;
	traceId: string;
	payloadHash: Uint8Array;
}

declare global {
	// eslint-disable-next-line no-var
	var galaxyCore: GalaxyCoreBridge | undefined;
	// eslint-disable-next-line no-var
	var Go: { new (): GoRuntime } | undefined;
}

interface GoRuntime {
	importObject: WebAssembly.Imports;
	run(instance: WebAssembly.Instance): Promise<void>;
}

/**
 * loadWasmCore boots the TinyGo `core.wasm` module in the browser and
 * returns a `Core` whose methods bridge into the Go-exported functions.
 * The module is started exactly once per page; subsequent calls return
 * the cached instance. The browser path expects `wasm_exec.js` to be
 * served alongside `core.wasm` under SvelteKit's `static/` root.
 */
let cached: Promise<Core> | undefined;

export function loadWasmCore(): Promise<Core> {
	if (!cached) {
		cached = bootBrowserWasm();
	}
	return cached;
}

async function bootBrowserWasm(): Promise<Core> {
	if (typeof window === "undefined") {
		throw new Error(
			"loadWasmCore: no `window` global; load via tests/setup-wasm.ts under JSDOM",
		);
	}
	await ensureGoRuntimeLoaded();
	const Go = globalThis.Go;
	if (!Go) {
		throw new Error("loadWasmCore: Go runtime missing after wasm_exec.js load");
	}
	const go = new Go();
	const response = await fetch("/core.wasm");
	const bytes = await response.arrayBuffer();
	const { instance } = await WebAssembly.instantiate(bytes, go.importObject);
	void go.run(instance);
	return adaptBridge(requireBridge());
}

async function ensureGoRuntimeLoaded(): Promise<void> {
	if (typeof globalThis.Go !== "undefined") {
		return;
	}
	await new Promise<void>((resolve, reject) => {
		const script = document.createElement("script");
		script.src = "/wasm_exec.js";
		script.onload = () => resolve();
		script.onerror = () => reject(new Error("failed to load /wasm_exec.js"));
		document.head.appendChild(script);
	});
}

/**
 * adaptBridge wraps the raw Go-installed bridge with the `Core` shape,
 * normalising `bigint` ↔ JS Number conversion at the timestamp boundary.
 * The bridge is also exported for the JSDOM test loader, which boots
 * the WASM module differently (see `tests/setup-wasm.ts`).
 */
export function adaptBridge(bridge: GalaxyCoreBridge): Core {
	return {
		signRequest(fields: RequestSigningFields): Uint8Array {
			return bridge.signRequest({
				protocolVersion: fields.protocolVersion,
				deviceSessionId: fields.deviceSessionId,
				messageType: fields.messageType,
				timestampMs: bigintToNumber(fields.timestampMs),
				requestId: fields.requestId,
				payloadHash: fields.payloadHash,
			});
		},
		verifyResponse(
			publicKey: Uint8Array,
			signature: Uint8Array,
			fields: ResponseSigningFields,
		): boolean {
			return bridge.verifyResponse(publicKey, signature, {
				protocolVersion: fields.protocolVersion,
				requestId: fields.requestId,
				timestampMs: bigintToNumber(fields.timestampMs),
				resultCode: fields.resultCode,
				payloadHash: fields.payloadHash,
			});
		},
		verifyEvent(
			publicKey: Uint8Array,
			signature: Uint8Array,
			fields: EventSigningFields,
		): boolean {
			return bridge.verifyEvent(publicKey, signature, {
				eventType: fields.eventType,
				eventId: fields.eventId,
				timestampMs: bigintToNumber(fields.timestampMs),
				requestId: fields.requestId,
				traceId: fields.traceId,
				payloadHash: fields.payloadHash,
			});
		},
		verifyPayloadHash(
			payloadBytes: Uint8Array,
			payloadHash: Uint8Array,
		): boolean {
			return bridge.verifyPayloadHash(payloadBytes, payloadHash);
		},
		driveEffective(input: DriveEffectiveInput): number {
			return bridge.driveEffective(input);
		},
		emptyMass(input: ShipBlocksInput): number | null {
			return bridge.emptyMass(input);
		},
		weaponsBlockMass(input: WeaponsBlockInput): number | null {
			return bridge.weaponsBlockMass(input);
		},
		fullMass(input: FullMassInput): number {
			return bridge.fullMass(input);
		},
		speed(input: SpeedInput): number {
			return bridge.speed(input);
		},
		cargoCapacity(input: CargoCapacityInput): number {
			return bridge.cargoCapacity(input);
		},
		carryingMass(input: CarryingMassInput): number {
			return bridge.carryingMass(input);
		},
		blockUpgradeCost(input: BlockUpgradeCostInput): number {
			return bridge.blockUpgradeCost(input);
		},
	};
}

export function requireBridge(): GalaxyCoreBridge {
	const bridge = globalThis.galaxyCore;
	if (!bridge) {
		throw new Error(
			"loadWasmCore: `globalThis.galaxyCore` not installed — was the WASM module instantiated?",
		);
	}
	return bridge;
}

// Unix-millisecond timestamps fit in 53 bits well past the year 2200, so
// the bigint → number narrowing is safe for every realistic value the
// envelope contract carries.
function bigintToNumber(value: bigint): number {
	return Number(value);
}
