using System;
using System.Collections.Generic;
using UnityEngine;

public class RowingInputManager : MonoBehaviour
{
    public static RowingInputManager Instance;

    public enum InputSource { LivePM5, Recorded, Simulated }

    [Header("Data Source")]
    public InputSource currentInputSource = InputSource.LivePM5;
    
    [Header("Recorded Mode Controls")]
    public List<RowingSessionData> recordedSessions;
    public int currentSessionIndex = 0;
    [Range(0.1f, 5f)] public float playbackSpeed = 1f;
    public bool loopSimulation = true;

    [Header("Simulated Mode (Button Mashing)")]
    public KeyCode simulateStrokeKey = KeyCode.Space;
    public float simulateWattJump = 50f; // Lowered slightly so you have to work for it
    public float simulateWattDecayRate = 85f; // Increased so the "flywheel" slows down faster
    public float simulateMaxWatts = 300f; // The new hard cap!
    
    private float _currentSimulatedWatts = 0f;
    private float _lastSimStrokeTime = 0f;
    
    [Header("Game Physics Settings")]
    public float maxGameSpeed = 50f;
    public float wattsToSpeedMultiplier = 0.1f;
    public float speedSmoothTime = 0.75f;
    public int userMaxHR = 190; 

    [Header("Live Output (Read Only)")]
    public float CurrentSmoothedVelocity;
    public float LastStrokeImpulse;
    public bool IsLastStrokeSmooth;
    public int MaxPowerOutput;
    public int CurrentHRZone;
    public float LastStrokeRatio; 

    public Action<float> OnVelocityChanged; 
    public Action<float, bool, float> OnStrokeCompleted; 
    public Action<int> OnZoneChanged;

    private float _targetVelocity;
    private float _velocityVelocity;

    // Recorded State
    private float _playbackTimer = 0f;
    private int _playbackFrameIndex = 0;

    // Stroke Tracking State Machine
    private enum StrokeState { Idle, Drive, Recovery }
    private StrokeState _currentState = StrokeState.Idle;
    private float _driveTimer = 0f;
    private float _recoveryTimer = 0f;

    private void Awake()
    {
        if (Instance == null) Instance = this;
        else Destroy(gameObject);
    }

    private void Start()
    {
        if (PerformanceMonitorManager.Instance != null)
        {
            PerformanceMonitorManager.Instance.OnStatsUpdated += (stats) => { if (currentInputSource == InputSource.LivePM5) ProcessStats(stats.Watts, stats.SPM, stats.HeartRate); };
            PerformanceMonitorManager.Instance.OnForceCurveUpdated += (curve) => { if (currentInputSource == InputSource.LivePM5) ProcessForceCurve(curve); };
        }
    }

    private void Update()
    {
        HandleKeyboardControls();

        if (currentInputSource == InputSource.Recorded)
        {
            RunRecordedSession();
        }
        else if (currentInputSource == InputSource.Simulated)
        {
            RunManualSimulation();
        }

        // Apply Smoothing
        CurrentSmoothedVelocity = Mathf.SmoothDamp(CurrentSmoothedVelocity, _targetVelocity, ref _velocityVelocity, speedSmoothTime);
        OnVelocityChanged?.Invoke(CurrentSmoothedVelocity);

        // Timers
        if (_currentState == StrokeState.Drive) _driveTimer += Time.deltaTime;
        else if (_currentState == StrokeState.Recovery) _recoveryTimer += Time.deltaTime;
    }

    private void RunManualSimulation()
    {
        // 1. Decay the watts over time (Simulating the flywheel slowing down)
        _currentSimulatedWatts = Mathf.Max(0f, _currentSimulatedWatts - (simulateWattDecayRate * Time.deltaTime));

        // 2. Handle the "Pull"
        if (Input.GetKeyDown(simulateStrokeKey))
        {
            // Add power, but firmly cap it so we don't break the sound barrier
            _currentSimulatedWatts = Mathf.Min(_currentSimulatedWatts + simulateWattJump, simulateMaxWatts);

            // Calculate a fake SPM based on how fast you are mashing
            float timeSinceLast = Time.time - _lastSimStrokeTime;
            int fakeSPM = timeSinceLast > 0 ? Mathf.RoundToInt(60f / timeSinceLast) : 0;
            _lastSimStrokeTime = Time.time;

            // Fake HR that scales up gently with your power output
            int fakeHR = Mathf.Clamp(100 + Mathf.RoundToInt(_currentSimulatedWatts / 4f), 70, 200);

            ProcessStats(Mathf.RoundToInt(_currentSimulatedWatts), fakeSPM, fakeHR);

            // Generate a fake, perfectly smooth force curve (a bell shape)
            List<float> fakeCurve = new List<float> { 20f, 60f, 120f, 150f, 120f, 60f, 20f };
            ProcessForceCurve(fakeCurve);
            
            // Immediately send an empty list to signify the drive ended and trigger recovery
            ProcessForceCurve(new List<float>());
        }
        else
        {
            // If not pulling, just update the decaying watts
            ProcessStats(Mathf.RoundToInt(_currentSimulatedWatts), 0, 100); 
        }
    }

