using System;
using System.Collections.Generic;
using System.IO;
using GTA;
using GTA.Math;
using GTA.Native;

public class AmbientAiPolice : Script
{
    private readonly Random random = new Random();
    private readonly List<ActiveUnit> activeUnits = new List<ActiveUnit>();
    private DateTime lastCheckTime = DateTime.MinValue;

    // =====================================================
    // UPDATED ZONE STRUCTURE - ONLY YOUR COORDINATES
    // =====================================================
    private readonly (Vector3 pos, float heading)[] blaineSandyGrapeseedSenoraFwySpawns =
    {
        (new Vector3(2414.94f, 3937.05f, 35.80f), 334.23f),
        (new Vector3(2467.29f, 4386.85f, 35.55f), 160.57f),
        (new Vector3(2661.18f, 4804.91f, 33.10f), 313.96f),
        (new Vector3(2974.36f, 4526.39f, 48.59f), 173.20f),
        (new Vector3(2782.182f, 4537.769f, 55.00f), 14.626f),
        (new Vector3(2668.800f, 4955.232f, 44.00f), 11.227f),
        (new Vector3(2603.198f, 5286.061f, 44.00f), 17.229f),
        (new Vector3(2722.210f, 4377.139f, 47.00f), 300.105f),
        (new Vector3(2471.31f, 2929.05f, 40.33f), 310.99f),
        (new Vector3(2735.24f, 5010.83f, 37.16f), 198.05f),
        (new Vector3(2472.64f, 5663.70f, 44.89f), 22.88f),
        (new Vector3(2484.76f, 5510.42f, 44.35f), 197.49f),
        (new Vector3(2800.569f, 3410.840f, 55.265f), 334.474f),
        (new Vector3(1913.825f, 3626.379f, 33.324f), 297.283f)
    };

    private readonly (Vector3 pos, float heading)[] blainePaletoGreatOceanHwySpawns =
    {
        (new Vector3(3.77f, 6400.67f, 30.85f), 143.21f),
        (new Vector3(-593.35f, 5673.33f, 37.79f), 159.0f),
        (new Vector3(-2395.07f, 3827.19f, 23.71f), 50.84f),
        (new Vector3(1842.502f, 6371.044f, 40.054f), 79.999f),
        (new Vector3(1401.671f, 6489.430f, 19.703f), 76.983f),
        (new Vector3(571.797f, 6556.521f, 27.462f), 84.742f),
        (new Vector3(-965.713f, 5417.881f, 38.817f), 118.630f),
        (new Vector3(-1445.328f, 5071.627f, 61.249f), 127.512f),
        (new Vector3(-2244.31f, 4294.07f, 46.78f), 147.23f),
        (new Vector3(-2231.715f, 4319.681f, 47.999f), 158.729f)
    };

    private readonly (Vector3 pos, float heading)[] senoraLocalSpawns =
    {
        (new Vector3(862.90f, 2705.95f, 40.25f), 88.12f),
        (new Vector3(237.97f, 2963.86f, 42.23f), 5.61f),
        (new Vector3(-673.35f, 2845.63f, 29.48f), 120.68f),
        (new Vector3(1805.17f, 2332.38f, 55.91f), 200.97f),
        (new Vector3(584.89f, 2334.08f, 48.52f), 237.15f),
        (new Vector3(2071.439f, 3055.153f, 45.603f), 243.161f)
    };

    private readonly (Vector3 pos, float heading)[] lsSouthPortSpawns =
    {
        (new Vector3(869.32f, -2092.50f, 30.13f), 352.10f),
        (new Vector3(788.75f, -2113.26f, 28.95f), 354.53f),
        (new Vector3(336.71f, -2224.86f, 10.84f), 257.41f),
        (new Vector3(312.54f, -2498.57f, 5.76f), 185.23f),
        (new Vector3(-55.97f, -2615.07f, 5.79f), 93.26f),
        (new Vector3(-402.12f, -2762.43f, 5.78f), 197.66f)
    };

    private readonly (Vector3 pos, float heading)[] lsNEHighwaySpawns =
    {
        (new Vector3(1585.724f, -994.692f, 59.576f), 299.646f),
        (new Vector3(2467.166f, -156.913f, 88.435f), 334.078f),
        (new Vector3(2554.416f, 777.331f, 89.473f), 14.106f),
        (new Vector3(2568.631f, 483.713f, 108.058f), 181.073f),
        (new Vector3(1377.448f, 688.062f, 78.825f), 140.195f),
        (new Vector3(1817.543f, 2133.373f, 54.093f), 188.367f)
    };

    private readonly (Vector3 pos, float heading)[] lsCoastalWestSpawns =
    {
        (new Vector3(-2438.295f, -234.612f, 15.937f), 242.858f),
        (new Vector3(-3002.615f, 422.426f, 14.522f), 174.479f),
        (new Vector3(-1833.71f, -243.92f, 40.34f), 37.68f),
        (new Vector3(-2109.14f, -293.34f, 12.72f), 260.46f),
        (new Vector3(-2816.33f, 2217.85f, 28.04f), 113.83f)
    };

    private readonly (Vector3 pos, float heading)[] lsCentralSpawns =
    {
        (new Vector3(-454.835f, -545.044f, 25.118f), 340.849f),
        (new Vector3(1132.927f, -1876.680f, 30.579f), 202.668f),
        (new Vector3(93.53f, -141.05f, 54.80f), 342.33f),
        (new Vector3(-12.62f, -1147.51f, 27.08f), 271.00f),
        (new Vector3(-692.38f, -648.07f, 30.68f), 87.90f),
        (new Vector3(85.09f, -1442.00f, 28.97f), 50.17f)
    };

