using System;
using System.Linq;
using Godot;

namespace KarrotStarterTemplate.Common.CameraController;

public enum ECameraControllerInputType
{
    None,
    TopDown,
    FirstPerson,
    ThirdPerson
    
}

[GlobalClass]
public partial class CharacterCameraController : Node3D
{
    [ExportCategory("Nodes")]
    [Export] public Node3D characterToFollow { get; set; }

    [ExportCategory("Properties")]
    [ExportGroup("Mouse input")]
    [Export] public ECameraControllerInputType inputType = ECameraControllerInputType.None;
    [Export] public float mouseSensitivity { get; set; } = .5f;
    [Export] public Vector2 cameraTiltLimit { get; set; } = new(-45f, 45f);
    
    [ExportGroup("Player")]
    [Export] public bool followPlayer = true;
    [Export(PropertyHint.Range, "0, 1")] public float followDelay { get; set; } = .5f;
    [Export] public bool rotatePlayer = true;
    [Export(PropertyHint.Range, "0, 1")] public float rotateDelay { get; set; } = .5f;
    
    private Camera3D _camera; // todo
    private SpringArm3D _springArm;
    private Vector2 _mouseInput = Vector2.Zero;

    public Camera3D camera => _camera;
    public SpringArm3D springArm => _springArm;
    // Called when the node enters the scene tree for the first time.
    public override void _Ready()
    {
        if (Engine.IsEditorHint())
        {
            TopLevel = true;
            return;
        }

        characterToFollow ??= (CharacterBody3D)GetParent();
        
        _springArm = GetChildren().OfType<SpringArm3D>().First();
        _camera = _springArm.GetChildren().OfType<Camera3D>().First();

        GlobalPosition = characterToFollow.GlobalPosition;
        // Input.SetMouseMode(Input.MouseModeEnum.Captured);
    }

    public override void _UnhandledInput(InputEvent @event)
    {
        if (Engine.IsEditorHint())
        {
            return;
        }
        
        if (inputType == ECameraControllerInputType.None)
        {
            return;
        }
        
        if (@event is InputEventMouseMotion mouseMotion)
        {
            _mouseInput = -mouseMotion.Relative * mouseSensitivity;
        }

        
        
    }

    public override void _PhysicsProcess(double delta)
    {
        if (Engine.IsEditorHint())
        {
            return;
        }
        
        if (followPlayer)
        {
            float weight = Mathf.Clamp(20 * (1 - followDelay) * (float)delta,  0,1 );
            GlobalPosition = followDelay > 0 ? GlobalPosition.Lerp(characterToFollow.GlobalPosition, weight) : characterToFollow.GlobalPosition;
        }

        if (rotatePlayer)
        {
            float weight = Mathf.Clamp(20 * (1 - rotateDelay) * (float)delta,  0,1 );
            Vector3 v = characterToFollow.GlobalRotation;

            switch (inputType)
            {

                case ECameraControllerInputType.None:
                    break;
                case ECameraControllerInputType.TopDown:
                    var (x, y, z) = GetMouseIntersectedDirection();
                    characterToFollow.GlobalRotation = new Vector3(v.X, Mathf.LerpAngle(v.Y, -Mathf.Atan2(-x, z), weight) , v.Z);
                    break;
                case ECameraControllerInputType.FirstPerson:
                case ECameraControllerInputType.ThirdPerson:
                    characterToFollow.GlobalRotation = new Vector3(v.X, Mathf.LerpAngle(v.Y, _springArm.GlobalRotation.Y, weight) , v.Z);
                    break;
            }
        }

        switch (inputType)
        {

            case ECameraControllerInputType.FirstPerson:
            case ECameraControllerInputType.ThirdPerson:
                _springArm.Rotation += new Vector3(_mouseInput.Y * (float)delta, 0f, 0f);
                Rotation += new Vector3(0f, _mouseInput.X * (float)delta, characterToFollow.GlobalPosition.Y);

                var (x, y, z) = _springArm.RotationDegrees;
                _springArm.RotationDegrees = new Vector3(Mathf.Clamp(x, cameraTiltLimit.X, cameraTiltLimit.Y), y, z);

                _mouseInput = Vector2.Zero;
                break;
        }
    }

    // Called every frame. 'delta' is the elapsed time since the previous frame.
    public override void _Process(double delta)
    {
        if (Engine.IsEditorHint())
        {
            return;
        }
    }

    public virtual Vector3 GetMouseIntersectedPosition()
    {
        var position2D = GetViewport().GetMousePosition();
        var dropPlane = new Plane(new Vector3(0, 10f, 0), 0f);
        var position3D =
            dropPlane.IntersectsRay(camera.ProjectRayOrigin(position2D), camera.ProjectRayNormal(position2D));


        return position3D ?? Vector3.Zero;
    }
    
    public virtual Vector3 GetMouseIntersectedDirection()
    {
        return (characterToFollow.GlobalPosition - GetMouseIntersectedPosition()).Normalized();
    }
}
