class_name BigNumber
extends RefCounted

## The core value representation. 
## For example, 1.5e24 is represented as mantissa = 1.5, exponent = 24.
var mantissa: float
var exponent: int

## Pre-calculate this to avoid dividing logs constantly
const LOG10_E: float = 0.4342944819032518

func _init(m: float = 0.0, e: int = 0) -> void:
	mantissa = m
	exponent = e
	_normalize()

## Adjusts the internal representation so the mantissa is always between 1.0 and 9.99...
## or exactly 0.0.
func _normalize() -> void:
	if mantissa == 0.0:
		exponent = 0
		return
		
	var is_negative: bool = mantissa < 0.0
	var abs_m: float = abs(mantissa)

	if abs_m >= 10.0 or abs_m < 1.0:
		# Calculate how many powers of 10 we need to shift
		# Godot's log() is base 'e', so we multiply by log10(e) to get log10(x)
		var exp_diff: int = floori(log(abs_m) * LOG10_E) 
		abs_m /= pow(10.0, float(exp_diff))
		exponent += exp_diff

	mantissa = abs_m if not is_negative else -abs_m

# ==========================================
# MATH OPERATIONS
# ==========================================

func add(other: BigNumber) -> BigNumber:
	if mantissa == 0: return BigNumber.new(other.mantissa, other.exponent)
	if other.mantissa == 0: return BigNumber.new(mantissa, exponent)
	
	var exp_diff: int = exponent - other.exponent
	
	# If the difference in magnitude is massive, the smaller number is insignificant
	# (15 is a safe threshold for 64-bit float precision).
	if exp_diff >= 15:
		return BigNumber.new(mantissa, exponent)
	elif exp_diff <= -15:
		return BigNumber.new(other.mantissa, other.exponent)

	var new_m: float = mantissa
	var new_e: int = exponent

	# Scale the smaller number down to match the larger number's exponent
	if exp_diff > 0:
		new_m += other.mantissa / pow(10.0, float(exp_diff))
	elif exp_diff < 0:
		new_m = (mantissa / pow(10.0, float(-exp_diff))) + other.mantissa
		new_e = other.exponent
	else:
		new_m += other.mantissa

	return BigNumber.new(new_m, new_e)

## High-performance addition for the _process() loop.
## Modifies THIS instance instead of creating a new RefCounted object.
func add_in_place(other: BigNumber) -> void:
	if other.mantissa == 0.0: return
	if mantissa == 0.0:
		mantissa = other.mantissa
		exponent = other.exponent
		return
		
	var exp_diff: int = exponent - other.exponent
	
	if exp_diff >= 15:
		return # Other number is too small to matter
	elif exp_diff <= -15:
		# This number is effectively replaced by the larger other number
		mantissa = other.mantissa
		exponent = other.exponent
		return

	if exp_diff > 0:
		mantissa += other.mantissa / pow(10.0, float(exp_diff))
	elif exp_diff < 0:
		mantissa = (mantissa / pow(10.0, float(-exp_diff))) + other.mantissa
		exponent = other.exponent
	else:
		mantissa += other.mantissa

	# Prevent floating-point drift near zero
	if abs(mantissa) < 0.0000000001:
		mantissa = 0.0
		exponent = 0
	else:
		_normalize()

func subtract(other: BigNumber) -> BigNumber:
	# Subtraction is just adding a negative number
	var negative_other = BigNumber.new(-other.mantissa, other.exponent)
	return add(negative_other)

func multiply(other: BigNumber) -> BigNumber:
	var new_m: float = mantissa * other.mantissa
	var new_e: int = exponent + other.exponent
	return BigNumber.new(new_m, new_e)

func divide(other: BigNumber) -> BigNumber:
	if other.mantissa == 0.0:
		push_error("BigNumber: Division by zero!")
		return BigNumber.new(0.0, 0)
		
	var new_m: float = mantissa / other.mantissa
	var new_e: int = exponent - other.exponent
	return BigNumber.new(new_m, new_e)

# ==========================================
# COMPARISONS
# ==========================================

