using UnityEngine;

public class AIHovercarController : BaseHovercarController
{
    [Header("Hover Settings")]
    public float hoverHeight = 3.0f;
    public float positionAdjustmentSpeed = 10.0f;
    public float raycastDistance = 10.0f;
    
    [Tooltip("Layer mask for smooth terrain.")]
    public LayerMask smoothTerrainLayer;
    [Tooltip("Layer mask for bumpy terrain.")]
    public LayerMask bumpyTerrainLayer;

    [Header("Position Adjustment Speeds")]
    [Tooltip("Adjustment speed when hovering over smooth terrain.")]
    public float smoothPositionAdjustmentSpeed = 10.0f;
    [Tooltip("Adjustment speed when hovering over bumpy terrain.")]
    public float bumpyPositionAdjustmentSpeed = 5.0f;
    [Header("Movement Settings")]
    public float movementSpeed = 10.0f;  // Max speed
    public float acceleration = 5.0f;    // How quickly to accelerate
    public float deceleration = 7.0f;    // How quickly to decelerate
    public float rotationSpeed = 100.0f;

    [Header("Checkpoint Navigation")]
    [Tooltip("Distance from a checkpoint at which the AI switches to the next one.")]
    public float checkpointThreshold = 5.0f;
    private int currentCheckpointIndex = 0;

    [Header("Random Steering Settings")]
    [Tooltip("Probability per second that a random steering offset is applied (0 = never, 1 = every second on average).")]
    public float randomSteeringProbability = 0.2f;
    [Tooltip("Maximum random steering offset added (in normalized input units; e.g., 0.2 means up to ±20% extra steering).")]
    public float randomSteeringMaxOffset = 0.2f;

    [Header("Slowdown Settings")]
    [Tooltip("Distance before a checkpoint at which the AI begins to slow down.")]
    public float slowdownDistance = 10f;
    [Tooltip("Strength of the slowdown (0 = no slowdown, 1 = full slowdown to minimum throttle at the checkpoint).")]
    public float slowdownStrength = 0.5f;

    [Header("Reset Settings")]
    [Tooltip("Time (in seconds) without track contact before resetting the vehicle.")]
    public float timeBeforeReset = 3.0f;

    [Header("Track Orientation Settings")]
    [Tooltip("Distance to search downward for track (used to align the vehicle’s bottom).")]
    public float trackRaycastDistance = 10.0f;
    [Tooltip("Tag used on the track geometry.")]
    public string trackTag = "Track";

    [Header("Avoidance Settings")]
    [Tooltip("Radius within which the AI will try to avoid other racers.")]
    public float avoidanceRadius = 5.0f;
    [Tooltip("Strength of the steering adjustment to avoid collisions.")]
    public float avoidanceSteeringStrength = 0.5f;
    [Tooltip("Throttle multiplier applied when avoiding other racers (0 to 1, where lower means more slowdown).")]
    public float avoidanceSlowdownMultiplier = 0.5f;
    [Tooltip("Tag used to identify other racers for avoidance.")]
    public string racerTag = "Racer";

    private float currentSpeed = 0.0f;
    private Rigidbody rb;
    private RacerProgress racerProgress;

    // Timer for lost ground/track contact
    private float noContactTimer = 0.0f;
    // Flag indicating if the hover raycast hit the terrain/track this physics frame
    private bool groundContact = false;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
        rb.useGravity = false;// Disable gravity for hover stability

