class_name M2PlacementTransformResolver
extends RefCounted

## Stateless ADT M2 placement orientation and origin-correction rules.

const CLIFFROCK_WORLD_YAW_OFFSET := PI
const WATERFALL_WORLD_YAW_OFFSET := PI * 0.5


## Resolves the unscaled Godot-space basis for one ADT M2 placement.
func resolve_basis(rotation_radians: Vector3, relative_m2_path: String = "") -> Basis:
	if not _is_waterfall_path(relative_m2_path):
		return _resolve_regular_basis(rotation_radians, relative_m2_path)

	var placement_basis := Basis.from_euler(rotation_radians)
	return (
		Basis(Vector3.UP, WATERFALL_WORLD_YAW_OFFSET)
		* placement_basis
		* Basis(
			_resolve_waterfall_local_fall_axis(relative_m2_path),
			_resolve_waterfall_sheet_twist(relative_m2_path)
		)
	)


## Resolves local origin compensation for a twisted waterfall sheet. Regular
## models and waterfall models without a measured anchor return zero.
func resolve_origin_offset(
		rotation_radians: Vector3,
		relative_m2_path: String,
		scale_value: float
) -> Vector3:
	if not _is_waterfall_path(relative_m2_path):
		return Vector3.ZERO
	var twist_radians := _resolve_waterfall_sheet_twist(relative_m2_path)
	if is_zero_approx(twist_radians):
		return Vector3.ZERO
	var anchor_local_position := _resolve_waterfall_sheet_twist_anchor(relative_m2_path)
	if anchor_local_position == Vector3.ZERO:
		return Vector3.ZERO
	var placement_basis := (
		Basis(Vector3.UP, WATERFALL_WORLD_YAW_OFFSET)
		* Basis.from_euler(rotation_radians)
	)
	var twisted_basis := placement_basis * Basis(
		_resolve_waterfall_local_fall_axis(relative_m2_path),
		twist_radians
	)
	return (
		placement_basis * anchor_local_position
		- twisted_basis * anchor_local_position
	) * maxf(scale_value, 0.0001)


func _resolve_regular_basis(rotation_radians: Vector3, relative_m2_path: String) -> Basis:
	if not _is_elwynn_cliffrock_path(relative_m2_path):
		return Basis.from_euler(rotation_radians)
	return (
		Basis(Vector3.UP, rotation_radians.y + CLIFFROCK_WORLD_YAW_OFFSET)
		* Basis(Vector3.BACK, rotation_radians.x)
		* Basis(Vector3.RIGHT, -rotation_radians.z)
	)


func _is_waterfall_path(relative_m2_path: String) -> bool:
	var model_name := _normalized_model_name(relative_m2_path)
	return model_name == "newwaterfall" or model_name == "elwynntallwaterfall01"


func _is_elwynn_cliffrock_path(relative_m2_path: String) -> bool:
	var model_name := _normalized_model_name(relative_m2_path)
	return model_name == "elwynncliffrock01" or model_name == "elwynncliffrock02"


func _normalized_model_name(relative_m2_path: String) -> String:
	return relative_m2_path.replace("\\", "/").to_lower().get_file().get_basename()


func _resolve_waterfall_local_fall_axis(relative_m2_path: String) -> Vector3:
	var normalized_path := relative_m2_path.replace("\\", "/").to_lower()
	if normalized_path.ends_with("newwaterfall.m2") or normalized_path.ends_with("newwaterfall.mdx"):
		return Vector3(-0.138742, 0.990329, 0.0).normalized()
	if (
		normalized_path.ends_with("elwynntallwaterfall01.m2")
		or normalized_path.ends_with("elwynntallwaterfall01.mdx")
	):
		return Vector3(-0.068962, 0.997619, 0.0).normalized()
	return Vector3.UP


func _resolve_waterfall_sheet_twist(relative_m2_path: String) -> float:
	var normalized_path := relative_m2_path.replace("\\", "/").to_lower()
	if normalized_path.ends_with("newwaterfall.m2") or normalized_path.ends_with("newwaterfall.mdx"):
		return PI * 0.5
	if (
		normalized_path.ends_with("elwynntallwaterfall01.m2")
		or normalized_path.ends_with("elwynntallwaterfall01.mdx")
	):
		return -PI * 0.5
	return 0.0


func _resolve_waterfall_sheet_twist_anchor(relative_m2_path: String) -> Vector3:
	var normalized_path := relative_m2_path.replace("\\", "/").to_lower()
	if (
		normalized_path.ends_with("elwynntallwaterfall01.m2")
		or normalized_path.ends_with("elwynntallwaterfall01.mdx")
	):
		return Vector3(-2.667799, 89.62273, 0.00129)
	return Vector3.ZERO
