using System;

namespace FluidSim.Components
{
    public class Vehicle
    {
        // ---- Gearbox ----
        public int CurrentGear { get; private set; } = 0;
        public readonly float[] GearRatios = { 2.5f, 1.8f, 1.4f, 1.1f, 0.9f, 0.75f };
        public float FinalDriveRatio = 3.0f;
        public float PrimaryReduction = 2.5f;

        // ---- Clutch ----
        public float ClutchInput { get; set; }
        public float ClutchDisengageTime = 0.15f;
        private float _clutchTimer;
        private float _currentEngagement = 0f;

        /// <summary>Time constant for clutch engagement smoothing (seconds).</summary>
        public float EngagementSmoothTime = 0.5f;   // longer, gentler bite

        private float TargetEngagement
        {
            get
            {
                if (ClutchInput > 0.01f) return 1f - ClutchInput;
                if (CurrentGear == 0 || _clutchTimer > 0f) return 0f;
                return 1f;
            }
        }

        public float Engagement => _currentEngagement;

        // ---- Clutch torque model ----
        /// <summary>Peak clutch friction torque (Nm) when fully engaged at high RPM.</summary>
        public float BaseMaxTorque = 80f;            // much lower than before

        /// <summary>Stiffness when slipping (Nm per rad/s). Lower = softer engagement.</summary>
        public float ClutchStiffness = 50f;          // very soft

        /// <summary>Below this engine RPM, the clutch torque is progressively reduced to prevent stalling.</summary>
        public float IdleRpm = 1200f;
        public float StallPreventionRamp = 300f;     // RPM band above idle where torque ramps up

        // ---- Physical constants ----
        public float Mass = 160f;
        public float WheelRadius = 0.32f;
        public float DragCoefficient = 0.35f;
        public float FrontalArea = 0.8f;
        public float AirDensity = 1.225f;
        public float RollingFrictionCoeff = 0.01f;
        public float Gravity = 9.81f;

        // ---- State ----
        public float Speed { get; private set; }

        public (float clutchTorqueOnEngine, float effectiveEngineInertia) Update(float engineRpm, float engineInertia, float dt)
        {
            if (_clutchTimer > 0f)
            {
                _clutchTimer -= dt;
                if (_clutchTimer < 0f) _clutchTimer = 0f;
            }

            float target = TargetEngagement;
            float smoothing = 1f - MathF.Exp(-dt / Math.Max(EngagementSmoothTime, 0.001f));
            _currentEngagement += (target - _currentEngagement) * smoothing;
            if (MathF.Abs(_currentEngagement - target) < 0.001f)
                _currentEngagement = target;

            float engagement = _currentEngagement;

            float totalGear = 1f;
            if (CurrentGear > 0)
                totalGear = GearRatios[CurrentGear - 1] * FinalDriveRatio * PrimaryReduction;

            float engineRadPerSec = engineRpm * 2f * MathF.PI / 60f;

            float v = MathF.Max(Speed, 0f);
            float drag = 0.5f * AirDensity * DragCoefficient * FrontalArea * v * v;
            float rolling = RollingFrictionCoeff * Mass * Gravity;
            float resistanceForce = drag + rolling;

            float clutchTorque = 0f;
            float effectiveInertia = engineInertia;

            if (engagement > 0f && CurrentGear > 0)
            {
                float vehicleReflectedRadPerSec = (Speed / WheelRadius) * totalGear;
                float slip = engineRadPerSec - vehicleReflectedRadPerSec;

                // Stall prevention: reduce max torque when engine RPM is near idle
                float torqueLimit = BaseMaxTorque * engagement;
                if (engineRpm < IdleRpm + StallPreventionRamp)
                {
                    float factor = Math.Clamp((engineRpm - IdleRpm) / StallPreventionRamp, 0f, 1f);
                    torqueLimit *= factor;
                }

                float stiffnessTorque = ClutchStiffness * engagement * slip;
                clutchTorque = Math.Clamp(stiffnessTorque, -torqueLimit, torqueLimit);

                // Lock if slip negligible and engagement high
                if (engagement >= 0.99f && MathF.Abs(slip) < 1.0f)
                {
                    float vehicleInertia = Mass * WheelRadius * WheelRadius;
                    float reflectedVehicleInertia = vehicleInertia / (totalGear * totalGear);
                    effectiveInertia = engineInertia + reflectedVehicleInertia;

                    Speed = engineRadPerSec * WheelRadius / totalGear;
                    float loadTorque = resistanceForce * WheelRadius / totalGear;
                    return (loadTorque, effectiveInertia);
                }
            }

            float driveTorqueAtWheel = clutchTorque * totalGear;
            float driveForce = driveTorqueAtWheel / WheelRadius;
            float netForce = driveForce - resistanceForce;
            float acceleration = netForce / Mass;
            Speed += acceleration * dt;
            if (Speed < 0f) Speed = 0f;

            return (clutchTorque, engineInertia);
        }

        public void ShiftUp()
        {
            if (CurrentGear < GearRatios.Length)
            {
                CurrentGear++;
                AutoDisengageClutch();
            }
        }

        public void ShiftDown()
        {
            if (CurrentGear > 1)
            {
                CurrentGear--;
                AutoDisengageClutch();
            }
        }

        public void SetNeutral()
        {
            CurrentGear = 0;
            _clutchTimer = 0f;
        }

        public void SetFirstGear()
        {
            if (CurrentGear == 0)
            {
                CurrentGear = 1;
                AutoDisengageClutch();
            }
        }

        private void AutoDisengageClutch()
        {
            _clutchTimer = ClutchDisengageTime;
        }

        public float SpeedKmh => Speed * 3.6f;
    }
}