    private void RunRecordedSession()
    {
        if (recordedSessions == null || recordedSessions.Count == 0 || recordedSessions[currentSessionIndex] == null) return;

        var session = recordedSessions[currentSessionIndex];
        
        _playbackTimer += Time.deltaTime * playbackSpeed;

        while (_playbackFrameIndex < session.frames.Count && _playbackTimer >= session.frames[_playbackFrameIndex].timestamp)
        {
            RowingFrame frame = session.frames[_playbackFrameIndex];
            
            ProcessStats(frame.watts, frame.spm, frame.heartRate);
            
            if (frame.forceCurve != null && frame.forceCurve.Count > 0)
            {
                ProcessForceCurve(frame.forceCurve);
            }
            else if (frame.watts == 0 && frame.spm == 0)
            {
                ProcessForceCurve(new List<float>());
            }

            _playbackFrameIndex++;
        }

        if (_playbackFrameIndex >= session.frames.Count)
        {
            if (loopSimulation)
            {
                _playbackTimer = 0f;
                _playbackFrameIndex = 0;
            }
        }
    }

    private void HandleKeyboardControls()
    {
        if (currentInputSource == InputSource.Recorded)
        {
            if (Input.GetKeyDown(KeyCode.UpArrow)) playbackSpeed += 0.25f;
            if (Input.GetKeyDown(KeyCode.DownArrow)) playbackSpeed = Mathf.Max(0.25f, playbackSpeed - 0.25f);

            for (int i = 0; i < recordedSessions.Count; i++)
            {
                if (Input.GetKeyDown(KeyCode.Alpha1 + i))
                {
                    currentSessionIndex = i;
                    _playbackTimer = 0f;
                    _playbackFrameIndex = 0;
                    Debug.Log($"Switched to Session: {recordedSessions[i].sessionName}");
                }
            }
        }
    }

    // --- Core Logic (Shared by Live, Recorded, and Simulated) ---

    private void ProcessStats(int watts, int spm, int hr)
    {
        _targetVelocity = Mathf.Clamp(watts * wattsToSpeedMultiplier, 0, maxGameSpeed);
        if (watts > MaxPowerOutput) MaxPowerOutput = watts;

        int newZone = CalculateHRZone(hr);
        if (newZone != CurrentHRZone)
        {
            CurrentHRZone = newZone;
            OnZoneChanged?.Invoke(CurrentHRZone);
        }
    }

    private void ProcessForceCurve(List<float> points)
    {
        if (points == null || points.Count == 0) 
        {
            if (_currentState == StrokeState.Drive)
            {
                _currentState = StrokeState.Recovery;
            }
            return;
        }

        if (_currentState == StrokeState.Recovery || _currentState == StrokeState.Idle)
        {
            if (_recoveryTimer > 0)
            {
                LastStrokeRatio = _driveTimer / _recoveryTimer; 
                
                LastStrokeImpulse = CalculateImpulse(points);
                IsLastStrokeSmooth = AnalyzeSmoothness(points);

                OnStrokeCompleted?.Invoke(LastStrokeImpulse, IsLastStrokeSmooth, LastStrokeRatio);
            }
            _driveTimer = 0f;
            _recoveryTimer = 0f;
            _currentState = StrokeState.Drive;
        }
    }

    private int CalculateHRZone(int currentHR) { return 1; /* Replace with your actual HR math from previous step */ }
    private float CalculateImpulse(List<float> curve) { return 1f; /* Replace with your actual Impulse math */ }
    private bool AnalyzeSmoothness(List<float> curve) { return true; /* Replace with your actual Smoothness math */ }
}