// TS port of `pkg/calc/validator.go.ValidateShipTypeValues` plus a
// thin wrapper that runs the entity-name rules and a duplicate-name
// check against the live `localShipClass` projection. The validator
// is reused by the ship-class designer (`active-view/designer-ship-class.svelte`)
// for inline error messages and by `OrderDraftStore.validateCommand`
// to gate auto-sync, so the local invariants match the engine's
// (`game/internal/controller/ship_class.go.ShipClassCreate`).
//
// Engine rules (from `pkg/calc/validator.go` and `game/rules.txt`):
//
//   - drive, weapons, shields, cargo: float, equal to 0 or >= 1;
//   - armament: integer, >= 0;
//   - either both `armament` and `weapons` are zero or both nonzero;
//   - not all five values may be zero at once;
//   - name must satisfy `validateEntityName` (`pkg/util/string.go`).
//
// The duplicate-name check is a UX-only addition — the engine raises
// `EntityDuplicateIdentifierError` for a duplicate `Create` and the
// auto-sync pipeline would surface that as a `rejected` row, but
// catching it locally keeps the Save button disabled with a clear
// hint instead of a red badge after a wire round-trip.

import {
	validateEntityName,
	type EntityNameInvalidReason,
} from "./entity-name";

/**
 * ShipClassInvalidReason enumerates every reason
 * `validateShipClass` can refuse a draft. Name-derived reasons are
 * the same identifiers `validateEntityName` returns, so the
 * designer's `aria-describedby` mapping reuses the existing
 * translation keys for those branches and adds new keys only for
 * the value-derived ones.
 */
export type ShipClassInvalidReason =
	| EntityNameInvalidReason
	| "duplicate_name"
	| "drive_value"
	| "armament_value"
	| "armament_not_integer"
	| "weapons_value"
	| "shields_value"
	| "cargo_value"
	| "armament_weapons_pair"
	| "all_zero";

/**
 * ShipClassDraft is the structural shape the designer composes. The
 * five numeric fields carry the player's typed values verbatim;
 * `validateShipClass` is responsible for refusing non-finite or
 * out-of-range entries.
 */
export interface ShipClassDraft {
	name: string;
	drive: number;
	armament: number;
	weapons: number;
	shields: number;
	cargo: number;
}

export type ShipClassValidation =
	| { ok: true; value: ShipClassDraft }
	| { ok: false; reason: ShipClassInvalidReason };

/**
 * validateShipClass mirrors `ValidateShipTypeValues` plus the
 * entity-name rules. `existingNames` is the optimistic projection of
 * already-designed ship classes (from `localShipClass` after
 * `applyOrderOverlay`). When the trimmed name matches any entry,
 * the validator returns `duplicate_name` so the designer's Save
 * button stays disabled. Pass an empty array (or omit the option)
 * to skip the duplicate check — useful for unit tests of the
 * value-only rules.
 */
export function validateShipClass(
	draft: ShipClassDraft,
	options: { existingNames?: readonly string[] } = {},
): ShipClassValidation {
	const nameResult = validateEntityName(draft.name);
	if (!nameResult.ok) {
		return { ok: false, reason: nameResult.reason };
	}
	const trimmedName = nameResult.value;

	if (!isValidDWSC(draft.drive)) {
		return { ok: false, reason: "drive_value" };
	}
	if (!Number.isFinite(draft.armament) || draft.armament < 0) {
		return { ok: false, reason: "armament_value" };
	}
	if (!Number.isInteger(draft.armament)) {
		return { ok: false, reason: "armament_not_integer" };
	}
	if (!isValidDWSC(draft.weapons)) {
		return { ok: false, reason: "weapons_value" };
	}
	if (!isValidDWSC(draft.shields)) {
		return { ok: false, reason: "shields_value" };
	}
	if (!isValidDWSC(draft.cargo)) {
		return { ok: false, reason: "cargo_value" };
	}
	if (
		(draft.armament === 0 && draft.weapons !== 0) ||
		(draft.armament !== 0 && draft.weapons === 0)
	) {
		return { ok: false, reason: "armament_weapons_pair" };
	}
	if (
		draft.drive === 0 &&
		draft.armament === 0 &&
		draft.weapons === 0 &&
		draft.shields === 0 &&
		draft.cargo === 0
	) {
		return { ok: false, reason: "all_zero" };
	}

	const existing = options.existingNames ?? [];
	if (existing.some((existingName) => existingName === trimmedName)) {
		return { ok: false, reason: "duplicate_name" };
	}

	return {
		ok: true,
		value: { ...draft, name: trimmedName },
	};
}

/**
 * isValidDWSC mirrors `pkg/calc/validator.go.CheckShipTypeValueDWSC`:
 * a Drive / Weapons / Shields / Cargo value is acceptable only when
 * it is exactly zero or at least one. NaN, infinity, and negative
 * numbers are rejected.
 */
function isValidDWSC(value: number): boolean {
	if (!Number.isFinite(value)) return false;
	return value === 0 || value >= 1;
}
