class_name OverworldCamera extends Camera3D

const COLLISION_MARGIN:float = 0.3

@export_category("Target")
@export var targetNode:Node3D = null
@export var pivotOffset:Vector3 = Vector3(0, 1.2, 0)

@export_category("Orbit")
@export var distance:float = 10.0
@export var minDistance:float = 1.5
@export var orbitSensitivity:float = 120.0
@export var pitchMin:float = -10.0
@export var pitchMax:float = 70.0

@export_category("Collision")
@export_flags_3d_physics var collisionMask:int = 1

var _yaw:float = 0.0
var _pitch:float = 30.0

func _process(delta:float) -> void:
  if targetNode == null:
    return

  var orbitInput:Vector2 = Input.get_vector(
    "camera_orbit_left", "camera_orbit_right",
    "camera_orbit_up", "camera_orbit_down"
  )
  _yaw += orbitInput.x * orbitSensitivity * delta
  # Invert Y so stick-up = camera rises (bird's-eye)
  _pitch -= orbitInput.y * orbitSensitivity * delta
  _pitch = clamp(_pitch, pitchMin, pitchMax)

  var pivot:Vector3 = targetNode.global_transform.origin + pivotOffset
  var yawRad:float = deg_to_rad(_yaw)
  var pitchRad:float = deg_to_rad(_pitch)
  var dir:Vector3 = Vector3(
    sin(yawRad) * cos(pitchRad),
    sin(pitchRad),
    cos(yawRad) * cos(pitchRad)
  )

  var desired:Vector3 = pivot + dir * distance
  var actual:Vector3 = _resolveCollision(pivot, desired)

  global_transform.origin = actual
  look_at(pivot, Vector3.UP)

# Cast a ray from the pivot to the desired camera position.
# If terrain blocks the shot, pull the camera in to just in front of the hit.
func _resolveCollision(pivot:Vector3, desired:Vector3) -> Vector3:
  var space:PhysicsDirectSpaceState3D = get_world_3d().direct_space_state
  var query:PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.create(pivot, desired)
  query.collision_mask = collisionMask

  var hit:Dictionary = space.intersect_ray(query)
  if hit.is_empty():
    return desired

  var hitDir:Vector3 = (desired - pivot).normalized()
  var pulled:Vector3 = hit["position"] - hitDir * COLLISION_MARGIN
  var pulledDist:float = (pulled - pivot).length()
  if pulledDist < minDistance:
    return pivot + hitDir * minDistance
  return pulled
