using System;
using Godot;
using RustyOptions;

namespace Movementtests.systems;

public partial class HeadSystem : Node3D
{
    private Camera3D _camera;
    private Marker3D _cameraAnchor;
    private AnimationPlayer _animationPlayer;
	
    [Export(PropertyHint.Range, "0,10,0.1,or_greater")]
    public float LookSensitivity { get; set; } = 1f;
	
    [Export(PropertyHint.Range, "0.1,50,0.1,or_greater")]
    public double CameraInclineAcceleration { get; set; } = 10f;
	
    [Export(PropertyHint.Range, "0,10,0.1,or_greater")]
    public float WallRunCameraIncline { get; set; } = 5f;
	
    [Export(PropertyHint.Range, "0,10,0.1,or_greater")]
    public float GroundedCameraIncline { get; set; } = 5f;
    
    public void Init()
    {
        Input.SetMouseMode(Input.MouseModeEnum.Captured);
        _camera = GetNode<Camera3D>("CameraSmooth/Camera3D");
        _cameraAnchor = GetNode<Marker3D>("CameraAnchor");
        _animationPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
    }

    public void OnMantle()
    {
        _animationPlayer.Play("mantle");
    }

    public void LookAround(double delta, Vector2 lookDir, Vector3 playerVelocity, Vector3? wallContactPoint = null, float sensitivitMultiplier = 1f)
    {
        // Horizontal movement of head
        float angleForHorizontalRotation = lookDir.X * LookSensitivity * sensitivitMultiplier;
        RotateY(angleForHorizontalRotation);

        // Vertical movement of head
        Vector3 currentCameraRotation = _cameraAnchor.Rotation;
        currentCameraRotation.X += Convert.ToSingle(lookDir.Y * LookSensitivity * sensitivitMultiplier);
        currentCameraRotation.X = Mathf.Clamp(currentCameraRotation.X, Mathf.DegToRad(-90f), Mathf.DegToRad(90f));
        
        var isWallRunning = wallContactPoint.HasValue && wallContactPoint.Value.Length() > Mathf.Epsilon;
        var cameraIncline = 0f;
        if (isWallRunning)
        {
            var directionToWall = (wallContactPoint.Value - GlobalPosition).Normalized();
            var cameraInclineFactor = ComputeCameraInclineFactor(directionToWall);
            cameraIncline = Mathf.DegToRad(WallRunCameraIncline * cameraInclineFactor);
        }
        else
        {
            var cameraInclineFactor = ComputeCameraInclineFactor(playerVelocity);
            cameraIncline = Mathf.DegToRad(GroundedCameraIncline * cameraInclineFactor * -1.0f);
        }
        currentCameraRotation.Z = (float) Mathf.Lerp(currentCameraRotation.Z, cameraIncline, delta * CameraInclineAcceleration);
        _cameraAnchor.Rotation = currentCameraRotation;
        
        _camera.GlobalTransform = _cameraAnchor.GetGlobalTransformInterpolated();
    }

    public float ComputeCameraInclineFactor(Vector3 direction)
    {
        var forward = GetForwardHorizontalVector().Normalized();
        var crossProduct = forward.Cross(direction);
        return crossProduct.Length()*Mathf.Sign(crossProduct.Y);
    }

    public Vector3 GetForwardHorizontalVector()
    {
        return GetGlobalTransform().Basis.Z;
    }

    public void SetHeight(float height)
    {
        Position = new Vector3(Position.X, height, Position.Z);
    }
}