    private readonly (Vector3 pos, float heading)[] lsWestSheriffRangerSpawns =
    {
        (new Vector3(-1698.002f, 867.090f, 146.550f), 330.646f),
        (new Vector3(-1530.274f, 1414.908f, 122.534f), 313.971f),
        (new Vector3(-1472.943f, 2030.788f, 64.130f), 27.114f),
        (new Vector3(-1363.992f, 2161.210f, 51.292f), 329.349f),
        (new Vector3(-1153.751f, 2641.986f, 15.755f), 133.056f),
        (new Vector3(-1940.944f, 657.573f, 123.814f), 152.401f)
    };

    private readonly (Vector3 pos, float heading)[] lsiaSpawns =
    {
        (new Vector3(-848.91f, -2232.03f, 17.27f), 127.91f),
        (new Vector3(-1018.27f, -2481.37f, 19.65f), 148.94f),
        (new Vector3(-1020.92f, -2504.28f, 13.35f), 148.61f),
        (new Vector3(-871.255f, -2421.678f, 13.473f), 71.364f),
        (new Vector3(-1047.148f, -2715.130f, 13.209f), 240.530f),
        (new Vector3(-868.342f, -2633.694f, 13.490f), 328.651f),
        (new Vector3(-572.982f, -2211.542f, 5.464f), 139.273f),
        (new Vector3(-1000.008f, -2098.143f, 11.864f), 101.621f),
        (new Vector3(-1093.378f, -2025.239f, 12.553f), 213.849f),
        (new Vector3(-781.195f, -2177.081f, 15.272f), 133.228f),
        (new Vector3(-1081.490f, -2589.566f, 19.605f), 150.086f)
    };

    private readonly (Vector3 pos, float heading)[] blaineParkRangerSpawns =
    {
        (new Vector3(-2205.00f, 2313.70f, 32.68f), 119.81f),
        (new Vector3(-1398.83f, 5102.84f, 60.52f), 121.63f),
        (new Vector3(-670.55f, 5894.67f, 16.16f), 348.36f),
        (new Vector3(1400.65f, 4485.89f, 51.61f), 262.84f),
        (new Vector3(2941.37f, 5048.36f, 30.17f), 95.21f)
    };

    // =====================================================
    // ADDED: CIVILIAN VEHICLE MODELS FOR TRAFFIC STOPS
    // =====================================================
    private readonly string[] civilianVehicles =
    {
        "premier",       // Existing
        "felon",         // Added: 4-door sedan
        "asterope",      // Added: Common sedan
        "washington",    // Added: 4-door sedan
        "intruder",      // Added: Sleek sedan
        "stanier",       // Added: Classic police-looking civilian car
        "tailgater",     // Added: High-end sedan
        "oracle",        // Added: Luxury sedan
        "fugitive",      // Added: Sports sedan
        "schafter",      // Added: Executive sedan
        "glendale",      // Added: Classic sedan
        "primo",         // Added: Family sedan
        "ingot",         // Added: Hatchback
        "asea",          // Added: Basic sedan
        "emperor",       // Added: Older sedan
        "regina",        // Added: Station wagon
        "stratum",       // Added: Compact wagon
        "surge",         // Added: Hybrid sedan
        "asterope",      // Added: Another common sedan
        "cognoscenti"    // Added: High-end luxury
    };

    // =====================================================
    // VEHICLES AND BEHAVIOR PER ZONE LOADED FROM INI
    // =====================================================
    private readonly Dictionary<string, ZoneSettings> zoneSettings = new Dictionary<string, ZoneSettings>();

    public AmbientAiPolice()
    {
        Tick += OnTick;
        Interval = 1000;
        LoadSettingsFromIni("scripts\\AIAmbientPolice_DefaultCars.ini");
    }

    // ===========================
    // LOAD ALL SETTINGS FROM INI
    // ===========================
    private void LoadSettingsFromIni(string path)
    {
        if (!File.Exists(path))
        {
            UI.Notify("Settings .ini file not found: " + path);
            return;
        }

        string currentZone = null;
        ZoneSettings settings = null;

        foreach (var line in File.ReadAllLines(path))
        {
            string l = line.Trim();
            if (string.IsNullOrEmpty(l) || l.StartsWith(";")) continue;

            if (l.StartsWith("[") && l.EndsWith("]"))
            {
                if (currentZone != null && settings != null)
                    zoneSettings[currentZone] = settings;

                currentZone = l.Substring(1, l.Length - 2);
                settings = new ZoneSettings();
                continue;
            }

            if (settings == null) continue;

            string[] kv = l.Split(new char[] { '=' }, 2);
            if (kv.Length != 2) continue;

            string key = kv[0].Trim().ToLower();
            string value = kv[1].Trim();

            switch (key)
            {
                // Vehicle lists
                case "sheriffvehicles":
                    settings.SheriffVehicles = SplitList(value);
                    break;
                case "copvehicles":
                    settings.CopVehicles = SplitList(value);
                    break;
                case "highwayvehicles":
                    settings.HighwayVehicles = SplitList(value);
                    break;

                // Vehicle chances
                case "copchance":
                    if (int.TryParse(value, out int cc)) settings.CopChance = ClampPercent(cc);
                    break;
                case "sheriffchance":
                    if (int.TryParse(value, out int sc)) settings.SheriffChance = ClampPercent(sc);
                    break;
                case "highwaychance":
                    if (int.TryParse(value, out int hc)) settings.HighwayChance = ClampPercent(hc);
                    break;

                // Behavior percentages (NEW - with defaults)
                case "pulloverpercentage":
                    if (int.TryParse(value, out int po)) settings.PulloverPercentage = ClampPercent(po);
                    break;
                case "donothingpercentage":
                    if (int.TryParse(value, out int dn)) settings.DoNothingPercentage = ClampPercent(dn);
                    break;
                case "sittingpatrolpercentage":
                    if (int.TryParse(value, out int sp)) settings.SittingPatrolPercentage = ClampPercent(sp);
                    break;
            }
        }

        if (currentZone != null && settings != null)
            zoneSettings[currentZone] = settings;
    }

