using System;
using Content.Shared.Movement.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Physics;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Timing;

namespace Content.Client.Eye;

public sealed class EyeLerpingSystem : EntitySystem
{
    [Dependency] private readonly IEyeManager _eyeManager = default!;
    [Dependency] private readonly IPlayerManager _playerManager = default!;
    [Dependency] private readonly IGameTiming _gameTiming = default!;

    private Angle? _lastGridAngle;
    private Angle? _lerpTo;
    private Angle _lerpStartRotation;
    private float _accumulator;

    // How fast the camera rotates in radians / s
    private const float CameraRotateSpeed = MathF.PI;

    // Safety override
    private const float LerpTimeMax = 1.5f;


    public override void Initialize()
    {
        base.Initialize();

        UpdatesAfter.Add(typeof(TransformSystem));
        UpdatesAfter.Add(typeof(PhysicsSystem));
        UpdatesBefore.Add(typeof(EyeUpdateSystem));
    }

    public override void FrameUpdate(float frameTime)
    {
        if (!_gameTiming.IsFirstTimePredicted)
            return;

        var currentEye = _eyeManager.CurrentEye;

        if (_playerManager.LocalPlayer?.ControlledEntity is not {} mob || Deleted(mob))
            return;

        // We can't lerp if the mob can't move!
        if (!TryComp(mob, out IMoverComponent? mover))
            return;

        var moverLastGridAngle = mover.LastGridAngle;

        // Let's not turn the camera into a washing machine when the game starts.
        if (_lastGridAngle == null)
        {
            _lastGridAngle = moverLastGridAngle;
            currentEye.Rotation = -moverLastGridAngle;
            return;
        }

        // Check if the last lerp grid angle we have is not the same as the last mover grid angle...
        if (!_lastGridAngle.Value.EqualsApprox(moverLastGridAngle))
        {
            // And now, we start lerping.
            _lerpTo = moverLastGridAngle;
            _lastGridAngle = moverLastGridAngle;
            _lerpStartRotation = currentEye.Rotation;
            _accumulator = 0f;
        }

        if (_lerpTo != null)
        {
            _accumulator += frameTime;

            var lerpRot = -_lerpTo.Value.FlipPositive().Reduced();
            var startRot = _lerpStartRotation.FlipPositive().Reduced();

            var changeNeeded = Angle.ShortestDistance(startRot, lerpRot);

            if (changeNeeded.EqualsApprox(Angle.Zero))
            {
                // Nothing to do here!
                CleanupLerp();
                return;
            }

            // Get how much the camera should have moved by now. Make it faster depending on the change needed.
            var changeRot = (CameraRotateSpeed * Math.Max(1f, Math.Abs(changeNeeded) * 0.75f)) * _accumulator * Math.Sign(changeNeeded);

            // How close is this from reaching the end?
            var percentage = (float)Math.Abs(changeRot / changeNeeded);

            currentEye.Rotation = Angle.Lerp(startRot, lerpRot, percentage);

            // Either we have overshot, or we have taken way too long on this, emergency reset time
            if (percentage >= 1.0f || _accumulator >= LerpTimeMax)
            {
                CleanupLerp();
            }

            void CleanupLerp()
            {
                currentEye.Rotation = -_lerpTo.Value;
                _lerpStartRotation = currentEye.Rotation;
                _lerpTo = null;
                _accumulator = 0f;
            }
        }
        else
        {
            // This makes it so rotating the camera manually is impossible...
            // However, it is needed. Why? Because of a funny (hilarious, even) race condition involving
            // ghosting, this system listening for attached mob changes, and the eye rotation being reset after our
            // changes back to zero because of an EyeComponent state coming from the server being applied.
            // At some point we'll need to come up with a solution for that. But for now, I just want to fix this.
            currentEye.Rotation = -moverLastGridAngle;
        }
    }
}
