namespace splat;

using Godot;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// Spawns and tracks physics-driven vehicles, and feeds road wear from actual
/// traffic throughput. Destinations are demand vectors between zone edges; the
/// simulation emerges from physics, not scripted paths.
/// </summary>
public partial class TrafficManager : Node
{
    [Export] public PackedScene VehicleScene { get; set; } = default!;
    [Export] public NodePath VehicleRootPath { get; set; } = new("../World/Vehicles");
    [Export] public float SpawnInterval { get; set; } = 4f;
    [Export] public int MaxVehicles { get; set; } = 20;
    [Export] public float RoadBounds { get; set; } = 100f;

    private readonly List<Vehicle> _vehicles = new();
    private readonly List<Road> _roads = new();
    private float _spawnTimer;
    private Node3D _vehicleRoot = default!;

    /// <summary>Emitted when a vehicle is spawned (for HUD/debug).</summary>
    [Signal] public delegate void VehicleSpawnedEventHandler(Vehicle vehicle);

    /// <summary>Emitted when a vehicle reaches its destination and despawns.</summary>
    [Signal] public delegate void VehicleDespawnedEventHandler(Vehicle vehicle);

    public IReadOnlyList<Vehicle> Vehicles => _vehicles;
    public IReadOnlyList<Road> Roads => _roads;

    public override void _Ready()
    {
        _vehicleRoot = GetNode<Node3D>(VehicleRootPath);

        var bm = FindManager<BuildingManager>();
        if (bm != null)
            bm.BuildingsChanged += _ => { }; // ensure manager order independent
    }

    public void RegisterRoad(Road road)
    {
        if (!_roads.Contains(road))
            _roads.Add(road);
    }

    public void UnregisterRoad(Road road)
        => _roads.Remove(road);

    public override void _Process(double delta)
    {
        _spawnTimer += (float)delta;
        if (_spawnTimer >= SpawnInterval && _vehicles.Count < MaxVehicles)
        {
            _spawnTimer = 0f;
            SpawnVehicle();
        }

        // Despawn arrived vehicles.
        foreach (var v in _vehicles.ToList())
        {
            if (v.GlobalPosition.DistanceTo(v.Destination) < 1f)
            {
                _vehicles.Remove(v);
                EmitSignal(SignalName.VehicleDespawned, v);
                v.QueueFree();
            }
        }

        // Feed wear from vehicles currently over a road.
        foreach (var v in _vehicles)
            ApplyRoadWear(v);
    }

    private void SpawnVehicle()
    {
        if (VehicleScene == null)
            return;

        var vehicle = VehicleScene.Instantiate<Vehicle>();
        vehicle.Data = (VehicleData)vehicle.Data.Duplicate(true);
        vehicle.Position = RandomEdgePoint();
        vehicle.Destination = RandomEdgePoint();
        _vehicleRoot.AddChild(vehicle);
        _vehicles.Add(vehicle);
        EmitSignal(SignalName.VehicleSpawned, vehicle);
    }

    private void ApplyRoadWear(Vehicle v)
    {
        foreach (var road in _roads)
        {
            // Cheap AABB-ish check: vehicle within road's XZ footprint.
            var local = road.ToLocal(v.GlobalPosition);
            if (Mathf.Abs(local.X) < 2f && Mathf.Abs(local.Z) < 6f)
            {
                road.RegisterTraffic(passes: 1f, wet: false);
                v.SetRoadQuality(road.QualityFactor());
                break;
            }
        }
    }

    private Vector3 RandomEdgePoint()
    {
        var r = RoadBounds * 0.45f;
        return new Vector3((float)GD.RandRange(-r, r), 0.75f, (float)GD.RandRange(-r, r));
    }

    private static T? FindManager<T>() where T : Node
        => (T)((SceneTree)Engine.GetMainLoop()).Root
            .FindChild(typeof(T).Name, owned: true, recursive: true);
}