    private string[] SplitList(string value) =>
        value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

    private int ClampPercent(int val) => Math.Max(0, Math.Min(100, val));

    // ===========================
    // ON TICK
    // ===========================
    private void OnTick(object sender, EventArgs e)
    {
        Ped player = Game.Player.Character;
        if (!player.Exists()) return;

        // Remove invalid units
        activeUnits.RemoveAll(u => !u.IsValid());

        // Update traffic stops
        foreach (var unit in activeUnits)
        {
            if (unit.IsTrafficStop && unit.TrafficStop != null && unit.TrafficStop.IsActive)
                unit.TrafficStop.Update();
        }

        // Check sitting patrols for deletion if they haven't moved for 30 seconds
        foreach (var unit in activeUnits)
        {
            if (!unit.IsTrafficStop && unit.Vehicle != null && unit.Vehicle.Exists())
            {
                if (!unit.HasMovedFromSpawn)
                {
                    float dist = unit.Vehicle.Position.DistanceTo(unit.SpawnPosition);
                    if (dist > 1f) unit.HasMovedFromSpawn = true;
                    else if (Game.GameTime - unit.SpawnGameTime >= 180000) //  3 min
                    {
                        unit.Vehicle.Delete();
                        if (unit.Driver != null && unit.Driver.Exists()) unit.Driver.Delete();
                    }
                }
            }
        }

        if ((DateTime.Now - lastCheckTime).TotalSeconds >= 20)
        {
            // Spawn all zones with 500f patrol distance
            SpawnZone(player, blaineSandyGrapeseedSenoraFwySpawns, "BlaineSandyGrapeseedSenoraFwy", 500f);
            SpawnZone(player, blainePaletoGreatOceanHwySpawns, "BlainePaletoGreatOceanHwy", 500f);
            SpawnZone(player, senoraLocalSpawns, "SenoraLocal", 500f);
            SpawnZone(player, lsSouthPortSpawns, "LSSouthPort", 500f);
            SpawnZone(player, lsNEHighwaySpawns, "LSNEHighway", 500f);
            SpawnZone(player, lsCoastalWestSpawns, "LSCoastalWest", 500f);
            SpawnZone(player, lsCentralSpawns, "LSCentral", 500f);
            SpawnZone(player, lsWestSheriffRangerSpawns, "LSWestSheriffRanger", 500f);
            SpawnZone(player, lsiaSpawns, "LSIA", 500f);
            SpawnZone(player, blaineParkRangerSpawns, "BlaineParkRanger", 500f);

            lastCheckTime = DateTime.Now;
        }
    }

    // ===========================
    // SPAWN ZONE LOGIC
    // ===========================
    private void SpawnZone(Ped player, (Vector3 pos, float heading)[] spawns, string zoneName, float patrolDistance)
    {
        if (spawns.Length == 0) return; // Skip empty zones

        if (!zoneSettings.TryGetValue(zoneName, out var settings))
            settings = new ZoneSettings(); // Use defaults

        foreach (var spot in spawns)
        {
            if (activeUnits.Exists(u => u.CanSpawnAt(spot.pos))) continue;
            if (IsEmergencyVehicleNearby(spot.pos, 100f)) continue;
            if (player.Position.DistanceTo(spot.pos) > patrolDistance) continue;

            string vehicle = GetRandomVehicleByChance(settings);
            if (vehicle == null) continue;

            // Determine behavior based on percentages
            double roll = random.NextDouble() * 100;

            if (roll < settings.PulloverPercentage)
                SpawnTrafficStop(spot.pos, spot.heading, vehicle, zoneName);
            else if (roll < settings.PulloverPercentage + settings.SittingPatrolPercentage)
                SpawnSittingPatrol(spot.pos, spot.heading, vehicle, zoneName);
            // Do Nothing case - just don't spawn anything
        }
    }

    // ===========================
    // EMERGENCY VEHICLE CHECK
    // ===========================
    private bool IsEmergencyVehicleNearby(Vector3 spot, float radius)
    {
        foreach (Vehicle v in World.GetAllVehicles())
        {
            if (v == null || !v.Exists()) continue;
            if (v == Game.Player.Character.CurrentVehicle) continue;

            int vehicleClass = Function.Call<int>(Hash.GET_VEHICLE_CLASS, v.Handle);
            if (vehicleClass != 18) continue;

            if (v.Position.DistanceTo(spot) <= radius) return true;
        }
        return false;
    }

    // ===========================
    // GET VEHICLE BASED ON PER-AGENCY CHANCE
    // ===========================
    private string GetRandomVehicleByChance(ZoneSettings settings)
    {
        int roll = random.Next(0, 100);

        if (roll < settings.CopChance && settings.CopVehicles.Length > 0)
            return settings.CopVehicles[random.Next(settings.CopVehicles.Length)];

        if (roll < settings.CopChance + settings.SheriffChance && settings.SheriffVehicles.Length > 0)
            return settings.SheriffVehicles[random.Next(settings.SheriffVehicles.Length)];

        if (settings.HighwayVehicles.Length > 0)
            return settings.HighwayVehicles[random.Next(settings.HighwayVehicles.Length)];

        return null;
    }

