extends Node2D
## Rain and storm visual overlay.
##
## Rendering choice: procedural _draw() with scrolling raindrop structs.
## Rationale over CPUParticles2D:
##   - No particle system overhead; logic is trivial at ~130 lines.
##   - Exact pixel-art aesthetic — 2 px diagonal lines, integer-aligned.
##   - Raindrop count, velocity, alpha tunable without opening the scene editor.
##   - Storm flash shares the same node + weather listener; no extra scene needed.
##
## Placement: lives inside a CanvasLayer at layer 5 (above z_index tiles,
## below the UI CanvasLayer at 10) so it follows the camera automatically.
## world.tscn owns the CanvasLayer; this script is the Node2D inside it.
##
## Visible only when Weather.is_raining() returns true.
## Storm flash fires every 4–8 s when Weather.is_storming() returns true.

class_name RainOverlay

# ── tuning constants ─────────────────────────────────────────────────────────

const DROP_COUNT: int = 120          ## Total raindrops on screen at once.
const DROP_MIN_LEN: int = 6          ## Minimum raindrop length in pixels.
const DROP_MAX_LEN: int = 10         ## Maximum raindrop length in pixels.
const DROP_VELOCITY_PX: float = 3.0  ## Downward pixels per render frame.
const DROP_SLANT: float = 0.35       ## Horizontal drift per vertical step (right-leaning).
const DROP_COLOR: Color = Color(0.70, 0.85, 1.0, 0.55)
const DROP_COLOR_STORM: Color = Color(0.65, 0.80, 1.0, 0.70)  ## Brighter/denser in storm.

## Viewport dimensions — updated in _ready and on resize.
## We overdraw by a margin so wrapping is seamless.
const OVERDRAW_PX: int = 32

# ── storm flash ──────────────────────────────────────────────────────────────

const FLASH_ALPHA_PEAK: float = 0.40
const FLASH_DURATION_S: float = 0.20

@onready var _flash_rect: ColorRect = $FlashRect
@onready var _flash_timer: Timer = $FlashTimer

# ── internal state ───────────────────────────────────────────────────────────

## Each raindrop: [x: float, y: float, length: int]
var _drops: Array = []

var _viewport_size: Vector2 = Vector2(1280.0, 720.0)

var _raining: bool = false  ## cached so _draw only runs when needed
var _storming: bool = false


func _ready() -> void:
	_viewport_size = get_viewport().get_visible_rect().size

	# Seed raindrop positions randomly across the full viewport + overdraw.
	_drops.clear()
	for i in DROP_COUNT:
		_drops.append(_random_drop(true))

	# Start hidden; listen for weather changes.
	visible = false
	_flash_rect.visible = false
	_flash_rect.color = Color(1.0, 1.0, 1.0, 0.0)

	EventBus.weather_changed.connect(_on_weather_changed)

	# Sync to current weather in case we spawned mid-session.
	_apply_weather(Weather.current_weather)

	get_viewport().size_changed.connect(_on_viewport_resized)


# ── public ───────────────────────────────────────────────────────────────────
## Called by world.gd if it ever needs to force-refresh (not required for now).
func refresh() -> void:
	_apply_weather(Weather.current_weather)


# ── weather events ───────────────────────────────────────────────────────────

func _on_weather_changed(weather: StringName) -> void:
	_apply_weather(weather)


func _apply_weather(weather: StringName) -> void:
	_raining = Weather.is_raining()
	_storming = Weather.is_storming()
	visible = _raining

	if _storming:
		_flash_timer.start(_random_flash_wait())
	else:
		_flash_timer.stop()
		# Ensure flash rect is hidden when not storming.
		_flash_rect.visible = false
		_flash_rect.color = Color(1.0, 1.0, 1.0, 0.0)


# ── raindrop rendering ───────────────────────────────────────────────────────

func _process(delta: float) -> void:
	if not _raining:
		return

	var dy: float = DROP_VELOCITY_PX
	var dx: float = dy * DROP_SLANT

	for i in _drops.size():
		var d: Array = _drops[i]
		d[0] += dx
		d[1] += dy
		# Wrap when the drop + its length scrolls past the bottom or right edge.
		var max_y: float = _viewport_size.y + DROP_MAX_LEN + OVERDRAW_PX
		var max_x: float = _viewport_size.x + DROP_MAX_LEN + OVERDRAW_PX
		if d[1] > max_y or d[0] > max_x:
			_drops[i] = _random_drop(false)
		else:
			_drops[i] = d

	queue_redraw()


func _draw() -> void:
	if not _raining:
		return

	var color: Color = DROP_COLOR_STORM if _storming else DROP_COLOR
	for d in _drops:
		var x: float = d[0]
		var y: float = d[1]
		var length: int = d[2]
		# Diagonal from top-left to bottom-right (slant matches velocity direction).
		var start := Vector2(x, y)
		var end := Vector2(x + length * DROP_SLANT, y + length)
		draw_line(start, end, color, 2.0)


func _random_drop(initial_scatter: bool) -> Array:
	## Returns [x, y, length].
	## initial_scatter=true seeds y anywhere in viewport so first frame isn't blank.
	var x: float = randf() * (_viewport_size.x + OVERDRAW_PX)
	var y: float
	if initial_scatter:
		y = randf() * _viewport_size.y
	else:
		y = -float(DROP_MAX_LEN) - randf() * OVERDRAW_PX
	var length: int = DROP_MIN_LEN + randi() % (DROP_MAX_LEN - DROP_MIN_LEN + 1)
	return [x, y, length]


func _on_viewport_resized() -> void:
	_viewport_size = get_viewport().get_visible_rect().size


# ── storm flash ──────────────────────────────────────────────────────────────

func _on_flash_timer_timeout() -> void:
	if not _storming:
		return
	_do_flash()
	_flash_timer.start(_random_flash_wait())


func _do_flash() -> void:
	_flash_rect.visible = true
	_flash_rect.color = Color(1.0, 1.0, 1.0, 0.0)

	var tw: Tween = create_tween()
	# Ramp up quickly to peak alpha, then fade out.
	tw.tween_property(_flash_rect, "color", Color(1.0, 1.0, 1.0, FLASH_ALPHA_PEAK),
		FLASH_DURATION_S * 0.25)
	tw.tween_property(_flash_rect, "color", Color(1.0, 1.0, 1.0, 0.0),
		FLASH_DURATION_S * 0.75)
	tw.tween_callback(func() -> void: _flash_rect.visible = false)


func _random_flash_wait() -> float:
	return 4.0 + randf() * 4.0  ## 4–8 seconds
