using Godot;

namespace Theriapolis.GodotHost.Rendering;

/// <summary>
/// Camera2D with mouse-wheel zoom and middle/right-button click-drag pan.
/// Zoom is centered on the cursor so zooming feels natural. Bounds:
/// the zoom factor is clamped between MinZoom (everything fits) and
/// MaxZoom (1 tile fills the screen).
/// </summary>
public partial class PanZoomCamera : Camera2D
{
    [Export] public float MinZoom { get; set; } = 0.04f;
    [Export] public float MaxZoom { get; set; } = 4.0f;
    [Export] public float ZoomStep { get; set; } = 1.15f;

    private bool _dragging;
    private Vector2 _dragStartCursor;
    private Vector2 _dragStartCameraPos;

    public override void _UnhandledInput(InputEvent @event)
    {
        switch (@event)
        {
            case InputEventMouseButton mb when mb.Pressed:
                HandleMouseButtonPressed(mb);
                break;
            case InputEventMouseButton mb when !mb.Pressed:
                if (mb.ButtonIndex is MouseButton.Middle or MouseButton.Right)
                    _dragging = false;
                break;
            case InputEventMouseMotion mm when _dragging:
                Position = _dragStartCameraPos
                    + (_dragStartCursor - mm.Position) / Zoom;
                break;
        }
    }

    private void HandleMouseButtonPressed(InputEventMouseButton mb)
    {
        switch (mb.ButtonIndex)
        {
            case MouseButton.WheelUp:
                ApplyZoom(ZoomStep, mb.Position);
                break;
            case MouseButton.WheelDown:
                ApplyZoom(1f / ZoomStep, mb.Position);
                break;
            case MouseButton.Middle:
            case MouseButton.Right:
                _dragging = true;
                _dragStartCursor = mb.Position;
                _dragStartCameraPos = Position;
                break;
        }
    }

    private void ApplyZoom(float factor, Vector2 cursorScreen)
    {
        float newZoom = Mathf.Clamp(Zoom.X * factor, MinZoom, MaxZoom);
        if (Mathf.IsEqualApprox(newZoom, Zoom.X)) return;

        // Zoom toward the cursor: keep the world point under the cursor fixed.
        Vector2 worldBefore = GetCanvasTransform().AffineInverse() * cursorScreen;
        Zoom = new Vector2(newZoom, newZoom);
        Vector2 worldAfter = GetCanvasTransform().AffineInverse() * cursorScreen;
        Position += worldBefore - worldAfter;
    }
}
