// Mass helpers for the Battle Viewer.
//
// Phase 27 refinement: ship-class circles are sized by per-ship
// FullMass (Empty + carrying), with a 6..24 px range. The viewer
// resolves a `(race, className) → ShipClassRef` lookup from the
// surrounding GameReport's `localShipClass` / `otherShipClass`
// tables and feeds it through the wasm bridge to `pkg/calc/ship.go`.
//
// Pure utilities live here; the Svelte components consume them.

import type { Core } from "../../platform/core/index";
import type { BattleReportGroup } from "../../api/battle-fetch";

/** Smallest visible ship circle. Picked so the `<class>:<n>` label
 * stays legible on every viewport. */
export const MIN_RADIUS = 6;

/** Largest ship circle. Matches the Phase-27 baseline so heavy
 * ships keep their previous visual prominence. */
export const MAX_RADIUS = 24;

/**
 * ShipClassRef is the minimum slice of a ship class needed to
 * compute its mass. Mirrors the relevant fields of
 * `ShipClassSummary` (own classes) and `ReportOtherShipClass`
 * (foreign classes) without coupling the viewer to either type.
 */
export interface ShipClassRef {
	drive: number;
	weapons: number;
	armament: number;
	shields: number;
	cargo: number;
}

/**
 * ShipClassLookup resolves `(race, className)` to a ship-class
 * descriptor. Returns `null` when the class is not in the parent
 * report — happens with legacy-mode foreign races that lack a
 * `<Race> Ship Types` block.
 */
export interface ShipClassLookup {
	get(race: string, className: string): ShipClassRef | null;
}

/**
 * computeBattleGroupMass returns the per-ship FullMass for a given
 * battle group. Mass=0 means "unknown" — either the wasm bridge
 * rejected the ship-class params (degenerate weapons/armament pair)
 * or the class did not resolve in the lookup. Either way the
 * caller's downstream `radiusForMass` falls back to MAX_RADIUS so
 * the node stays visible.
 *
 * Cargo never changes during a battle, so this can be cached per
 * `(race, className)` bucket for the lifetime of the viewer
 * session.
 */
export function computeBattleGroupMass(
	group: BattleReportGroup,
	classDef: ShipClassRef | null,
	core: Core,
): number {
	if (classDef === null) return 0;
	const empty = core.emptyMass({
		drive: classDef.drive,
		weapons: classDef.weapons,
		armament: classDef.armament,
		shields: classDef.shields,
		cargo: classDef.cargo,
	});
	if (empty === null) return 0;
	const cargoTech = classDef.cargo * (group.tech.CARGO ?? 0);
	const carrying = core.carryingMass({
		load: group.loadQuantity,
		cargoTech,
	});
	return core.fullMass({ emptyMass: empty, carryingMass: carrying });
}

/**
 * radiusForMass maps an absolute ship mass to a circle radius via
 * a per-battle normalisation: the heaviest visual node always
 * renders at MAX_RADIUS, lighter ones scale by sqrt(mass /
 * maxMassInBattle) so the smallest ships don't disappear and the
 * heaviest ones don't dominate the scene at >MAX_RADIUS. mass<=0
 * falls back to MAX_RADIUS so unresolved/invalid classes stay
 * visible.
 */
export function radiusForMass(mass: number, maxMassInBattle: number): number {
	if (maxMassInBattle <= 0 || mass <= 0) return MAX_RADIUS;
	const scaled = MIN_RADIUS + (MAX_RADIUS - MIN_RADIUS) * Math.sqrt(mass / maxMassInBattle);
	if (scaled < MIN_RADIUS) return MIN_RADIUS;
	if (scaled > MAX_RADIUS) return MAX_RADIUS;
	return scaled;
}

/**
 * MapShipClassLookup is a `Map<string, ShipClassRef>`-backed
 * implementation of `ShipClassLookup`. Key encoding mirrors the
 * one battle.svelte uses when populating the lookup from the
 * parent GameReport.
 */
export class MapShipClassLookup implements ShipClassLookup {
	constructor(private readonly map: Map<string, ShipClassRef>) {}

	get(race: string, className: string): ShipClassRef | null {
		return this.map.get(`${race}::${className}`) ?? null;
	}
}