## Returns 1 if this > other, -1 if this < other, 0 if equal
func compare_to(other: BigNumber) -> int:
	if mantissa == 0.0 and other.mantissa == 0.0:
		return 0

	# Handle zero explicitly before sign/exponent checks.
	if mantissa == 0.0:
		return -1 if other.mantissa > 0.0 else 1
	if other.mantissa == 0.0:
		return 1 if mantissa > 0.0 else -1

	# Handle signs
	if mantissa > 0 and other.mantissa < 0:
		return 1
	if mantissa < 0 and other.mantissa > 0:
		return -1

	# Both are same sign. Compare exponents first.
	var sign_mult: int = 1 if mantissa > 0 else -1

	if exponent > other.exponent:
		return sign_mult
	if exponent < other.exponent:
		return -sign_mult

	# Exponents are equal, compare mantissas
	if mantissa > other.mantissa:
		return 1
	if mantissa < other.mantissa:
		return -1

	return 0

func is_greater_than(other: BigNumber) -> bool:
	return compare_to(other) == 1

func is_less_than(other: BigNumber) -> bool:
	return compare_to(other) == -1

func is_equal_to(other: BigNumber) -> bool:
	return compare_to(other) == 0

## Calculates the progress ratio between this number and a target number.
## Returns a standard float clamped between 0.0 and 1.0 for UI progress bars.
func get_ratio(target: BigNumber) -> float:
	if target.mantissa == 0.0:
		return 1.0 # If the goal is 0, you've already beaten it!
	if mantissa == 0.0:
		return 0.0
		
	var exp_diff: int = exponent - target.exponent
	
	# If the target is massively larger, progress is practically 0%
	if exp_diff <= -15:
		return 0.0
		
	# If current is equal or greater, progress is 100%
	if exp_diff >= 15 or is_greater_than(target) or is_equal_to(target):
		return 1.0
		
	# Calculate the actual float ratio
	var ratio: float = (mantissa / target.mantissa) * pow(10.0, float(exp_diff))
	
	# Clamp it just to be perfectly safe for UI elements
	return clampf(ratio, 0.0, 1.0)

# ==========================================
# UTILITIES
# ==========================================

## Creates a BigNumber from a standard float or int
static func from_float(val: float) -> BigNumber:
	return BigNumber.new(val, 0)

## Outputs a UI-friendly string (e.g., "1.50e12")
func to_string_sci(decimals: int = 2) -> String:
	if exponent < 3:
		# For small numbers, just show the regular number
		var val: float = mantissa * pow(10.0, float(exponent))
		return ("%." + str(decimals) + "f") % val
		
	var format_str: String = "%." + str(decimals) + "f"
	return (format_str % mantissa) + "e" + str(exponent)

## Optional: Standard idle game suffix formatting (K, M, B, T, Qa, etc.)
func to_string_suffix(decimals: int = 2) -> String:
	if exponent < 3:
		return to_string_sci(decimals)
		
	var suffixes = ["", "K", "M", "B", "T", "Qa", "Qi", "Sx", "Sp", "Oc", "No", "Dc"]
	var suffix_index: int = floori(exponent / 3.0)
	
	if suffix_index < suffixes.size():
		var display_mantissa = mantissa * pow(10.0, float(exponent % 3))
		var format_str: String = "%." + str(decimals) + "f"
		return (format_str % display_mantissa) + suffixes[suffix_index]
	else:
		# Fall back to scientific if we run out of suffixes
		return to_string_sci(decimals)

# ==========================================
# SAVE & LOAD (SERIALIZATION)
# ==========================================

## Converts the BigNumber into a basic Dictionary for easy JSON saving.
func serialize() -> Dictionary:
	return {
		"m": mantissa,
		"e": exponent
	}

## A static factory method that creates a new BigNumber from loaded Dictionary data.
static func deserialize(data: Dictionary) -> BigNumber:
	# Provide fallbacks (0.0 and 0) just in case the save file is corrupted
	var loaded_m: float = data.get("m", 0.0)
	var loaded_e: int = data.get("e", 0)
	return BigNumber.new(loaded_m, loaded_e)
