@tool
extends Node3D


const ScatterUtil := preload('./common/scatter_util.gd')


@export_category("ScatterItem")
@export var proportion := 100:
	set(val):
		proportion = val
		ScatterUtil.request_parent_to_rebuild(self)

@export_enum("From current scene:0", "From disk:1") var source = 1:
	set(val):
		source = val
		property_list_changed.emit()

@export_group("Source options", "source_")
@export var source_scale_multiplier := 1.0:
	set(val):
		source_scale_multiplier = val
		ScatterUtil.request_parent_to_rebuild(self)

@export var source_ignore_position := true:
	set(val):
		source_ignore_position = val
		ScatterUtil.request_parent_to_rebuild(self)

@export var source_ignore_rotation := true:
	set(val):
		source_ignore_rotation = val
		ScatterUtil.request_parent_to_rebuild(self)

@export var source_ignore_scale := true:
	set(val):
		source_ignore_scale = val
		ScatterUtil.request_parent_to_rebuild(self)

@export_group("Override options", "override_")
@export var override_material: Material:
	set(val):
		override_material = val
		ScatterUtil.request_parent_to_rebuild(self)

@export var override_process_material: Material:
	set(val):
		override_process_material = val
		ScatterUtil.request_parent_to_rebuild(self) # TODO - No need for a full rebuild here

@export var override_cast_shadow: GeometryInstance3D.ShadowCastingSetting = GeometryInstance3D.SHADOW_CASTING_SETTING_ON:
	set(val):
		override_cast_shadow = val
		ScatterUtil.request_parent_to_rebuild(self) # TODO - Only change the multimesh flag instead

@export_group("Visibility", "visibility")
@export_flags_3d_render var visibility_layers: int = 1
@export var visibility_range_begin : float = 0
@export var visibility_range_begin_margin : float = 0
@export var visibility_range_end : float = 0
@export var visibility_range_end_margin : float = 0
#TODO what is a nicer way to expose this?
@export_enum("Disabled:0", "Self:1") var visibility_range_fade_mode = 0

@export_group("Level Of Detail", "lod_")
@export var lod_generate := true:
	set(val):
		lod_generate = val
		ScatterUtil.request_parent_to_rebuild(self)
@export_range(0.0, 180.0) var lod_merge_angle := 25.0:
	set(val):
		lod_merge_angle = val
		ScatterUtil.request_parent_to_rebuild(self)
@export_range(0.0, 180.0) var lod_split_angle := 60.0:
	set(val):
		lod_split_angle = val
		ScatterUtil.request_parent_to_rebuild(self)

var path: String:
	set(val):
		path = val
		source_data_ready = false
		_target_scene = load(path) if source != 0 else null
		ScatterUtil.request_parent_to_rebuild(self)

var source_position: Vector3
var source_rotation: Vector3
var source_scale: Vector3
var source_data_ready := false

var _target_scene: PackedScene


func _get_property_list() -> Array:
	var list := []

	if source == 0:
		list.push_back({
			name = "path",
			type = TYPE_NODE_PATH,
		})
	else:
		list.push_back({
			name = "path",
			type = TYPE_STRING,
			hint = PROPERTY_HINT_FILE,
		})

	return list


func get_item() -> Node3D:
	if path.is_empty():
		return null

	var node: Node3D

	if source == 0 and has_node(path):
		node = get_node(path).duplicate() # Never expose the original node
	elif source == 1:
		node = _target_scene.instantiate()

	if node:
		_save_source_data(node)
		return node

	return null


# Takes a transform in input, scale it based on the local scale multiplier
# If the source transform is not ignored, also copy the source position, rotation and scale.
# Returns the processed transform
func process_transform(t: Transform3D) -> Transform3D:
	if not source_data_ready:
		_update_source_data()

	var origin = t.origin
	t.origin = Vector3.ZERO

	t = t.scaled(Vector3.ONE * source_scale_multiplier)

	if not source_ignore_scale:
		t = t.scaled(source_scale)

	if not source_ignore_rotation:
		t = t.rotated(t.basis.x.normalized(), source_rotation.x)
		t = t.rotated(t.basis.y.normalized(), source_rotation.y)
		t = t.rotated(t.basis.z.normalized(), source_rotation.z)

	t.origin = origin

	if not source_ignore_position:
		t.origin += source_position

	return t


func _save_source_data(node: Node3D) -> void:
	if not node:
		return

	source_position = node.position
	source_rotation = node.rotation
	source_scale = node.scale
	source_data_ready = true


func _update_source_data() -> void:
	var node = get_item()
	if node:
		node.queue_free()
