package calc

// This file holds the inverse ("goal-seek") counterparts of the forward
// ship formulas. The ship-class calculator lets a player pin one derived
// result and back-solve the single input it claims; each solver inverts
// exactly one forward function so the math stays in this package rather
// than leaking into the UI bridge. Every solver reports ok == false when
// the request is infeasible (e.g. an unreachable target or a division by
// a non-positive tech level), leaving the returned value undefined.

// WeaponsForAttack returns the weapons block that yields targetAttack at
// weapons tech level weaponsTech, inverting [EffectiveAttack]. It is
// infeasible when weaponsTech is non-positive or targetAttack is
// negative.
func WeaponsForAttack(targetAttack, weaponsTech float64) (float64, bool) {
	if weaponsTech <= 0 || targetAttack < 0 {
		return 0, false
	}
	return targetAttack / weaponsTech, true
}

// DriveForSpeed returns the drive block that yields targetSpeed for a
// ship whose mass excluding the drive block is restMass, at drive tech
// level driveTech, inverting [Speed] composed with [DriveEffective].
// With a positive restMass the speed approaches but never reaches the
// stripped-hull ceiling 20*driveTech, so a target at or above the
// ceiling is infeasible. With restMass==0 the drive block carries no
// other mass: every positive drive yields exactly the ceiling speed, so
// the ceiling target is the only feasible one and any positive drive
// (canonically 1) solves it. Non-positive targetSpeed or driveTech are
// always infeasible.
func DriveForSpeed(targetSpeed, driveTech, restMass float64) (float64, bool) {
	if driveTech <= 0 || targetSpeed <= 0 {
		return 0, false
	}
	ceiling := 20 * driveTech
	if restMass <= 0 {
		if targetSpeed != ceiling {
			return 0, false
		}
		return 1, true
	}
	if targetSpeed >= ceiling {
		return 0, false
	}
	return targetSpeed * restMass / (ceiling - targetSpeed), true
}

// ShieldsForDefence returns the shields block that yields targetDefence
// for a ship whose mass excluding the shields block is restMass, at
// shields tech level shieldsTech, inverting [EffectiveDefence]. Defence
// rises monotonically with shields (the block adds mass to its own
// denominator), so the block is found by bisection. It is infeasible when
// targetDefence or shieldsTech is non-positive.
func ShieldsForDefence(targetDefence, shieldsTech, restMass float64) (float64, bool) {
	if targetDefence <= 0 || shieldsTech <= 0 {
		return 0, false
	}
	lo, hi := 0.0, 1.0
	for EffectiveDefence(hi, shieldsTech, hi+restMass) < targetDefence {
		hi *= 2
		if hi > 1e12 {
			return 0, false
		}
	}
	for range 100 {
		mid := (lo + hi) / 2
		if EffectiveDefence(mid, shieldsTech, mid+restMass) < targetDefence {
			lo = mid
		} else {
			hi = mid
		}
	}
	return (lo + hi) / 2, true
}

// CargoForEmptyMass returns the cargo block that brings a ship's empty
// mass to targetEmptyMass, given restMass — the combined mass of the
// other blocks (drive, shields, and the weapons block) — inverting the
// cargo term of [EmptyMass]. It is infeasible when targetEmptyMass is
// below restMass, which would require a negative cargo block.
func CargoForEmptyMass(targetEmptyMass, restMass float64) (float64, bool) {
	cargo := targetEmptyMass - restMass
	if cargo < 0 {
		return 0, false
	}
	return cargo, true
}

// LoadForFullMass returns the cargo load that brings a ship's full mass
// to targetFullMass, given its empty mass and cargo tech level, inverting
// [CarryingMass] inside [FullMass]. It is infeasible when targetFullMass
// is below emptyMass or cargoTech is non-positive.
func LoadForFullMass(targetFullMass, emptyMass, cargoTech float64) (float64, bool) {
	if cargoTech <= 0 || targetFullMass < emptyMass {
		return 0, false
	}
	return (targetFullMass - emptyMass) * cargoTech, true
}
