class_name SurfaceDrawingPlacement
extends RefCounted

const SNAP_DISTANCE: float = 0.55
const SNAP_PLANE_DISTANCE: float = 0.18
const SNAP_NORMAL_DOT: float = 0.94


static func resolve(
	origin: Vector3,
	normal: Vector3,
	fallback_tangent: Vector3,
	canvas_states: Array[Dictionary],
	requested_grid_size: int = SurfaceDrawingProtocol.DEFAULT_GRID_SIZE,
) -> Dictionary:
	var surface_normal: Vector3 = normal.normalized()
	var surface_tangent: Vector3 = _projected_tangent(
		fallback_tangent, surface_normal
	)
	var best: Dictionary = {
		"origin": origin,
		"normal": surface_normal,
		"tangent": surface_tangent,
		"snapped": false,
	}
	var nearest_distance: float = SNAP_DISTANCE
	for state: Dictionary in canvas_states:
		if not SurfaceDrawingProtocol.validate_canvas_state(state):
			continue
		var anchor_normal: Vector3 = SurfaceDrawingProtocol.array_to_vector(
			state["normal"]
		).normalized()
		if anchor_normal.dot(surface_normal) < SNAP_NORMAL_DOT:
			continue
		var anchor_origin: Vector3 = SurfaceDrawingProtocol.array_to_vector(
			state["origin"]
		)
		var relative: Vector3 = origin - anchor_origin
		if absf(relative.dot(anchor_normal)) > SNAP_PLANE_DISTANCE:
			continue
		var anchor_tangent: Vector3 = _projected_tangent(
			SurfaceDrawingProtocol.array_to_vector(state["tangent"]),
			anchor_normal,
		)
		var anchor_bitangent: Vector3 = anchor_normal.cross(
			anchor_tangent
		).normalized()
		var width: float = float(state["width"]) * float(state["cell_size"])
		var height: float = float(state["height"]) * float(state["cell_size"])
		var requested_extent: float = (
			float(requested_grid_size) * SurfaceDrawingProtocol.CELL_SIZE
		)
		if width <= 0.0 or height <= 0.0:
			continue
		var horizontal_spacing: float = (width + requested_extent) * 0.5
		var vertical_spacing: float = (height + requested_extent) * 0.5
		var horizontal_step: int = roundi(
			relative.dot(anchor_tangent) / horizontal_spacing
		)
		var vertical_step: int = roundi(
			relative.dot(anchor_bitangent) / vertical_spacing
		)
		if horizontal_step == 0 and vertical_step == 0:
			continue
		var candidate: Vector3 = (
			anchor_origin
			+ anchor_tangent * float(horizontal_step) * horizontal_spacing
			+ anchor_bitangent * float(vertical_step) * vertical_spacing
		)
		var distance: float = candidate.distance_to(origin)
		if distance > nearest_distance:
			continue
		nearest_distance = distance
		best = {
			"origin": candidate,
			"normal": anchor_normal,
			"tangent": anchor_tangent,
			"snapped": true,
		}
	return best


static func _projected_tangent(
	value: Vector3,
	normal: Vector3,
) -> Vector3:
	var tangent: Vector3 = (value - normal * value.dot(normal)).normalized()
	if tangent.is_zero_approx():
		tangent = Vector3.UP.cross(normal).normalized()
	if tangent.is_zero_approx():
		tangent = Vector3.RIGHT
	return tangent
