namespace splat;

using Godot;

/// <summary>
/// A vehicle: a <see cref="CharacterBody3D"/> driven by physics. It seeks a
/// destination via a steering direction and is throttled by the road quality it
/// is currently on. Collisions with other vehicles/buildings are resolved by the
/// physics engine (real momentum, real consequences).
/// </summary>
public partial class Vehicle : CharacterBody3D
{
    [Export] public VehicleData Data { get; set; } = default!;

    /// <summary>World-space destination the vehicle is currently seeking.</summary>
    public Vector3 Destination { get; set; }

    /// <summary>Current forward speed in m/s (read-only diagnostic).</summary>
    public float Speed { get; private set; }

    /// <summary>Emitted when this vehicle collides with something substantial.</summary>
    [Signal] public delegate void CrashedEventHandler(Vehicle vehicle, float impactSpeed);

    private float _qualityFactor = 1f;

    public override void _Ready()
    {
        CollisionLayer = 2u;   // Vehicles
        CollisionMask = 1u | 2u | 8u | 16u; // buildings, vehicles, roads, terrain

        if (GetNodeOrNull<CollisionShape3D>("CollisionShape3D") == null)
        {
            var shape = new CollisionShape3D();
            shape.Shape = new BoxShape3D { Size = new Vector3(2f, 1.5f, 4f) };
            shape.Position = new Vector3(0f, 0.75f, 0f);
            AddChild(shape);
        }

        if (GetNodeOrNull<MeshInstance3D>("MeshInstance3D") == null)
        {
            var mesh = new MeshInstance3D();
            var box = new BoxMesh { Size = new Vector3(2f, 1.5f, 4f) };
            mesh.Mesh = box;
            mesh.Position = new Vector3(0f, 0.75f, 0f);
            var mat = new StandardMaterial3D { AlbedoColor = Data?.BodyColor ?? Colors.Gray };
            mesh.MaterialOverride = mat;
            AddChild(mesh);
        }
    }

    public override void _PhysicsProcess(double delta)
    {
        if (Data == null)
            return;

        var dt = (float)delta;
        var toTarget = Destination - GlobalPosition;
        toTarget.Y = 0f;
        var dist = toTarget.Length();

        // Steer toward target; ease off as we arrive.
        if (dist > 0.5f)
        {
            var dir = toTarget.Normalized();
            var targetSpeed = Data.MaxSpeed * _qualityFactor;
            Speed = Mathf.MoveToward(Speed, targetSpeed, Data.Acceleration * dt);
            Velocity = dir * Speed;
            LookAt(GlobalPosition + dir, Vector3.Up);
        }
        else
        {
            Speed = Mathf.MoveToward(Speed, 0f, Data.Braking * dt);
            Velocity = Velocity with { X = 0f, Z = 0f };
        }

        var prevVel = Velocity;
        MoveAndSlide();

        // Detect a hard collision this frame (velocity killed by impact).
        if (prevVel.Length() > 3f && GetLastSlideCollision() != null)
        {
            var impact = prevVel.Length() - Velocity.Length();
            if (impact > 2f)
                EmitSignal(SignalName.Crashed, this, impact);
        }
    }

    /// <summary>Called by TrafficManager when the vehicle is on a given road.</summary>
    public void SetRoadQuality(float qualityFactor) => _qualityFactor = Mathf.Clamp(qualityFactor, 0.1f, 1f);
}
