namespace splitter.algo;

public enum TrackState
{
    Tracking,
    LostFreeze,
    LostDrift
}

public sealed class CameraController
{
    private readonly int _videoWidth;
    private readonly int _videoHeight;
    private readonly int _cropWidth;
    private readonly int _cropHeight;
    private readonly KalmanTracker _kalman;
    private readonly SingleJob _cmd;
    private int _dropoutCounter;

    // --- Dropout tolerance ---
    private int   _dropoutToleranceFrames = 20;
    private float _emaFactor              = 0.65f; // smoother but responsive
    private float _cameraEasing           = 0.03f; // stronger follow-through
    private int   _lostFreezeFrames       = 60;    // 2 seconds at 30 FPS


    private int        _lostFrames;
    private Point2f    _cameraCenter;
    private TrackState _state;
    private Point2f    _smoothedCenter;
    private Rect?      _objectBox;
    private Point2f?   _objectCenter;
    private Rect       _roi;

    public CameraController(
        int videoWidth,
        int videoHeight,
        int cropWidth,
        int cropHeight,
        KalmanTracker kalman,
        SingleJob cmd
        )
    {
        _videoWidth   = videoWidth;
        _videoHeight  = videoHeight;
        _cropWidth    = cropWidth;
        _cropHeight   = cropHeight;
        _kalman       = kalman;
        _cmd          = cmd;
        _cameraCenter = DefaultCenter;
        _state        = TrackState.Tracking;

    cmd.Override(ref _dropoutToleranceFrames, "DropoutToleranceFrames");
    cmd.Override(ref _emaFactor,              "EmaFactor");
    cmd.Override(ref _cameraEasing,           "CameraEasing");
    cmd.Override(ref _lostFreezeFrames,       "LostFreezeFrames");

        _kalman.Reset(_cameraCenter);
    }

    private Point2f DefaultCenter => _cmd.GravitateTo;

    public int LostFrames         => _lostFrames;
    public Point2f CameraCenter   => _cameraCenter;
    public TrackState State       => _state;
    public Point2f SmoothedCenter => _smoothedCenter;
    public Rect? ObjectBox        => _objectBox;
    public Point2f? ObjectCenter  => _objectCenter;
    public Rect Roi => _roi;

    public void Update(DetectedPerson? primary)
    {
        Rect?    objectBox    = null;
        Point2f? objectCenter = null;

        if (primary.HasValue)
        {
            objectCenter = primary.Value.Center;
            objectBox = primary.Value.Box;
        }

        // ---------------------------------------------------------
        // Dropout tolerance
        // ---------------------------------------------------------
        if (!objectCenter.HasValue)
        {
            if (_dropoutCounter < _dropoutToleranceFrames)
            {
                objectCenter = _kalman.LastMeasurement;
                _dropoutCounter++;
            }
        }
        else
        {
            _dropoutCounter = 0;
        }

        var isLost = !objectCenter.HasValue;

        // LOST / REACQUIRE STATE MACHINE
        if (isLost)
        {
            _lostFrames++;

            if (_lostFrames <= _lostFreezeFrames)
            {
                _state = TrackState.LostFreeze;
                objectCenter = null;
            }
            else
            {
                _state = TrackState.LostDrift;
                objectCenter = DefaultCenter;
            }
        }
        else
        {
            _state = TrackState.Tracking;
            _lostFrames = 0;
        }

        // KALMAN + CAMERA UPDATE
        Point2f smoothedCenter;

        if (_state == TrackState.Tracking)
        {
            smoothedCenter = _kalman.Update(objectCenter);

            // ---------------------------------------------------------
            // NEW: EMA smoothing
            // ---------------------------------------------------------
            smoothedCenter = new Point2f(
                smoothedCenter.X * (1 - _emaFactor) + _cameraCenter.X * _emaFactor,
                smoothedCenter.Y * (1 - _emaFactor) + _cameraCenter.Y * _emaFactor
            );

            _cameraCenter = new Point2f(
                _cameraCenter.X + (smoothedCenter.X - _cameraCenter.X) * _cameraEasing,
                _cameraCenter.Y + (smoothedCenter.Y - _cameraCenter.Y) * _cameraEasing);

        }
        else if (_state == TrackState.LostFreeze)
        {
            smoothedCenter = _kalman.LastMeasurement ?? _cameraCenter;
        }
        else // LOST_DRIFT
        {
            smoothedCenter = _kalman.Update(objectCenter);

            var driftEasing = 0.01f;
            var fallbackCenter = new Point2f(_videoWidth / 2f, _videoHeight / 2f);

            _cameraCenter = new Point2f(
                _cameraCenter.X + (fallbackCenter.X - _cameraCenter.X) * driftEasing,
                _cameraCenter.Y + (fallbackCenter.Y - _cameraCenter.Y) * driftEasing);
        }

        var halfW = _cropWidth  / 2f;
        var halfH = _cropHeight / 2f;

        smoothedCenter.X = Math.Clamp(smoothedCenter.X, halfW, _videoWidth - halfW);
        smoothedCenter.Y = Math.Clamp(smoothedCenter.Y, halfH, _videoHeight - halfH);

        _cameraCenter.X = Math.Clamp(_cameraCenter.X, halfW, _videoWidth - halfW);
        _cameraCenter.Y = Math.Clamp(_cameraCenter.Y, halfH, _videoHeight - halfH);

        var x = (int)Math.Round(_cameraCenter.X - halfW);
        var y = (int)Math.Round(_cameraCenter.Y - halfH);

        x = Math.Clamp(x, 0, _videoWidth - _cropWidth);
        y = Math.Clamp(y, 0, _videoHeight - _cropHeight);

        _roi = new Rect(x, y, _cropWidth, _cropHeight);

        _smoothedCenter = smoothedCenter;
        _objectBox      = objectBox;
        _objectCenter   = objectCenter;
    }
}