    // ===========================
    // GET RANDOM CIVILIAN VEHICLE
    // ===========================
    private string GetRandomCivilianVehicle()
    {
        return civilianVehicles[random.Next(civilianVehicles.Length)];
    }

    // ===========================
    // SPAWN SITTING PATROL
    // ===========================
    private void SpawnSittingPatrol(Vector3 pos, float heading, string vehicle, string zoneName)
    {
        if (vehicle == null) return;

        Model vModel = new Model(vehicle); vModel.Request(2000);
        if (!vModel.IsLoaded) return;

        Vehicle veh = World.CreateVehicle(vModel, pos, heading);
        if (veh == null || !veh.Exists()) return;

        veh.IsPersistent = true;
        veh.EngineRunning = true;

        string pedModel = GetPedForVehicleAndZone(vehicle, zoneName);
        Model pModel = new Model(pedModel); pModel.Request(2000);
        if (!pModel.IsLoaded) return;

        Ped ped = World.CreatePed(pModel, pos);
        if (ped != null && ped.Exists())
        {
            ped.BlockPermanentEvents = true;
            ped.SetIntoVehicle(veh, VehicleSeat.Driver);
        }

        activeUnits.Add(new ActiveUnit
        {
            Vehicle = veh,
            Driver = ped,
            SpawnPosition = pos,
            SpawnGameTime = Game.GameTime,
            HasMovedFromSpawn = false,
            IsTrafficStop = false
        });
    }

    // ===========================
    // SPAWN TRAFFIC STOP
    // ===========================
    private void SpawnTrafficStop(Vector3 pos, float heading, string vehicleName, string zoneName)
    {
        if (vehicleName == null) return;

        string pedModel = GetPedForVehicleAndZone(vehicleName, zoneName);

        // Get a random civilian vehicle from the expanded list
        string civilianVehicle = GetRandomCivilianVehicle();

        TrafficStop stop = new TrafficStop(pos, heading, vehicleName, pedModel, civilianVehicle);
        stop.Spawn();

        activeUnits.Add(new ActiveUnit
        {
            Vehicle = stop.SheriffVehicle,
            Driver = stop.SheriffPed,
            IsTrafficStop = true,
            TrafficStop = stop
        });
    }

    // ===========================
    // GET PED MODEL
    // ===========================
    private string GetPedForVehicleAndZone(string vehicleName, string zoneName)
    {
        if (zoneName.Equals("BlaineParkRanger", StringComparison.OrdinalIgnoreCase))
            return "s_m_m_ranger_01";

        if (zoneName.Equals("LSWestSheriffRanger", StringComparison.OrdinalIgnoreCase))
        {
            // Special logic for LSWestSheriffRanger zone
            if (zoneSettings.TryGetValue(zoneName, out var settings))
            {
                // If vehicle is in CopVehicles list, use ranger ped
                if (Array.Exists(settings.CopVehicles, v => v == vehicleName))
                    return "s_m_m_ranger_01";

                // If vehicle is in SheriffVehicles list, use sheriff ped
                if (Array.Exists(settings.SheriffVehicles, v => v == vehicleName))
                    return "s_m_y_sheriff_01";

                // If vehicle is in HighwayVehicles list, use highway patrol ped
                if (Array.Exists(settings.HighwayVehicles, v => v == vehicleName))
                    return "s_m_y_hwaycop_01";
            }

            // Fallback to ranger for this zone
            return "s_m_m_ranger_01";
        }

        // Default logic for other zones
        if (zoneSettings.TryGetValue(zoneName, out var defaultSettings))
        {
            if (Array.Exists(defaultSettings.CopVehicles, v => v == vehicleName))
                return "s_m_y_cop_01";
            if (Array.Exists(defaultSettings.SheriffVehicles, v => v == vehicleName))
                return "s_m_y_sheriff_01";
            if (Array.Exists(defaultSettings.HighwayVehicles, v => v == vehicleName))
                return "s_m_y_hwaycop_01";
        }

        return "s_m_y_cop_01"; // fallback
    }

    // ===========================
    // NESTED CLASSES
    // ===========================

    // NEW: Combined settings class for vehicles and behavior
    private class ZoneSettings
    {
        // Vehicle settings
        public string[] SheriffVehicles = new string[0];
        public string[] CopVehicles = new string[0];
        public string[] HighwayVehicles = new string[0];

        public int CopChance = 33;
        public int SheriffChance = 33;
        public int HighwayChance = 34;

        // Behavior settings (NEW - with defaults)
        public int PulloverPercentage = 50;    // Default: 50% traffic stops
        public int DoNothingPercentage = 25;   // Default: 25% do nothing
        public int SittingPatrolPercentage = 25; // Default: 25% sitting patrols
    }

    private class ActiveUnit
    {
        public Vehicle Vehicle;
        public Ped Driver;
        public Vector3 SpawnPosition;
        public int SpawnGameTime;
        public bool HasMovedFromSpawn;
        public bool IsTrafficStop;
        public TrafficStop TrafficStop;

        public bool IsValid() => Vehicle != null && Vehicle.Exists();
        public bool CanSpawnAt(Vector3 pos) =>
            Vehicle != null && Vehicle.Exists() &&
            Vehicle.Position.DistanceTo(pos) < 5f &&
            (!IsTrafficStop || (TrafficStop != null && TrafficStop.IsActive));
    }

