extends Camera3D

@export var height: float = 25.0
@export var horizontal_distance: float = 25.0
@export var horizontal_angle_deg: float = 45.0

# Perspective zoom (FOV)
@export var fov_min: float = 20.0
@export var fov_max: float = 75.0
@export var default_fov: float = 30.0

@export var focus_slice_thickness: float = 6.0
@export var blur_transition: float = 15.0

var _shake_amount: float = 0.0
var _shake_decay: float = 0.0
var _user_fov: float = 0.0

# Trailing-camera state: _focus is the smoothed point the camera frames (it lags
# the bull), _drag_offset is the velocity-driven trail that slides it behind.
var _focus: Vector3 = Vector3.ZERO
var _drag_offset: Vector3 = Vector3.ZERO
var _focus_init: bool = false


func _ready() -> void:
	set_as_top_level(true)

	projection = Camera3D.PROJECTION_PERSPECTIVE
	fov = default_fov
	_user_fov = default_fov

	near = 0.1
	far = 150.0


func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("zoom_in"):
		_user_fov = clamp(_user_fov / 1.1, fov_min, fov_max)

	if event.is_action_pressed("zoom_out"):
		_user_fov = clamp(_user_fov * 1.1, fov_min, fov_max)


func _process(delta: float) -> void:
	fov = lerpf(fov, _user_fov, delta * 8.0)

	var angle_rad := deg_to_rad(horizontal_angle_deg)

	var offset := Vector3(
		sin(angle_rad) * horizontal_distance,
		height,
		cos(angle_rad) * horizontal_distance
	)

	var target := get_parent() as Node3D
	var focus := _drag_focus(target, delta)

	global_position = focus + offset
	look_at(focus, Vector3.UP)

	_update_dof(focus)

	if _shake_amount > 0.005:
		global_position += Vector3(
			randf_range(-_shake_amount, _shake_amount),
			randf_range(-_shake_amount * 0.25, _shake_amount * 0.25),
			randf_range(-_shake_amount, _shake_amount)
		)

		_shake_amount = move_toward(_shake_amount, 0.0, _shake_decay * delta)


# Smoothed follow point that drags behind the bull. The target trail sits opposite
# the bull's flat velocity (cam_drag metres per m/s); the offset and the focus both
# ease toward their targets so the camera lags and settles rather than snapping.
func _drag_focus(target: Node3D, delta: float) -> Vector3:
	var pos := target.global_position
	if not _focus_init:
		_focus = pos
		_focus_init = true

	var vel := Vector3.ZERO
	if target is CharacterBody3D:
		vel = (target as CharacterBody3D).velocity
		vel.y = 0.0

	var drag_target := -vel * DP.f("cam_drag")
	_drag_offset = _drag_offset.lerp(drag_target, clampf(DP.f("cam_drag_speed") * delta, 0.0, 1.0))
	_focus = _focus.lerp(pos + _drag_offset, clampf(DP.f("cam_follow_lerp") * delta, 0.0, 1.0))
	return _focus


func trigger_hit(strength: float) -> void:
	if not Settings.screen_shake:
		return
	_shake_amount = strength * 0.5
	_shake_decay = _shake_amount / 0.4


func _update_dof(target_pos: Vector3) -> void:
	var cam_attr: CameraAttributesPractical = null

	if attributes is CameraAttributesPractical:
		cam_attr = attributes

	if cam_attr == null:
		return

	var distance := global_position.distance_to(target_pos)

	cam_attr.dof_blur_near_distance = max(distance - focus_slice_thickness * 0.5, near + 0.1)
	cam_attr.dof_blur_far_distance = distance + focus_slice_thickness * 0.5

	cam_attr.dof_blur_near_transition = blur_transition
	cam_attr.dof_blur_far_transition = blur_transition

	cam_attr.dof_blur_near_enabled = false
	cam_attr.dof_blur_far_enabled = false
