class_name CurrencyGeneratorData
extends Resource

@export var id: StringName = &""
@export var name: String = ""
@export var starts_unlocked: bool = true
@export var starts_available: bool = true
@export var initial_owned: int = 0
@export var purchase_currency: Currency
@export var unlock_goal: GoalData

## Base cost and exponential growth (part I): cost_next = b * r^owned
@export var initial_cost: float = 10.0
@export var coefficient: float = 1.15

## Generator timing and output.
@export var initial_time: float = 1.0
@export var initial_revenue: float = 1.0
@export var initial_productivity: float = 1.0

## Milestone boosts (part I), e.g. every 25 purchased grant x2.
@export var milestone_step: int = 25
@export var milestone_multiplier: float = 2.0

## Optional purchased-only boost from derivative-style models (part II).
## Example: 0.05 means each purchased unit adds +0.05% to output.
@export var purchased_boost_percent: float = 0.0

## Manual click reward for this generator.
@export var click_mantissa: float = 1.0
@export var click_exponent: int = 0
@export var click_cooldown_seconds: float = 0.2

func has_unlock_goal() -> bool:
	if unlock_goal == null:
		return false

	return bool(unlock_goal.is_valid())

func get_unlock_goal_id() -> StringName:
	if not has_unlock_goal():
		return &""

	var goal_id_text: String = String(unlock_goal.id).strip_edges()
	if goal_id_text.is_empty():
		return &""

	return StringName(goal_id_text)

## Returns cost of next generator
func cost_next(owned: int) -> float:
		return cost_for_amount(owned, 1)

## Geometric-series bulk-buy cost (part I):
## total = b * r^k * ((r^n - 1) / (r - 1))
func cost_for_amount(owned: int, amount: int = 1) -> float:
		var safe_owned: int = maxi(owned, 0)
		var safe_amount: int = maxi(amount, 0)
		if safe_amount == 0:
				return 0.0
		if initial_cost <= 0.0:
				return 0.0
		if coefficient <= 0.0:
				return INF

		if is_equal_approx(coefficient, 1.0):
				return initial_cost * float(safe_amount)

		var r_to_owned: float = pow(coefficient, float(safe_owned))
		var r_to_amount: float = pow(coefficient, float(safe_amount))
		return initial_cost * r_to_owned * ((r_to_amount - 1.0) / (coefficient - 1.0))

## Closed-form max-buy estimate from available currency (part I):
## max = floor(log((c(r-1)/(b*r^k))+1) / log(r))
func max_affordable(owned: int, currency: float) -> int:
		if currency <= 0.0:
				return 0
		if initial_cost <= 0.0:
				return 0
		if coefficient <= 0.0:
				return 0

		var safe_owned: int = maxi(owned, 0)
		if is_equal_approx(coefficient, 1.0):
				return maxi(0, floori(currency / initial_cost))

		var denominator: float = initial_cost * pow(coefficient, float(safe_owned))
		if denominator <= 0.0:
				return 0

		var inside: float = (currency * (coefficient - 1.0) / denominator) + 1.0
		if inside <= 1.0:
				return 0

		return maxi(0, floori(log(inside) / log(coefficient)))

func milestone_count(owned: int) -> int:
		if milestone_step <= 0:
				return 0
		return maxi(owned, 0) / milestone_step

func milestone_multiplier_total(owned: int) -> float:
		if milestone_multiplier <= 1.0:
				return 1.0
		var count: int = milestone_count(owned)
		if count <= 0:
				return 1.0
		return pow(milestone_multiplier, float(count))

func purchased_multiplier(purchased: int) -> float:
		if purchased_boost_percent <= 0.0:
				return 1.0
		return 1.0 + (float(maxi(purchased, 0)) * purchased_boost_percent * 0.01)

func production_per_cycle(owned: int, run_multiplier: float = 1.0, purchased: int = -1) -> float:
		var safe_owned: int = maxi(owned, 0)
		if safe_owned == 0:
				return 0.0

		var purchased_count: int = safe_owned if purchased < 0 else maxi(purchased, 0)
		var milestone_mult: float = milestone_multiplier_total(safe_owned)
		var purchased_mult: float = purchased_multiplier(purchased_count)

		return initial_productivity * float(safe_owned) * run_multiplier * milestone_mult * purchased_mult

func production_per_second(owned: int, run_multiplier: float = 1.0, purchased: int = -1) -> float:
		return production_per_cycle(owned, run_multiplier, purchased) / maxf(initial_time, 0.0001)

## Returns value produced
func production_total(owned: int, multipliers: float) -> float:
		return production_per_second(owned, multipliers)

func payback_seconds(owned: int, amount: int = 1, run_multiplier: float = 1.0, purchased: int = -1) -> float:
		var safe_amount: int = maxi(amount, 1)
		var upgrade_cost: float = cost_for_amount(owned, safe_amount)
		if not is_finite(upgrade_cost):
				return INF

		var next_owned: int = maxi(owned, 0) + safe_amount
		var projected_rate: float = production_per_second(next_owned, run_multiplier, purchased)
		if projected_rate <= 0.0:
				return INF

		return upgrade_cost / projected_rate

func income_to_cost_ratio(owned: int, amount: int = 1, run_multiplier: float = 1.0, purchased: int = -1) -> float:
		var safe_amount: int = maxi(amount, 1)
		var upgrade_cost: float = cost_for_amount(owned, safe_amount)
		if upgrade_cost <= 0.0 or not is_finite(upgrade_cost):
				return 0.0

		var projected_rate: float = production_per_second(maxi(owned, 0) + safe_amount, run_multiplier, purchased)
		return projected_rate / upgrade_cost
