extends Node
## Daily weather roll + queries.
##
## Rolls once per in-game day (triggered by EventBus.sim_tick), using
## season-weighted probabilities from design.md §"Weather — 4 types, daily roll".
## Emits EventBus.weather_changed(weather) when the kind changes.
##
## Public API (locked — Agents A + C compile against these names):
##   const WEATHER_CLEAR / RAIN / STORM / COLD_SNAP : StringName
##   current_weather: StringName
##   is_raining() -> bool   # rain OR storm
##   is_storming() -> bool  # storm only
##   is_cold_snap() -> bool # cold_snap only
##   save_dict() / apply_dict()  # day_index + current_weather round-trip

const WEATHER_CLEAR:     StringName = &"clear"
const WEATHER_RAIN:      StringName = &"rain"
const WEATHER_STORM:     StringName = &"storm"
const WEATHER_COLD_SNAP: StringName = &"cold_snap"

## Current weather kind. Changes once per in-game day.
var current_weather: StringName = WEATHER_CLEAR

## Last day index we rolled for; -1 means no roll yet this session.
## Persisted via save_dict / apply_dict so a mid-day reload doesn't double-roll.
var _last_day_index: int = -1

# Season-weighted probability tables.
# Each entry is [clear_weight, rain_weight, storm_weight, cold_snap_weight].
# Weights are fractions that sum to 1.0 — design.md values (placeholders, tune Phase 20).
#
# spring: 60% clear, 35% rain, 5% storm
# summer: 75% clear, 18% rain, 7% storm
# autumn: 50% clear, 35% rain, 12% storm, 3% cold_snap
# winter: 55% clear, 15% rain, 10% storm, 20% cold_snap
#
# Note: design.md lists slightly different values (e.g. summer 70/20/10).
# Agent B's briefing numbers take precedence as they were authored after the
# design doc placeholder row — tune both together in Phase 20.
const _SEASON_WEIGHTS: Dictionary = {
	&"spring": [0.60, 0.35, 0.05, 0.00],
	&"summer": [0.75, 0.18, 0.07, 0.00],
	&"autumn": [0.50, 0.35, 0.12, 0.03],
	&"winter": [0.55, 0.15, 0.10, 0.20],
}

# Maps weight-table index → weather kind constant.
const _WEATHER_KINDS: Array[StringName] = [
	WEATHER_CLEAR,
	WEATHER_RAIN,
	WEATHER_STORM,
	WEATHER_COLD_SNAP,
]


func _ready() -> void:
	EventBus.sim_tick.connect(_on_sim_tick)


# ── sim tick handler ─────────────────────────────────────────────────────────

func _on_sim_tick(_tick_n: int) -> void:
	var day_idx: int = Clock.day_index_from_start()
	if day_idx == _last_day_index:
		return  # same day — no roll needed
	_last_day_index = day_idx
	_roll(day_idx)


func _roll(day_idx: int) -> void:
	var season: StringName = Clock.current_season()
	var weights: Array = _SEASON_WEIGHTS.get(season, _SEASON_WEIGHTS[&"spring"])

	var roll: float = randf()
	var cumulative: float = 0.0
	var new_weather: StringName = WEATHER_CLEAR

	for i in _WEATHER_KINDS.size():
		cumulative += weights[i]
		if roll <= cumulative:
			new_weather = _WEATHER_KINDS[i]
			break

	Audit.log("weather", "day %d %s → %s (roll %.3f)" % [day_idx, season, new_weather, roll])

	if new_weather != current_weather:
		current_weather = new_weather
		EventBus.weather_changed.emit(current_weather)


# ── public queries ───────────────────────────────────────────────────────────

func is_raining() -> bool:
	return current_weather == WEATHER_RAIN or current_weather == WEATHER_STORM


func is_storming() -> bool:
	return current_weather == WEATHER_STORM


func is_cold_snap() -> bool:
	return current_weather == WEATHER_COLD_SNAP


# ── save / load ──────────────────────────────────────────────────────────────

func save_dict() -> Dictionary:
	return {
		"weather": String(current_weather),
		"last_day_index": _last_day_index,
	}


func apply_dict(d: Dictionary) -> void:
	current_weather = StringName(d.get("weather", String(WEATHER_CLEAR)))
	_last_day_index = int(d.get("last_day_index", -1))