    private class TrafficStop
    {
        public bool IsActive { get; private set; } = true;
        public Vehicle SheriffVehicle;
        public Ped SheriffPed;

        private Vehicle _civilianVehicle;
        private Ped _civilianDriver;
        private Vector3 _approachPoint;
        private Vector3 _windowPoint;
        private Vector3 _patDownPoint;
        private bool _civilianStartedDriving = false;
        private bool _policeStartedDriving = false;

        // ADDED: Track pat down animation stage
        private int _patDownAnimStage = 0; // 0 = not started, 1-6 = animation sequence stages
        private int _patDownAnimStartTime = 0;

        private Random _random = new Random();

        private Vector3 StopLocation;
        private float StopHeading;
        private string PoliceVehicleName;
        private string PedModel;
        private string CivilianVehicleModel;

        private enum StopState
        {
            Spawned, WaitExited, GoApproachPoint, GoPassengerWindow,
            WaitAtWindow, WritingTicket, GoBackToCar, EnteringCar, DrivingOff, Cleanup,
            // New states for scenario 2
            CivilianExiting, CivilianWalkingToCop, PatDown, CivilianReturningToCar,
            OfficerWaiting, OfficerReturningToCar
        }

        private StopState _state = StopState.Spawned;
        private int _stateStartTime;
        private int _scenario = 1;

        public TrafficStop(Vector3 pos, float heading, string policeVehicle, string pedModel, string civilianVehicleModel)
        {
            StopLocation = pos;
            StopHeading = heading;
            PoliceVehicleName = policeVehicle;
            PedModel = pedModel;
            CivilianVehicleModel = civilianVehicleModel;

            _scenario = _random.Next(1, 3); // 1 or 2
        }

        public void Spawn()
        {
            Model sheriffVehModel = new Model(PoliceVehicleName); sheriffVehModel.Request(5000);
            Model sheriffPedModel = new Model(PedModel); sheriffPedModel.Request(5000);
            Model civVehModel = new Model(CivilianVehicleModel); civVehModel.Request(5000);

            if (!sheriffVehModel.IsLoaded || !sheriffPedModel.IsLoaded || !civVehModel.IsLoaded) return;

            SheriffVehicle = World.CreateVehicle(sheriffVehModel, StopLocation, StopHeading);
            if (SheriffVehicle == null || !SheriffVehicle.Exists()) { IsActive = false; return; }

            SheriffPed = SheriffVehicle.CreatePedOnSeat(VehicleSeat.Driver, sheriffPedModel);
            if (SheriffPed != null) SheriffPed.BlockPermanentEvents = true;

            _civilianVehicle = World.CreateVehicle(civVehModel, StopLocation + (SheriffVehicle.ForwardVector * 8f), StopHeading);
            if (_civilianVehicle != null) { _civilianVehicle.IsPersistent = true; _civilianVehicle.EngineRunning = true; }

            _civilianDriver = _civilianVehicle?.CreatePedOnSeat(VehicleSeat.Driver, PedHash.Business01AMY);
            if (_civilianDriver != null) _civilianDriver.BlockPermanentEvents = true;

            // Set emergency lights on
            Function.Call(Hash.SET_VEHICLE_ENGINE_ON, SheriffVehicle.Handle, true, true, false);
            Function.Call(Hash.SET_VEHICLE_SIREN, SheriffVehicle.Handle, true);
            Function.Call(Hash.DISABLE_VEHICLE_IMPACT_EXPLOSION_ACTIVATION, SheriffVehicle.Handle, true);

            _stateStartTime = Game.GameTime;
        }

        private bool Elapsed(int ms) => Game.GameTime - _stateStartTime >= ms;

        // Helper method to wait
        private void Wait(int ms)
        {
            int start = Game.GameTime;
            while (Game.GameTime - start < ms)
            {
                Script.Yield();
            }
        }

