using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class NukeBall : BallBase
{
    public AudioClip criticalSound;

    public AudioClip explosionSound;

    public GameObject explosionEffectPrefab;

    public WorldFiller worldFiller;

    // Tracks escalating danger/critical phases for audio cues.
    private int criticalState = 0;

    private float criticalTime = 0.5f;

    private float criticalTimer = 0.0f;

    // Initial resting height the ball bounces back toward after a floor hit.
    private float height;

    private Collider2D selfCollider;

    private bool isBouncingToHeight = false;

    private float bounceStartY = 0.0f;

    private float fallTargetY = 0.0f;

    private float bounceTimer = 0.0f;

    private float bounceDuration = 1.00f;

    private bool exploding = false;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        // NukeBall is not affected by magnets, unlike other balls.
        affectedByMagnet = false;

        // Cache frequently used components once.
        audioSource = GetComponent<AudioSource>();
        animator = GetComponent<Animator>();
        selfCollider = GetComponent<Collider2D>();

        // Start with a small random horizontal drift.
        speed = new Vector2(Random.Range(-0.09f, 0.09f), 0.0f);
        height = GetComponent<Transform>().position.y;
        fallTargetY = height;

        // Find the closest valid floor point under the current position.
        UpdateFallTargetFromCurrentPosition();

        audioSource.PlayOneShot(spawnSound);
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        if (exploding)
        {
            return;
        }

        AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);

        if (stateInfo.IsName("NuclearBallBounce")) {
            transform.Translate(speed);

            Vector3 currentPosition = transform.position;

            if (isBouncingToHeight)
            {
                bounceTimer += Time.fixedDeltaTime;

                float t = Mathf.Clamp01(bounceTimer / bounceDuration);
                // Ease-out ascent: fast start, gentle finish near target height.
                float easedT = 1.0f - Mathf.Pow(1.0f - t, 2.0f);

                currentPosition.y = Mathf.Lerp(bounceStartY, height, easedT);

                if (t >= 1.0f)
                {
                    // Reached top of bounce arc; resume falling phase.
                    isBouncingToHeight = false;
                    bounceTimer = 0.0f;
                    currentPosition.y = height;
                    UpdateFallTargetFromCurrentPosition();
                }
            }
            else
            {
                bounceTimer += Time.fixedDeltaTime;

                float t = Mathf.Clamp01(bounceTimer / bounceDuration);
                // Ease-in descent: gentle start, faster approach to floor.
                float easedT = t * t;

                currentPosition.y = Mathf.Lerp(height, fallTargetY, easedT);
            }

            transform.position = currentPosition;
        }
    }

    void Update()
    {
        // Set critical state.
        var area = worldFiller != null ? worldFiller.GetAreaPercentOfFreeRegion(transform.position) : -1.0f;

        if (area == -1.0f || exploding)
        {
            return;
        }

        criticalState = (area < 0.12f) ? 3 : (area < 0.2f) ? 2 : (area < 0.4f) ? 1 : 0;

        // State 1 beeps every 0.5s, state 2 beeps every 0.25s.
        criticalTime = (criticalState == 2) ? 0.5f : 1.0f;

        // If in a critical state, increment the critical timer and play the
        // critical sound if the timer exceeds the critical time.
        if (criticalState == 1 || criticalState == 2)
        {
            criticalTimer += Time.deltaTime;

            if (criticalTimer >= criticalTime)
            {
                audioSource.PlayOneShot(criticalSound);

                // Keep fractional overflow so cadence stays stable across frame rates.
                criticalTimer -= criticalTime;
            }
        }
        else if (criticalState == 3)
        {
            audioSource.PlayOneShot(explosionSound);

            var balls = worldFiller != null ? 
                worldFiller.GetBallsInSameFreeRegion(transform.position) :
                new Queue<GameObject>();

            foreach (var ball in balls)
            {
                ball.GetComponent<BallBase>()?.StopBall();

                if (explosionEffectPrefab != null)
                {
                    Instantiate(explosionEffectPrefab, ball.transform.position, Quaternion.identity);
                }
            }

            exploding = true;

            StartCoroutine(DestroyAndFillRegion(balls));
        }
        else
        {
            criticalTimer = 0.0f;
        }
    }

    void OnCollisionEnter2D(Collision2D collision)
    {
        // NukeBall should bounce off walls only. It cannot collide with other balls, not even other NukeBalls.
        if (collision.gameObject.CompareTag("Wall"))
        {
            ContactPoint2D contact = collision.GetContact(0);

            // Reflect horizontal movement from wall surfaces.
            speed = Vector2.Reflect(speed, contact.normal);
            speed.y = 0.0f;

            audioSource.PlayOneShot(bounceSound);

            // A floor hit (normal pointing upward) starts an ease-out bounce back to initial height.
            if (contact.normal.y > 0.5f)
            {
                isBouncingToHeight = true;
                bounceTimer = 0.0f;

                // Use exact contact point as bounce origin so visual contact feels grounded.
                bounceStartY = contact.point.y;
                fallTargetY = bounceStartY;

                Vector3 currentPosition = transform.position;
                currentPosition.y = bounceStartY;
                transform.position = currentPosition;
            }
            else if (contact.normal.y <= -0.5f)
            {
                isBouncingToHeight = false;
                bounceTimer = 0.0f;
                height = contact.point.y - 0.01f;
            }
        }
        else
        {
            Physics2D.IgnoreCollision(collision.collider, GetComponent<Collider2D>(), true);
        }
    }

    private void UpdateFallTargetFromCurrentPosition()
    {
        // Default to current y in case no valid floor is found.
        fallTargetY = transform.position.y;

        // Probe straight down and choose nearest upward-facing wall surface.
        RaycastHit2D[] hits = Physics2D.RaycastAll(transform.position, Vector2.down, 100.0f);
        float closestDistance = float.MaxValue;

        foreach (RaycastHit2D hit in hits)
        {
            if (hit.collider == null || hit.collider == selfCollider)
            {
                continue;
            }

            if (!hit.collider.CompareTag("Wall"))
            {
                continue;
            }

            if (hit.normal.y <= 0.5f)
            {
                continue;
            }

            if (hit.distance < closestDistance)
            {
                closestDistance = hit.distance;
                fallTargetY = hit.point.y;
            }
        }
    }

    private IEnumerator DestroyAndFillRegion(Queue<GameObject> balls)
    {
        yield return new WaitForSeconds(1.4f);

        foreach (var ball in balls)
        {
            Destroy(ball);
        }

        var freeRegion = worldFiller != null ? 
            worldFiller.GetFreeRegionOfPosition(transform.position) :
            null;

        if (freeRegion != null)
        {
            worldFiller.FillFreeRegion(freeRegion.Value);
        }

        Destroy(gameObject);
    }
}
