@tool
class_name TerrainGenerator
extends MeshInstance3D

@export var noise_setting: FastNoiseLite:
	set(value):
		if noise_setting and noise_setting.changed.is_connected(_on_settings_changed):
			noise_setting.changed.disconnect(_on_settings_changed)
		noise_setting = value
		if noise_setting:
			noise_setting.changed.connect(_on_settings_changed)
		_request_update()

@export var material_setting: ShaderMaterial:
	set(value):
		material_setting = value
		if mesh:
			mesh.surface_set_material(0, material_setting)

@export_group("Terrain Size")
@export var terrain_size: Vector2 = Vector2(200, 200):
	set(value):
		terrain_size = value
		_request_update()

@export_range(2, 512) var subdivision: int = 128:
	set(value):
		subdivision = value
		_request_update()

@export var max_height: float = 50.0:
	set(value):
		max_height = value
		_request_update()

@export_group("Island Mask")
@export var island_mask: bool = true:
	set(value):
		island_mask = value
		_request_update()

@export_range(0.0, 1.0) var mask_falloff_start: float = 0.5:
	set(value):
		mask_falloff_start = value
		_request_update()

@export_range(0.1, 2.0) var mask_power: float = 1.5:
	set(value):
		mask_power = value
		_request_update()

@export_group("Actions")
@export var regenerate: bool = false:
	set(_value):
		generate_terrain()

@export var bake_collision: bool = false:
	set(_value):
		_bake_collision()

var _update_queued: bool = false


func _ready() -> void:
	if noise_setting:
		if not noise_setting.changed.is_connected(_on_settings_changed):
			noise_setting.changed.connect(_on_settings_changed)
	generate_terrain()


func _on_settings_changed() -> void:
	_request_update()


func _request_update() -> void:
	if _update_queued:
		return
	_update_queued = true
	call_deferred("_do_update")


func _do_update() -> void:
	_update_queued = false
	generate_terrain()


func generate_terrain() -> void:
	if not noise_setting:
		return
	
	var st := SurfaceTool.new()
	st.begin(Mesh.PRIMITIVE_TRIANGLES)
	
	var step_x: float = terrain_size.x / float(subdivision - 1)
	var step_z: float = terrain_size.y / float(subdivision - 1)
	var half_x: float = terrain_size.x / 2.0
	var half_z: float = terrain_size.y / 2.0
	
	var heights: Array[float] = []
	heights.resize(subdivision * subdivision)
	
	for z in range(subdivision):
		for x in range(subdivision):
			var world_x: float = x * step_x - half_x
			var world_z: float = z * step_z - half_z
			
			var height: float = noise_setting.get_noise_2d(world_x, world_z) * max_height
			
			if island_mask:
				height *= _calculate_island_mask(world_x, world_z, half_x, half_z)
			
			heights[z * subdivision + x] = height
	
	for z in range(subdivision - 1):
		for x in range(subdivision - 1):
			var idx00: int = z * subdivision + x
			var idx10: int = z * subdivision + (x + 1)
			var idx01: int = (z + 1) * subdivision + x
			var idx11: int = (z + 1) * subdivision + (x + 1)
			
			var x0: float = x * step_x - half_x
			var x1: float = (x + 1) * step_x - half_x
			var z0: float = z * step_z - half_z
			var z1: float = (z + 1) * step_z - half_z
			
			var v00 := Vector3(x0, heights[idx00], z0)
			var v10 := Vector3(x1, heights[idx10], z0)
			var v01 := Vector3(x0, heights[idx01], z1)
			var v11 := Vector3(x1, heights[idx11], z1)
			
			# Triangle 1: v00, v01, v10
			var normal1: Vector3 = (v01 - v00).cross(v10 - v00).normalized()
			st.set_normal(normal1)
			st.set_uv(Vector2(x0 / terrain_size.x + 0.5, z0 / terrain_size.y + 0.5))
			st.add_vertex(v00)
			st.set_uv(Vector2(x0 / terrain_size.x + 0.5, z1 / terrain_size.y + 0.5))
			st.add_vertex(v01)
			st.set_uv(Vector2(x1 / terrain_size.x + 0.5, z0 / terrain_size.y + 0.5))
			st.add_vertex(v10)
			
			# Triangle 2: v10, v01, v11
			var normal2: Vector3 = (v01 - v10).cross(v11 - v10).normalized()
			st.set_normal(normal2)
			st.set_uv(Vector2(x1 / terrain_size.x + 0.5, z0 / terrain_size.y + 0.5))
			st.add_vertex(v10)
			st.set_uv(Vector2(x0 / terrain_size.x + 0.5, z1 / terrain_size.y + 0.5))
			st.add_vertex(v01)
			st.set_uv(Vector2(x1 / terrain_size.x + 0.5, z1 / terrain_size.y + 0.5))
			st.add_vertex(v11)
	
	mesh = st.commit()
	
	if material_setting:
		mesh.surface_set_material(0, material_setting)


func _calculate_island_mask(x: float, z: float, half_x: float, half_z: float) -> float:
	var nx: float = x / half_x
	var nz: float = z / half_z
	var dist: float = sqrt(nx * nx + nz * nz)
	
	if dist < mask_falloff_start:
		return 1.0
	elif dist > 1.0:
		return 0.0
	else:
		var t: float = (dist - mask_falloff_start) / (1.0 - mask_falloff_start)
		return pow(1.0 - t, mask_power)


func _bake_collision() -> void:
	if not mesh:
		push_warning("TerrainGenerator: No mesh to bake collision from")
		return
	
	var static_body: StaticBody3D
	var collision_shape: CollisionShape3D
	
	for child in get_children():
		if child is StaticBody3D:
			static_body = child
			break
	
	if not static_body:
		static_body = StaticBody3D.new()
		static_body.name = "TerrainCollision"
		add_child(static_body)
		if Engine.is_editor_hint():
			static_body.owner = get_tree().edited_scene_root
	
	for child in static_body.get_children():
		if child is CollisionShape3D:
			collision_shape = child
			break
	
	if not collision_shape:
		collision_shape = CollisionShape3D.new()
		collision_shape.name = "CollisionShape"
		static_body.add_child(collision_shape)
		if Engine.is_editor_hint():
			collision_shape.owner = get_tree().edited_scene_root
	
	collision_shape.shape = mesh.create_trimesh_shape()
	print("TerrainGenerator: Collision baked successfully")