        racerProgress = GetComponent<RacerProgress>();
        if (racerProgress == null)
        {
            Debug.LogWarning("No RacerProgress component found on this vehicle!");
        }
    }

    void FixedUpdate()
    {
        HandleHovering();
        HandleMovement();

        // If no ground/track contact is detected, count up the timer.
        if (!groundContact)
        {
            noContactTimer += Time.fixedDeltaTime;
            if (noContactTimer >= timeBeforeReset)
            {
                ResetToCheckpoint();
                noContactTimer = 0.0f; // Reset timer after repositioning.
            }
        }
        else
        {
            // Reset the timer when ground/track contact is re-established.
            noContactTimer = 0.0f;
        }
    }

    /// <summary>
    /// Hover logic: cast a ray downward, interpolate the hit point and normal on the terrain,
    /// and adjust the vehicle's position and rotation so it hovers along the track.
    /// Also sets the groundContact flag for reset timing.
    /// </summary>
    void HandleHovering()
    {
        RaycastHit hit;
        Vector3 rayOrigin = transform.position;

        // Combine both terrain layers for the raycast.
        LayerMask combinedLayer = smoothTerrainLayer | bumpyTerrainLayer;

        // Cast a ray downward to detect the terrain.
        if (Physics.Raycast(rayOrigin, -transform.up, out hit, raycastDistance, combinedLayer))
        {
            groundContact = true;

            Mesh mesh = hit.collider.GetComponent<MeshFilter>().mesh;
            int triangleIndex = hit.triangleIndex;
            int vertex1Index = mesh.triangles[triangleIndex * 3 + 0];
            int vertex2Index = mesh.triangles[triangleIndex * 3 + 1];
            int vertex3Index = mesh.triangles[triangleIndex * 3 + 2];

            Vector3 worldVertex1 = hit.collider.transform.TransformPoint(mesh.vertices[vertex1Index]);
            Vector3 worldVertex2 = hit.collider.transform.TransformPoint(mesh.vertices[vertex2Index]);
            Vector3 worldVertex3 = hit.collider.transform.TransformPoint(mesh.vertices[vertex3Index]);

            Vector3 interpolatedPoint = worldVertex1 * hit.barycentricCoordinate.x +
                                        worldVertex2 * hit.barycentricCoordinate.y +
                                        worldVertex3 * hit.barycentricCoordinate.z;

            Vector3 localNormal1 = mesh.normals[vertex1Index];
            Vector3 localNormal2 = mesh.normals[vertex2Index];
            Vector3 localNormal3 = mesh.normals[vertex3Index];
            Vector3 worldNormal1 = hit.collider.transform.TransformDirection(localNormal1);
            Vector3 worldNormal2 = hit.collider.transform.TransformDirection(localNormal2);
            Vector3 worldNormal3 = hit.collider.transform.TransformDirection(localNormal3);
            Vector3 interpolatedNormal = worldNormal1 * hit.barycentricCoordinate.x +
                                         worldNormal2 * hit.barycentricCoordinate.y +
                                         worldNormal3 * hit.barycentricCoordinate.z;
            interpolatedNormal.Normalize();
            // Choose the appropriate adjustment speed based on which terrain layer was hit.
            float currentAdjustmentSpeed = smoothPositionAdjustmentSpeed;
            if (IsInLayerMask(hit.collider.gameObject, bumpyTerrainLayer))
            {
                currentAdjustmentSpeed = bumpyPositionAdjustmentSpeed;
            }

            // Compute the target hover position.
            Vector3 targetPosition = interpolatedPoint + interpolatedNormal * hoverHeight;
            transform.position = Vector3.Lerp(transform.position, targetPosition, Time.fixedDeltaTime * positionAdjustmentSpeed);

            Quaternion targetRotation = Quaternion.FromToRotation(transform.up, interpolatedNormal) * transform.rotation;
            rb.rotation = Quaternion.Slerp(rb.rotation, targetRotation, Time.fixedDeltaTime * 5.0f);
        }
        else
        {
            groundContact = false;
        }
    }

    /// <summary>
    /// Simulates input by computing throttle and steering based on the AI's orientation relative to the next checkpoint.
    /// New additions:
    /// - Random steering offset.
    /// - Throttle reduction when turning sharply and when approaching a checkpoint.
    /// - Avoidance: steer away from and slow down for nearby racers.
    /// </summary>
    void HandleMovement()
    {
        // Default simulated inputs.
        float verticalInput = 1f; // full throttle by default
        float horizontalInput = 0f;

        if (checkpoints != null && checkpoints.Length > 0)
        {
            Transform targetCheckpoint = checkpoints[currentCheckpointIndex];
            Vector3 toCheckpoint = targetCheckpoint.position - transform.position;
            float distanceToCheckpoint = toCheckpoint.magnitude;
            Vector3 toCheckpointNormalized = toCheckpoint.normalized;

            // If the target checkpoint is behind the vehicle, switch to the next one immediately.
            if (Vector3.Dot(transform.forward, toCheckpointNormalized) < 0)
            {
                currentCheckpointIndex = (currentCheckpointIndex + 1) % checkpoints.Length;
            }
            else
            {
                // Compute steering input based on the angle between the vehicle's forward and the direction to the checkpoint.
                Vector3 projectedForward = Vector3.ProjectOnPlane(transform.forward, transform.up).normalized;
                Vector3 projectedTarget = Vector3.ProjectOnPlane(toCheckpointNormalized, transform.up).normalized;
                float angle = Vector3.SignedAngle(projectedForward, projectedTarget, transform.up);
                horizontalInput = Mathf.Clamp(angle / 45f, -1f, 1f);

                // Add a random steering offset occasionally.
                if (Random.value < randomSteeringProbability * Time.fixedDeltaTime)
                {
                    float randomOffset = Random.Range(-randomSteeringMaxOffset, randomSteeringMaxOffset);
                    horizontalInput += randomOffset;
                    horizontalInput = Mathf.Clamp(horizontalInput, -1f, 1f);
                }

                // Reduce throttle when turning sharply.
                verticalInput = Mathf.Clamp(1f - (Mathf.Abs(horizontalInput) * 0.5f), 0f, 1f);

                // Additional slowdown when approaching a checkpoint.
                if (distanceToCheckpoint < slowdownDistance)
                {
                    float slowdownFactor = Mathf.Lerp(1 - slowdownStrength, 1f, distanceToCheckpoint / slowdownDistance);
                    verticalInput *= slowdownFactor;
                }

                // Switch to the next checkpoint when close enough.
                if (distanceToCheckpoint < checkpointThreshold)
                {
                    currentCheckpointIndex = (currentCheckpointIndex + 1) % checkpoints.Length;
                }
            }
        }

        // --- Avoidance Logic ---
        // Look for nearby racers and, if any are detected, adjust steering and throttle.
        Collider[] nearbyColliders = Physics.OverlapSphere(transform.position, avoidanceRadius);
        Vector3 avoidanceVector = Vector3.zero;
        int avoidanceCount = 0;
        foreach (Collider col in nearbyColliders)
        {
            if (col.gameObject != gameObject && col.CompareTag(racerTag))
            {
                Vector3 diff = transform.position - col.transform.position;
                if (diff.magnitude > 0)
                {
                    // Closer objects contribute more.
                    avoidanceVector += diff.normalized / diff.magnitude;
                    avoidanceCount++;
                }
            }
        }
        if (avoidanceCount > 0)
        {
            avoidanceVector /= avoidanceCount;
            // Project onto the horizontal plane.
            avoidanceVector = Vector3.ProjectOnPlane(avoidanceVector, transform.up).normalized;
            // Determine steering adjustment from avoidance.
            float avoidanceAngle = Vector3.SignedAngle(transform.forward, avoidanceVector, transform.up);
            float avoidanceInput = Mathf.Clamp(avoidanceAngle / 45f, -1f, 1f) * avoidanceSteeringStrength;
            horizontalInput += avoidanceInput;
            horizontalInput = Mathf.Clamp(horizontalInput, -1f, 1f);
            // Slow down throttle when avoiding.
            verticalInput *= avoidanceSlowdownMultiplier;
        }
        // --- End Avoidance Logic ---

        // Apply movement: accelerate/decelerate toward the target speed, then update velocity and angular velocity.
        float targetSpeed = verticalInput * movementSpeed;
        if (!canAccelerate) {
            targetSpeed = 0;
        }
        if (verticalInput != 0)
        {
            currentSpeed = Mathf.Lerp(currentSpeed, targetSpeed, Time.fixedDeltaTime * acceleration);
        }
        else
        {
            currentSpeed = Mathf.Lerp(currentSpeed, 0, Time.fixedDeltaTime * deceleration);
        }

        rb.linearVelocity = transform.forward * currentSpeed;
        float turn = horizontalInput * rotationSpeed;
        rb.angularVelocity = transform.up * turn * Mathf.Deg2Rad;
    }

    /// <summary>
    /// Resets the AI's position to the last checkpoint it passed and reorients it so that:
    /// - Its front faces toward the nearest (other) checkpoint.
    /// - Its bottom is aligned with the nearby track surface (determined via a raycast and track tag).
    /// </summary>
    void ResetToCheckpoint()
    {
    // Ensure checkpoints and progress tracking are valid.
    if (checkpoints == null || checkpoints.Length == 0)
    {
        Debug.LogWarning("No checkpoints have been assigned in the Inspector.");
        return;
    }
    if (racerProgress == null)
    {
        Debug.LogWarning("RacerProgress component is missing. Cannot reset to checkpoint.");
        return;
    }
    
    int cpIndex = racerProgress.currentCheckpointIndex;
    if (cpIndex < 0 || cpIndex >= checkpoints.Length)
    {
        Debug.LogWarning("Invalid checkpoint index in RacerProgress!");
        return;
    }

    // Get the last checkpoint.
    Transform lastCheckpoint = checkpoints[cpIndex];

    // Determine the next checkpoint in sequence.
    int nextCheckpointIndex = (cpIndex + 1) % checkpoints.Length;
    Transform nextCheckpoint = checkpoints[nextCheckpointIndex];

    // Determine desired forward direction toward the next checkpoint.
    Vector3 desiredForward = (nextCheckpoint.position - lastCheckpoint.position).normalized;

        // Determine the track’s surface normal to align the vehicle’s bottom.
        // Raycast downward from a point just above the checkpoint.
        Vector3 desiredUp = Vector3.up; // Fallback
        RaycastHit hit;
        Vector3 rayOrigin = lastCheckpoint.position + Vector3.up * 1.0f;
        if (Physics.Raycast(rayOrigin, -Vector3.up, out hit, trackRaycastDistance))
        {
            if (hit.collider.CompareTag(trackTag))
            {
                // Use the inverted hit normal so the vehicle’s bottom (–transform.up) is flush with the track.
                desiredUp = -hit.normal;
            }
        }

        // Adjust desired forward so that it is perpendicular to desired up.
        desiredForward = Vector3.ProjectOnPlane(desiredForward, desiredUp).normalized;

        Quaternion desiredRotation = Quaternion.LookRotation(desiredForward, desiredUp);

        // Reset position and orientation.
        transform.position = lastCheckpoint.position;
        transform.rotation = desiredRotation;

    // Clear any existing velocity.
        currentSpeed = 0f;
        rb.linearVelocity = Vector3.zero;
        rb.angularVelocity = Vector3.zero;

        Debug.Log("AI reset to checkpoint: " + lastCheckpoint.name +
                  (nextCheckpoint != null ? " with front facing: " + nextCheckpoint.name : ""));
    }


    // Helper method to determine if a GameObject's layer is in a given LayerMask.
    private bool IsInLayerMask(GameObject obj, LayerMask mask)
    {
        return ((mask.value & (1 << obj.layer)) != 0);
    }
}
