// makeFakeCore builds a complete `Core` whose calc methods mirror
// `pkg/calc` exactly, for component and unit tests that must not boot the
// real WASM module. The committed `core.wasm` is rebuilt out-of-band
// (`make wasm`, needs TinyGo), so tests that exercise calculator math
// inject this fake instead of depending on a freshly built binary. The
// Go parity tests in `ui/core/calc` guarantee the real bridge agrees with
// `pkg/calc`, so a fake that also mirrors `pkg/calc` stays faithful.
//
// Pass `overrides` to replace individual methods — e.g. `vi.fn()` spies
// when a test wants to assert how the calc-model orchestrates the bridge.

import type { Core } from "../src/platform/core/index";

function weaponsBlockMass(weapons: number, armament: number): number | null {
	if ((armament === 0 && weapons !== 0) || (armament !== 0 && weapons === 0)) {
		return null;
	}
	return (armament + 1) * (weapons / 2);
}

export function makeFakeCore(overrides: Partial<Core> = {}): Core {
	const base: Core = {
		signRequest: () => new Uint8Array(),
		verifyResponse: () => true,
		verifyEvent: () => true,
		verifyPayloadHash: () => true,
		driveEffective: ({ drive, driveTech }) => drive * driveTech,
		emptyMass: ({ drive, weapons, armament, shields, cargo }) => {
			const wb = weaponsBlockMass(weapons, armament);
			if (wb === null) return null;
			return drive + shields + cargo + wb;
		},
		weaponsBlockMass: ({ weapons, armament }) =>
			weaponsBlockMass(weapons, armament),
		fullMass: ({ emptyMass, carryingMass }) => emptyMass + carryingMass,
		speed: ({ driveEffective, fullMass }) =>
			fullMass <= 0 ? 0 : (driveEffective * 20) / fullMass,
		cargoCapacity: ({ cargo, cargoTech }) =>
			cargoTech * (cargo + (cargo * cargo) / 20),
		carryingMass: ({ load, cargoTech }) => (load <= 0 ? 0 : load / cargoTech),
		blockUpgradeCost: ({ blockMass, currentTech, targetTech }) =>
			blockMass === 0 || targetTech <= currentTech
				? 0
				: (1 - currentTech / targetTech) * 10 * blockMass,
		effectiveAttack: ({ weapons, weaponsTech }) => weapons * weaponsTech,
		effectiveDefence: ({ shields, shieldsTech, fullMass }) =>
			fullMass <= 0
				? 0
				: ((shields * shieldsTech) / Math.cbrt(fullMass)) * Math.cbrt(30),
		bombingPower: ({ weapons, weaponsTech, armament, number }) =>
			(Math.sqrt(weapons * weaponsTech) / 10 + 1) *
			weapons *
			weaponsTech *
			armament *
			number,
		shipBuildCost: ({ shipMass, material, resources }) => {
			const matNeed = Math.max(0, shipMass - material);
			const matFarm = resources > 0 ? matNeed / resources : 0;
			return shipMass * 10 + matFarm;
		},
		produceShipsInTurn: ({
			productionAvailable,
			material,
			resources,
			shipMass,
		}) => {
			if (productionAvailable <= 0 || shipMass <= 0) {
				return {
					ships: 0,
					materialLeft: material,
					productionUsed: 0,
					progress: 0,
				};
			}
			let pa = productionAvailable;
			let mat = material;
			let ships = 0;
			for (;;) {
				const matNeed = Math.max(0, shipMass - mat);
				const cost = shipMass * 10 + (resources > 0 ? matNeed / resources : 0);
				if (pa < cost) {
					return {
						ships,
						materialLeft: mat,
						productionUsed: pa,
						progress: pa / cost,
					};
				}
				pa -= cost;
				mat = mat - shipMass + matNeed;
				ships += 1;
			}
		},
		weaponsForAttack: ({ targetAttack, weaponsTech }) =>
			weaponsTech <= 0 || targetAttack < 0 ? null : targetAttack / weaponsTech,
		driveForSpeed: ({ targetSpeed, driveTech, restMass }) => {
			if (driveTech <= 0 || targetSpeed <= 0) return null;
			const ceiling = 20 * driveTech;
			if (restMass <= 0) {
				if (targetSpeed !== ceiling) return null;
				return 1;
			}
			if (targetSpeed >= ceiling) return null;
			return (targetSpeed * restMass) / (ceiling - targetSpeed);
		},
		shieldsForDefence: ({ targetDefence, shieldsTech, restMass }) => {
			if (targetDefence <= 0 || shieldsTech <= 0) return null;
			const def = (s: number) =>
				((s * shieldsTech) / Math.cbrt(s + restMass)) * Math.cbrt(30);
			let lo = 0;
			let hi = 1;
			while (def(hi) < targetDefence) {
				hi *= 2;
				if (hi > 1e12) return null;
			}
			for (let i = 0; i < 100; i++) {
				const mid = (lo + hi) / 2;
				if (def(mid) < targetDefence) lo = mid;
				else hi = mid;
			}
			return (lo + hi) / 2;
		},
		cargoForEmptyMass: ({ targetEmptyMass, restMass }) =>
			targetEmptyMass - restMass < 0 ? null : targetEmptyMass - restMass,
		loadForFullMass: ({ targetFullMass, emptyMass, cargoTech }) =>
			cargoTech <= 0 || targetFullMass < emptyMass
				? null
				: (targetFullMass - emptyMass) * cargoTech,
		ceil3: ({ value }) => Math.ceil(Math.round(value * 1e9) / 1e6) / 1000,
	};
	return { ...base, ...overrides };
}
