extends Camera2D
## World-view camera rig — pinch-zoom, drag-pan, double-tap-centre.
## All input handled via _unhandled_input so CanvasLayer UI overlays swallow first.
## Call set_world_bounds(rect) once from the world scene after map generation.

const MIN_ZOOM: float = 0.5   # strategic / whole-map-ish
const MAX_ZOOM: float = 4.0   # close / sprite-readable
const BOUNDS_BLEED_PX: int = 32  # 2 tiles of bleed at map edge

const DOUBLE_TAP_MS: int = 300       # window for double-tap detection
const DOUBLE_TAP_DIST_PX: float = 16.0  # 1 tile radius

var target_zoom: float = 1.0

var _dragging: bool = false  # desktop left-mouse drag tracking

var _last_tap_time_ms: int = 0
var _last_tap_world_pos: Vector2 = Vector2.ZERO

var _centre_tween: Tween = null


func _ready() -> void:
	position_smoothing_enabled = false
	# Phase 5 visual polish: default 2.5× — at 1× the world feels sparse on phone
	# and pawns become 6-pixel dots; 2.5× shows ~30 tiles wide which is the
	# "comfortable inspection" zoom level.
	target_zoom = 2.5
	zoom = Vector2(target_zoom, target_zoom)


## Pan speed in world pixels per second at zoom 1.0.
## Scales inversely with zoom so on-screen apparent speed feels constant.
const PAN_SPEED_PX_PER_SEC: float = 600.0


func _process(delta: float) -> void:
	# Lerp zoom toward target each render frame.
	# Plain 0.15 factor is correct at 60 fps; frame-rate independent form used for safety.
	var t: float = 1.0 - pow(1.0 - 0.15, delta * 60.0)
	zoom = zoom.lerp(Vector2(target_zoom, target_zoom), t)

	# Keyboard pan — WASD / arrow keys.  Strength gives an analogue-stick-style
	# value (0..1) so held-key is continuous and action-just-pressed is not needed.
	var pan_input := Vector2(
		Input.get_action_strength("pan_right") - Input.get_action_strength("pan_left"),
		Input.get_action_strength("pan_down")  - Input.get_action_strength("pan_up"),
	)
	if pan_input != Vector2.ZERO:
		position += pan_input.normalized() * (PAN_SPEED_PX_PER_SEC / zoom.x) * delta


## Returns true when a Designation paint tool is active.
## Camera drag-pan must yield to the paint tool so drag strokes reach Designation.
func _paint_tool_active() -> bool:
	var d = World.designation_ctl
	return d != null and d.active_tool() != Designation.TOOL_NONE


func _unhandled_input(event: InputEvent) -> void:
	# --- Pinch-zoom via magnify gesture (trackpad + native touch pinch) ---
	if event is InputEventMagnifyGesture:
		target_zoom = clampf(target_zoom * event.factor, MIN_ZOOM, MAX_ZOOM)
		return

	# --- Keyboard zoom (= / + / KP_ADD and - / KP_SUBTRACT) ---
	if event.is_action_pressed("zoom_in"):
		target_zoom = clampf(target_zoom * 1.1, MIN_ZOOM, MAX_ZOOM)
		Audit.log("camera", "zoom_in → %.2f" % target_zoom)
		return
	if event.is_action_pressed("zoom_out"):
		target_zoom = clampf(target_zoom / 1.1, MIN_ZOOM, MAX_ZOOM)
		Audit.log("camera", "zoom_out → %.2f" % target_zoom)
		return

	# --- Scroll-wheel zoom (desktop) ---
	if event is InputEventMouseButton:
		if event.button_index == MOUSE_BUTTON_WHEEL_UP and event.pressed:
			target_zoom = clampf(target_zoom * 1.1, MIN_ZOOM, MAX_ZOOM)
			return
		if event.button_index == MOUSE_BUTTON_WHEEL_DOWN and event.pressed:
			target_zoom = clampf(target_zoom / 1.1, MIN_ZOOM, MAX_ZOOM)
			return

		# --- Desktop left-mouse: drag-pan tracking + double-tap detection ---
		if event.button_index == MOUSE_BUTTON_LEFT:
			if event.pressed:
				# Don't start a pan drag while a paint tool is active; the
				# button-down belongs to Designation's stroke start.
				if not _paint_tool_active():
					_dragging = true
				_check_double_tap(event.position)
			else:
				_dragging = false
			return

	# --- Touch drag-pan (skip when a paint tool is active) ---
	if event is InputEventScreenDrag:
		if not _paint_tool_active():
			_apply_pan(event.relative)
		return

	# --- Desktop mouse-motion drag-pan (only while left mouse held) ---
	# _dragging is only set when no paint tool was active at press time, so this
	# guard is sufficient for the desktop case.
	if event is InputEventMouseMotion and _dragging:
		_apply_pan(event.relative)
		return

	# --- Touch press: double-tap detection ---
	if event is InputEventScreenTouch and event.pressed:
		_check_double_tap(event.position)


func _apply_pan(relative: Vector2) -> void:
	# zoom.x > 1 means more zoomed-in; each screen pixel = fewer world units.
	position -= relative / zoom.x


func _check_double_tap(screen_pos: Vector2) -> void:
	var now_ms: int = Time.get_ticks_msec()
	var world_pos: Vector2 = get_canvas_transform().affine_inverse() * screen_pos

	var dt: int = now_ms - _last_tap_time_ms
	var dist: float = world_pos.distance_to(_last_tap_world_pos)

	if dt < DOUBLE_TAP_MS and dist < DOUBLE_TAP_DIST_PX:
		_centre_on(world_pos)
		# Reset so a triple-tap doesn't re-trigger immediately
		_last_tap_time_ms = 0
	else:
		_last_tap_time_ms = now_ms
		_last_tap_world_pos = world_pos


func _centre_on(world_pos: Vector2) -> void:
	# Kill any in-progress centre tween before starting a new one
	if _centre_tween != null and _centre_tween.is_valid():
		_centre_tween.kill()

	_centre_tween = create_tween()
	_centre_tween.set_ease(Tween.EASE_OUT)
	_centre_tween.set_trans(Tween.TRANS_QUAD)
	_centre_tween.tween_property(self, "position", world_pos, 0.2)


## Public API — smooth-pan the camera to the centre of a tile (world space).
## Used by StorytellerBanner and StorytellerModal "Go there" buttons.
## Reuses the same _centre_tween slot so a rapid second call replaces the first.
func pan_to_tile(tile: Vector2i) -> void:
	const TILE_PX: int = 16
	var world_pos := Vector2(tile.x * TILE_PX + TILE_PX / 2, tile.y * TILE_PX + TILE_PX / 2)
	_centre_on(world_pos)
	Audit.log("camera", "pan_to_tile %s → world %s" % [tile, world_pos])


## Public API — call once from the world scene after map bounds are known.
func set_world_bounds(rect: Rect2) -> void:
	limit_left   = int(rect.position.x)    - BOUNDS_BLEED_PX
	limit_top    = int(rect.position.y)    - BOUNDS_BLEED_PX
	limit_right  = int(rect.end.x)         + BOUNDS_BLEED_PX
	limit_bottom = int(rect.end.y)         + BOUNDS_BLEED_PX
	Audit.log("camera", "bounds set: %s (bleed %d)" % [rect, BOUNDS_BLEED_PX])