        public void Update()
        {
            // Check if police vehicle was deleted/disappeared
            if (SheriffVehicle == null || !SheriffVehicle.Exists())
            {
                Cleanup();
                DeleteAllTrafficStopEntities();
                return;
            }

            if (SheriffVehicle == null || !SheriffVehicle.Exists() ||
                SheriffPed == null || !SheriffPed.Exists() ||
                _civilianVehicle == null || !_civilianVehicle.Exists() ||
                _civilianDriver == null || !_civilianDriver.Exists())
            {
                Cleanup();
                return;
            }

            // ADDED: Handle pat down animation sequence
            if (_state == StopState.PatDown && _patDownAnimStage > 0 && _patDownAnimStage < 7)
            {
                UpdatePatDownState();
            }

            switch (_state)
            {
                case StopState.Spawned:
                    SheriffPed.Task.LeaveVehicle(SheriffVehicle, LeaveVehicleFlags.None);
                    _stateStartTime = Game.GameTime;
                    _state = StopState.WaitExited;
                    break;

                // ==============================================
                // COMMON INITIAL STATES (BOTH SCENARIOS)
                // ==============================================
                case StopState.WaitExited:
                    // Wait for officer to exit vehicle
                    if (!SheriffPed.IsInVehicle(SheriffVehicle) || Elapsed(3000))
                    {
                        _approachPoint = _civilianVehicle.Position + (_civilianVehicle.RightVector * 2.5f) - (_civilianVehicle.ForwardVector * 1.5f);
                        SheriffPed.Task.GoTo(_approachPoint);
                        _stateStartTime = Game.GameTime;
                        _state = StopState.GoApproachPoint;
                    }
                    break;

                case StopState.GoApproachPoint:
                    if (SheriffPed.Position.DistanceTo(_approachPoint) <= 1.5f || Elapsed(6000))
                    {
                        // Different paths based on scenario
                        if (_scenario == 1)
                        {
                            // Scenario 1: Go to passenger window (original behavior)
                            _windowPoint = _civilianVehicle.Position + (_civilianVehicle.RightVector * 1.4f) + (_civilianVehicle.ForwardVector * 0.8f);
                            SheriffPed.Task.GoTo(_windowPoint);
                            _stateStartTime = Game.GameTime;
                            _state = StopState.GoPassengerWindow;
                        }
                        else
                        {
                            // Scenario 2: Signal civilian to exit
                            _windowPoint = _civilianVehicle.Position + (_civilianVehicle.RightVector * 1.4f) + (_civilianVehicle.ForwardVector * 0.8f);
                            SheriffPed.Task.GoTo(_windowPoint);
                            _stateStartTime = Game.GameTime;
                            _state = StopState.CivilianExiting;
                        }
                    }
                    break;

                // ==============================================
                // SCENARIO 1: WRITE TICKET (ORIGINAL BEHAVIOR)
                // ==============================================
                case StopState.GoPassengerWindow:
                    if (SheriffPed.Position.DistanceTo(_windowPoint) <= 1.5f || Elapsed(7000))
                    {
                        SheriffPed.Task.TurnTo(_civilianVehicle);
                        _stateStartTime = Game.GameTime;
                        _state = StopState.WaitAtWindow;
                    }
                    break;
                case StopState.WaitAtWindow:
                    if (Elapsed(1500))
                    {
                        const string dict = "amb@world_human_clipboard@male@base";
                        Function.Call(Hash.REQUEST_ANIM_DICT, dict);
                        if (Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, dict))
                            SheriffPed.Task.PlayAnimation(dict, "base", 8f, -1, AnimationFlags.Loop);

                        _stateStartTime = Game.GameTime;
                        _state = StopState.WritingTicket;
                    }
                    break;
                case StopState.WritingTicket:
                    if (Elapsed(30000))
                    {
                        SheriffPed.Task.ClearAll();
                        SheriffPed.Task.GoTo(SheriffVehicle.Position);
                        _stateStartTime = Game.GameTime;
                        _state = StopState.GoBackToCar;
                    }
                    break;
                case StopState.GoBackToCar:
                    if (SheriffPed.Position.DistanceTo(SheriffVehicle.Position) <= 2.5f || Elapsed(8000))
                    {
                        SheriffPed.Task.EnterVehicle(SheriffVehicle, VehicleSeat.Driver);
                        _stateStartTime = Game.GameTime;
                        _state = StopState.EnteringCar;
                    }
                    break;
                case StopState.EnteringCar:
                    if (SheriffPed.IsInVehicle(SheriffVehicle) || Elapsed(8000))
                    {
                        // Turn OFF all emergency lights and sirens when officer gets back in car
                        Function.Call(Hash.SET_VEHICLE_SIREN, SheriffVehicle.Handle, false); // Turn off siren system completely
                        Function.Call(Hash.DISABLE_VEHICLE_IMPACT_EXPLOSION_ACTIVATION, SheriffVehicle.Handle, false);

                        _stateStartTime = Game.GameTime;
                        _state = StopState.DrivingOff;
                    }
                    break;
                case StopState.DrivingOff:
                    if (!_civilianStartedDriving && Elapsed(2000))
                    {
                        // Make civilian drive like normal AI traffic
                        _civilianDriver.Task.CruiseWithVehicle(_civilianVehicle, 18f, (int)DrivingStyle.Normal);
                        _civilianStartedDriving = true;
                    }
                    if (!_policeStartedDriving && Elapsed(4000))
                    {
                        // Make police officer drive like normal AI traffic
                        SheriffPed.Task.CruiseWithVehicle(SheriffVehicle, 18f, (int)DrivingStyle.Normal);
                        _policeStartedDriving = true;
                    }

                    // No timeout - let them drive normally in the game world
                    if (_civilianStartedDriving && _policeStartedDriving)
                    {
                        // Set IsActive to false but don't delete entities
                        // They'll continue driving like normal AI
                        IsActive = false;
                    }
                    break;

                // ==============================================
                // SCENARIO 2: PAT DOWN
                // ==============================================
                case StopState.CivilianExiting:
                    if (SheriffPed.Position.DistanceTo(_windowPoint) <= 1.5f || Elapsed(3000))
                    {
                        // Officer signals civilian to exit by stepping back
                        SheriffPed.Task.TurnTo(_civilianVehicle);

                        // Officer takes 2 steps back from the window
                        Vector3 officerStepBack = SheriffPed.Position - (_civilianVehicle.ForwardVector * 1f);
                        officerStepBack.Z = SheriffPed.Position.Z;

                        // Move officer to step back position
                        Function.Call(Hash.TASK_GO_TO_COORD_ANY_MEANS, SheriffPed.Handle,
                                     officerStepBack.X, officerStepBack.Y, officerStepBack.Z,
                                     2.0f, 0, false, 1, 0f);

                        _stateStartTime = Game.GameTime;
                        _state = StopState.CivilianWalkingToCop;
                    }
                    break;

                case StopState.CivilianWalkingToCop:
                    // Wait for officer to get to step back position
                    if (SheriffPed.Position.DistanceTo(SheriffPed.Position - (_civilianVehicle.ForwardVector * 1f)) <= 1f || Elapsed(2000))
                    {
                        // Now have civilian exit
                        if (!_civilianDriver.IsInVehicle(_civilianVehicle))
                        {
                            // Civilian is already out, proceed to pat down
                            _civilianDriver.Task.StandStill(1000);
                            CreatePatDown();
                        }
                        else
                        {
                            // Civilian exits vehicle and walks to officer
                            _civilianDriver.Task.LeaveVehicle(_civilianVehicle, LeaveVehicleFlags.None);

                            // Wait for civilian to exit vehicle
                            Wait(1000);

                            // Calculate position in front of officer (near passenger side of civilian car)
                            Vector3 officerPos = SheriffPed.Position;
                            Vector3 walkToPos = officerPos + (officerPos - _civilianVehicle.Position).Normalized * 1f;

                            // Have civilian walk to officer
                            _civilianDriver.Task.GoTo(walkToPos, false);

                            // Wait for civilian to get near officer
                            if (_civilianDriver.Position.DistanceTo(walkToPos) <= 2f || Elapsed(4000))
                            {
                                CreatePatDown();
                            }
                        }
                    }
                    break;

                case StopState.PatDown:
                    // Pat down in progress - wait for sequence to complete (approx 7-8 seconds)
                    if (_patDownAnimStage == 6 && Elapsed(2000)) // Give extra 2 seconds after last animation
                    {
                        // Clear animations
                        SheriffPed.Task.ClearAll();
                        _civilianDriver.Task.ClearAll();

                        // Civilian returns to car
                        _civilianDriver.Task.EnterVehicle(_civilianVehicle, VehicleSeat.Driver, -1, 2f, 1);
                        _stateStartTime = Game.GameTime;
                        _state = StopState.CivilianReturningToCar;
                    }
                    break;

                case StopState.CivilianReturningToCar:
                    // Wait for civilian to get back in car
                    if (_civilianDriver.IsInVehicle(_civilianVehicle) || Elapsed(5000))
                    {
                        // Officer goes back to passenger window position
                        Vector3 finalWindowPoint = _civilianVehicle.Position + (_civilianVehicle.RightVector * 1.4f) + (_civilianVehicle.ForwardVector * 0.8f);
                        SheriffPed.Task.GoTo(finalWindowPoint);
                        _stateStartTime = Game.GameTime;
                        _state = StopState.OfficerWaiting;
                    }
                    break;

                case StopState.OfficerWaiting:
                    if (SheriffPed.Position.DistanceTo(_civilianVehicle.Position + (_civilianVehicle.RightVector * 1.4f) + (_civilianVehicle.ForwardVector * 0.8f)) <= 1.5f || Elapsed(5000))
                    {
                        // Officer waits at window for 10 seconds
                        SheriffPed.Task.TurnTo(_civilianVehicle);

                        // Use standing animation while waiting
                        const string standDict = "amb@world_human_stand_guard@male@base";
                        Function.Call(Hash.REQUEST_ANIM_DICT, standDict);
                        if (Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, standDict))
                            SheriffPed.Task.PlayAnimation(standDict, "base", 8f, -1, AnimationFlags.Loop);
                        else
                            SheriffPed.Task.StandStill(10000);

                        _stateStartTime = Game.GameTime;
                        _state = StopState.OfficerReturningToCar;
                    }
                    break;

