using Godot;

namespace Cirno.Scripts.Misc;

public partial class CameraController3D : Camera3D
{
    [Export] public bool EnableSmoothing = true;
    [Export] public bool FollowTargeting = true;

    [Export] public float SmoothTime = 0.2f;
    [Export] public float MaxAimOffsetDistance = 2.0f;
    [Export] public float AimLerpSpeed = 8.0f;
    [Export] public float AimDeadzone = 0.2f;

    [Export] public Vector3 CameraOffset = new Vector3(0, 12, -12); // Relative to target

    [Export] public StringName AimUpName = "aim_up";
    [Export] public StringName AimDownName = "aim_down";
    [Export] public StringName AimLeftName = "aim_left";
    [Export] public StringName AimRightName = "aim_right";

    [Export] public NodePath TargetPath;

    private Node3D _target;
    private Vector3 _currentPosition = Vector3.Zero;
    private Vector3 _currentAimOffset = Vector3.Zero;

    public override void _Ready()
    {
        _target = GetNode<Node3D>(TargetPath);
        if (_target == null)
        {
            GD.PushError("Camera target not found.");
            return;
        }

        _currentPosition = GlobalPosition;

        // Set fixed isometric angle once: -45° X tilt, 45° Y pan
        RotationDegrees = new Vector3(-45f, 45f, 0f);
        Projection = ProjectionType.Orthogonal;
    }

    public override void _Process(double delta)
    {
        if (_target == null) return;

        float dt = (float)delta;
        Vector3 targetPos = _target.GlobalTransform.Origin;

        // Aim offset
        if (FollowTargeting)
        {
            Vector3 desiredOffset = GetAimOffsetWorldSpace();
            _currentAimOffset = _currentAimOffset.Lerp(desiredOffset, AimLerpSpeed * dt);

            if (_currentAimOffset.Length() > MaxAimOffsetDistance)
                _currentAimOffset = _currentAimOffset.Normalized() * MaxAimOffsetDistance;
        }
        else
        {
            _currentAimOffset = Vector3.Zero;
        }

        // Final target position
        Vector3 targetWithOffset = targetPos + _currentAimOffset;
        Vector3 desiredCameraPos = targetWithOffset + CameraOffset;

        if (EnableSmoothing)
        {
            float smoothingFactor = 1f - Mathf.Exp(-dt / SmoothTime);
            _currentPosition = _currentPosition.Lerp(desiredCameraPos, smoothingFactor);
        }
        else
        {
            _currentPosition = desiredCameraPos;
        }

        GlobalPosition = _currentPosition;
        // No LookAt or dynamic rotation — angle is fixed
    }

    private Vector3 GetAimOffsetWorldSpace()
    {
        Vector2 stickDir = new Vector2(
            Input.GetActionStrength(AimRightName) - Input.GetActionStrength(AimLeftName),
            Input.GetActionStrength(AimDownName) - Input.GetActionStrength(AimUpName)
        );

        float stickLen = stickDir.Length();
        if (stickLen > AimDeadzone)
        {
            float scaled = (stickLen - AimDeadzone) / (1f - AimDeadzone);
            Vector2 aimDir2D = stickDir.Normalized() * Mathf.Clamp(scaled, 0f, 1f);
            return new Vector3(aimDir2D.X, 0, aimDir2D.Y);
        }

        // Mouse fallback
        Vector2 mousePos = GetViewport().GetMousePosition();
        Vector3 rayOrigin = ProjectRayOrigin(mousePos);
        Vector3 rayDir = ProjectRayNormal(mousePos) * 1000f;

        var plane = new Plane(Vector3.Up, 0);
        var hit = plane.IntersectsRay(rayOrigin, rayDir);

        if (hit is Vector3 hitPoint)
        {
            Vector3 offset = hitPoint - _target.GlobalTransform.Origin;
            offset.Y = 0;

            float dist = offset.Length();
            if (dist > 0.01f)
            {
                float scaled = Mathf.Clamp((dist - AimDeadzone) / (10f - AimDeadzone), 0f, 1f);
                return offset.Normalized() * scaled;
            }
        }

        return Vector3.Zero;
    }
}