@icon("res://src/ui/hud/timer/stopwatch.svg")
class_name TimescalableTimer
extends Node


## A timescalable-countdown timer.
##
## The [b]Timer[/b] node is a countdown timer and is the simplest way to handle time-based logic in the engine.
## When a timer reaches the end of its [member wait_time], it will emit the [signal timeout] signal.[br][br]
## After a timer enters the tree, it can be manually started with [method start].
## A timer node is also started automatically if [member autostart] is [code]true[/code].[br][br]
## Without requiring much code, a timer node can be added and configured in the editor.
## The [signal timeout] signal it emits can also be connected through the Node dock in the editor:
##
## [codeblock]
## func _on_timer_timeout():
##     print("Time to attack!")
## [/codeblock]
## [b]Note:[/b] To create a one-shot timer without instantiating a node, use [method SceneTree.create_timer].[br][br]
## [b]Note:[/b] Timers are affected by [member Engine.time_scale].
## The higher the time scale, the sooner timers will end.
## How often a timer processes may depend on the framerate or [member Engine.physics_ticks_per_second].


## Emitted when the timer reaches the end.
signal timeout

## See documentation for [param process_callback] in [Timer]
@export var process_callback: Timer.TimerProcessCallback
## The time required for the timer to end, in seconds. This property can also be set every time [member start] is called.
##
## [b]Note[/b]: Timers can only process once per physics or process frame (depending on the [member process_callback]).
## An unstable framerate may cause the timer to end inconsistently,
## which is especially noticeable if the wait time is lower than roughly [code]0.05[/code] seconds.
## For very short timers, it is recommended to write your own code instead of using a [Timer] node.
## Timers are also affected by [Engine.time_scale].
@export_range(0.001, 4096.0, 0.001, "suffix:s") var wait_time: float = 1.0: get = get_wait_time, set = set_wait_time
## The maximum allowed elapsed time we can have.[br]If this is under [code]0[/code]
## we don't have any limit and we count towards infinity.
@export var one_shot: bool = false
## If [code]true[/code], the stopwatch will call [method start] when entering the scene tree.
@export var autostart: bool = false
## Manipulates, how fast or slow the timer will count down.
@export_range(-10.0, 10.0, 0.01, "or_greater", "or_less") var time_scale: float = 1.0

var time_left: float = -1.0: get = get_time_left
var paused: bool = false: set = set_paused, get = is_paused
var _stopped: bool = true


func _ready() -> void:
	if autostart:
		start()


func _process(delta: float) -> void:
	if process_callback == Timer.TIMER_PROCESS_IDLE:
		_process_timer(delta)


func _physics_process(delta: float) -> void:
	if process_callback == Timer.TIMER_PROCESS_PHYSICS:
		_process_timer(delta)


func _process_timer(delta: float) -> void:
	if is_stopped() or is_paused():
		return
	
	time_left = maxf(time_left - delta * time_scale, 0.0)
	
	if time_left == 0.0:
		timeout.emit()
		
		if one_shot:
			stop()
		else:
			time_left += wait_time


#region Starting/Stopping
## Starts the stopwatch. [param custom_start_time] will set the elapsed time to it instead of
## using [param start_time].
func start(time_sec: float = -1.0) -> void:
	if time_sec >= 0.0:
		wait_time = time_sec
	
	time_left = wait_time
	_stopped = false
	paused = false


## Stops the stopwatch.
func stop() -> float:
	var final_time: float = time_left
	
	time_left = -1.0
	_stopped = true
	
	return final_time
#endregion


func get_wait_time() -> float:
	return wait_time


func set_wait_time(value: float) -> void:
	wait_time = value


## Returns the elapsed time since this stopwatch was started.
func get_time_left() -> float:
	return time_left if not is_stopped() else 0.0


## Returns [code]true[/code] if the timer is stopped or has not started.
func is_stopped() -> bool:
	return _stopped


func set_paused(value: bool) -> void:
	paused = value

func is_paused() -> bool:
	return paused