                case StopState.OfficerReturningToCar:
                    if (Elapsed(10000)) // Wait at window for 10 seconds
                    {
                        SheriffPed.Task.ClearAll();
                        SheriffPed.Task.GoTo(SheriffVehicle.Position);
                        _stateStartTime = Game.GameTime;
                        _state = StopState.GoBackToCar; // Reuse the original GoBackToCar state
                    }
                    break;

                case StopState.Cleanup:
                    Cleanup();
                    break;
            }
        }

        // Helper method to set up the pat down
        private void CreatePatDown()
        {
            // Calculate position right next to the officer, in front of civilian car
            // Position civilian right in front of the officer (very close)
            _patDownPoint = SheriffPed.Position + (SheriffPed.ForwardVector * 0.5f);

            // Civilian walks to the pat down point
            _civilianDriver.Task.GoTo(_patDownPoint, false);

            // Officer turns to face where civilian will be
            SheriffPed.Task.TurnTo(_patDownPoint);

            // Wait for civilian to get close
            if (_civilianDriver.Position.DistanceTo(_patDownPoint) <= 1f || Elapsed(3000))
            {
                // Make them face each other exactly
                Vector3 officerToCivilian = _civilianDriver.Position - SheriffPed.Position;
                Vector3 civilianToOfficer = SheriffPed.Position - _civilianDriver.Position;

                // Calculate headings
                float officerHeading = (float)Math.Atan2(officerToCivilian.Y, officerToCivilian.X) * (180f / (float)Math.PI) - 90f;
                float civilianHeading = (float)Math.Atan2(civilianToOfficer.Y, civilianToOfficer.X) * (180f / (float)Math.PI) - 90f;

                // Normalize headings
                if (officerHeading < 0) officerHeading += 360f;
                if (civilianHeading < 0) civilianHeading += 360f;

                // Set headings
                SheriffPed.Heading = officerHeading;
                _civilianDriver.Heading = civilianHeading;

                // Make sure they're positioned correctly (officer slightly to the side of civilian)
                Vector3 finalOfficerPos = _civilianDriver.Position + (_civilianDriver.RightVector * -0.3f);
                finalOfficerPos.Z = SheriffPed.Position.Z; // Keep same height

                // Warp officer to correct position
                Function.Call(Hash.SET_ENTITY_COORDS, SheriffPed.Handle,
                             finalOfficerPos.X, finalOfficerPos.Y, finalOfficerPos.Z,
                             false, false, false, false);

                // Warp civilian slightly closer if needed
                Vector3 finalCivilianPos = SheriffPed.Position + (SheriffPed.ForwardVector * 0.8f);
                finalCivilianPos.Z = _civilianDriver.Position.Z;

                // Optional: Adjust civilian position to be right in front of officer
                Function.Call(Hash.SET_ENTITY_COORDS, _civilianDriver.Handle,
                             finalCivilianPos.X, finalCivilianPos.Y, finalCivilianPos.Z,
                             false, false, false, false);

                // Play pat down animations
                PlayPatDownAnimations();

                _stateStartTime = Game.GameTime;
                _state = StopState.PatDown;
            }
        }

        // Method to play the complete pat down animation sequence
        private void PlayPatDownAnimations()
        {
            // Start with loading box animation for officer
            string loadBoxDict = "anim@heists@load_box";
            Function.Call(Hash.REQUEST_ANIM_DICT, loadBoxDict);
            if (Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, loadBoxDict))
            {
                SheriffPed.Task.PlayAnimation(loadBoxDict, "idle", 8f, -1, AnimationFlags.Loop);
            }

            // Civilian plays yoga pose (hands up)
            string yogaDict = "missfam5_yoga";
            Function.Call(Hash.REQUEST_ANIM_DICT, yogaDict);
            if (Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, yogaDict))
            {
                _civilianDriver.Task.PlayAnimation(yogaDict, "start_pose", 8f, -1, AnimationFlags.Loop);
            }

            // Track animation stage for switching
            _patDownAnimStage = 1; // Start with stage 1
            _patDownAnimStartTime = Game.GameTime;
        }

        // Method to update the pat down animation sequence
        private void UpdatePatDownState()
        {
            switch (_patDownAnimStage)
            {
                case 1: // Initial search animation
                    if (Game.GameTime - _patDownAnimStartTime >= 2000) // After 2 seconds
                    {
                        // Switch to carrying animation
                        string carryDict = "anim@heists@box_carry@";
                        Function.Call(Hash.REQUEST_ANIM_DICT, carryDict);
                        if (Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, carryDict))
                        {
                            SheriffPed.Task.PlayAnimation(carryDict, "idle", 8f, -1, AnimationFlags.Loop);
                        }
                        _patDownAnimStage = 2;
                        _patDownAnimStartTime = Game.GameTime;
                    }
                    break;

                case 2: // Carry animation
                    if (Game.GameTime - _patDownAnimStartTime >= 1500) // After 1.5 seconds
                    {
                        // Switch to right leg check
                        string bootDict = "missbigscore2aig_7@driver";
                        Function.Call(Hash.REQUEST_ANIM_DICT, bootDict);
                        if (Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, bootDict))
                        {
                            SheriffPed.Task.PlayAnimation(bootDict, "boot_r_loop", 8f, -1, AnimationFlags.Loop);
                        }
                        _patDownAnimStage = 3;
                        _patDownAnimStartTime = Game.GameTime;
                    }
                    break;

                case 3: // Right leg check
                    if (Game.GameTime - _patDownAnimStartTime >= 1000) // After 1 second
                    {
                        // Civilian switches to outro pose
                        string miniYogaDict = "mini@yoga";
                        Function.Call(Hash.REQUEST_ANIM_DICT, miniYogaDict);
                        if (Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, miniYogaDict))
                        {
                            _civilianDriver.Task.PlayAnimation(miniYogaDict, "outro_2", 8f, -1, AnimationFlags.Loop);
                        }
                        _patDownAnimStage = 4;
                        _patDownAnimStartTime = Game.GameTime;
                    }
                    break;

                case 4: // Civilian outro pose
                    if (Game.GameTime - _patDownAnimStartTime >= 800) // After 0.8 seconds
                    {
                        // Switch to left leg check
                        string bootDict = "missbigscore2aig_7@driver";
                        Function.Call(Hash.REQUEST_ANIM_DICT, bootDict);
                        if (Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, bootDict))
                        {
                            SheriffPed.Task.PlayAnimation(bootDict, "boot_l_loop", 8f, -1, AnimationFlags.Loop);
                        }
                        _patDownAnimStage = 5;
                        _patDownAnimStartTime = Game.GameTime;
                    }
                    break;

                case 5: // Left leg check
                    if (Game.GameTime - _patDownAnimStartTime >= 1000) // After 1 second
                    {
                        // Civilian final outro pose
                        string miniYogaDict = "mini@yoga";
                        Function.Call(Hash.REQUEST_ANIM_DICT, miniYogaDict);
                        if (Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, miniYogaDict))
                        {
                            _civilianDriver.Task.PlayAnimation(miniYogaDict, "outro_2", 8f, -1, AnimationFlags.Loop);
                        }
                        _patDownAnimStage = 6; // Pat down complete
                        _patDownAnimStartTime = Game.GameTime;
                    }
                    break;

                case 6: // Pat down complete
                    // Animation sequence is finished
                    // The timer in the main state will handle the next steps
                    break;
            }
        }

        private void DeleteAllTrafficStopEntities()
        {
            if (_civilianVehicle != null && _civilianVehicle.Exists())
                _civilianVehicle.Delete();

            if (_civilianDriver != null && _civilianDriver.Exists())
                _civilianDriver.Delete();

            if (SheriffPed != null && SheriffPed.Exists())
                SheriffPed.Delete();

            if (SheriffVehicle != null && SheriffVehicle.Exists())
                SheriffVehicle.Delete();
        }

        private void Cleanup()
        {
            IsActive = false;
        }
    } // TrafficStop
} // AmbientAiPolice