extends Node

@export var cycle_duration_seconds := 240.0
@export var directional_light: DirectionalLight3D
@export var world_environment: WorldEnvironment

const TO_RAD := PI / 180.0

const DAY_SKY_TOP := Color(0.23, 0.43, 0.62, 1)
const DAY_SKY_HORIZON := Color(0.82, 0.68, 0.5, 1)
const DAY_GROUND_BOTTOM := Color(0.12, 0.17, 0.12, 1)
const DAY_GROUND_HORIZON := Color(0.62, 0.55, 0.42, 1)
const DAY_AMBIENT := Color(0.91, 0.85, 0.72, 1)
const DAY_AMBIENT_ENERGY := 0.7
const DAY_FOG_LIGHT := Color(0.82, 0.78, 0.66, 1)
const DAY_FOG_LIGHT_ENERGY := 0.72
const DAY_FOG_DENSITY := 0.002
const DAY_LIGHT_COLOR := Color(1, 0.87, 0.72, 1)
const DAY_LIGHT_ENERGY := 1.45

const NIGHT_SKY_TOP := Color(0.04, 0.04, 0.12, 1)
const NIGHT_SKY_HORIZON := Color(0.08, 0.06, 0.14, 1)
const NIGHT_GROUND_BOTTOM := Color(0.02, 0.02, 0.04, 1)
const NIGHT_GROUND_HORIZON := Color(0.04, 0.03, 0.06, 1)
const NIGHT_AMBIENT := Color(0.12, 0.14, 0.28, 1)
const NIGHT_AMBIENT_ENERGY := 0.25
const NIGHT_FOG_LIGHT := Color(0.1, 0.12, 0.24, 1)
const NIGHT_FOG_LIGHT_ENERGY := 0.3
const NIGHT_FOG_DENSITY := 0.005
const NIGHT_LIGHT_COLOR := Color(0.22, 0.28, 0.5, 1)
const NIGHT_LIGHT_ENERGY := 0.3

const SUNSET_LIGHT_COLOR := Color(1, 0.55, 0.3, 1)
const SUNRISE_LIGHT_COLOR := Color(1, 0.6, 0.45, 1)
const SUNRISE_SUNSET_ENERGY := 0.8

var elapsed := 0.0


func _process(delta: float) -> void:
	elapsed += delta
	if elapsed >= cycle_duration_seconds:
		elapsed = fmod(elapsed, cycle_duration_seconds)
	var cycle := elapsed / cycle_duration_seconds
	_apply_light_rotation(cycle)
	_apply_environment(cycle)


func _apply_light_rotation(cycle: float) -> void:
	if directional_light == null:
		return
	var angle_rad: float = cycle * 2.0 * PI
	directional_light.rotation = Vector3(-PI / 4.0 + sin(angle_rad) * PI / 4.0, angle_rad, 0)


func _apply_environment(cycle: float) -> void:
	if directional_light == null:
		return

	var day_factor := _day_factor(cycle)
	var night_factor := 1.0 - day_factor

	directional_light.light_color = _lerp_color(NIGHT_LIGHT_COLOR, DAY_LIGHT_COLOR, day_factor)
	directional_light.light_energy = lerpf(NIGHT_LIGHT_ENERGY, DAY_LIGHT_ENERGY, day_factor)

	var sunset_peak := _sunrise_sunset_weight(cycle)
	if sunset_peak > 0.0:
		var sunset_color: Color
		if cycle < 0.5:
			sunset_color = SUNSET_LIGHT_COLOR
		else:
			sunset_color = SUNRISE_LIGHT_COLOR
		directional_light.light_color = directional_light.light_color.lerp(
			sunset_color, sunset_peak * 0.85
		)
		directional_light.light_energy = maxf(
			directional_light.light_energy, SUNRISE_SUNSET_ENERGY * sunset_peak
		)

	if world_environment == null or world_environment.environment == null:
		return

	var env: Environment = world_environment.environment

	env.ambient_light_color = _lerp_color(NIGHT_AMBIENT, DAY_AMBIENT, day_factor)
	env.ambient_light_energy = lerpf(NIGHT_AMBIENT_ENERGY, DAY_AMBIENT_ENERGY, day_factor)

	env.fog_light_color = _lerp_color(NIGHT_FOG_LIGHT, DAY_FOG_LIGHT, day_factor)
	env.fog_light_energy = lerpf(NIGHT_FOG_LIGHT_ENERGY, DAY_FOG_LIGHT_ENERGY, day_factor)
	env.fog_density = lerpf(NIGHT_FOG_DENSITY, DAY_FOG_DENSITY, day_factor)

	var sky: Sky = env.sky
	if sky != null and sky.sky_material is ProceduralSkyMaterial:
		var mat: ProceduralSkyMaterial = sky.sky_material
		mat.sky_top_color = _lerp_color(NIGHT_SKY_TOP, DAY_SKY_TOP, day_factor)
		mat.sky_horizon_color = _lerp_color(NIGHT_SKY_HORIZON, DAY_SKY_HORIZON, day_factor)
		mat.ground_bottom_color = _lerp_color(NIGHT_GROUND_BOTTOM, DAY_GROUND_BOTTOM, day_factor)
		mat.ground_horizon_color = _lerp_color(NIGHT_GROUND_HORIZON, DAY_GROUND_HORIZON, day_factor)


func _day_factor(cycle: float) -> float:
	if cycle < 0.15:
		return 0.0
	if cycle < 0.3:
		return smoothstep(0.15, 0.3, cycle)
	if cycle < 0.7:
		return 1.0
	if cycle < 0.85:
		return 1.0 - smoothstep(0.7, 0.85, cycle)
	return 0.0


func _sunrise_sunset_weight(cycle: float) -> float:
	if cycle < 0.15:
		return smoothstep(0.05, 0.15, cycle) * (1.0 - smoothstep(0.1, 0.15, cycle))
	if cycle > 0.85:
		return smoothstep(0.85, 0.95, cycle) * (1.0 - smoothstep(0.9, 0.95, cycle))
	return 0.0


func _lerp_color(from: Color, to: Color, weight: float) -> Color:
	return from.lerp(to, clampf(weight, 0.0, 1.0))
