using UnityEngine;
using System.Collections.Generic;

public class WorldFiller : MonoBehaviour
{
    public Player player;

    public SpriteRenderer gameBoardSpriteRenderer;

    public GameObject wallPrefab;

    [SerializeField]
    private float fallbackBarThicknessWorldUnits = 0.08f;

    [SerializeField]
    private float geometryEpsilon = 0.0001f;

    [SerializeField]
    private float wallPaddingPixelsPerSide = 1.0f;

    private Bounds gameBoardBounds;

    private Vector2?[] wallPositions;

    private int wallCount = 0;

    private Queue<GameObject> spawnedWalls = new Queue<GameObject>();

    private List<Rect> freeAreas = new List<Rect>();

    private float boardArea;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        gameBoardBounds = gameBoardSpriteRenderer.bounds;
        InitializeFreeAreas();
        ClearWalls();
    }

    public void ClearWalls()
    {
        wallPositions = new Vector2?[2];
        wallPositions[0] = null;
        wallPositions[1] = null;
        wallCount = 0;
    }

    public void ClearSpawnedWalls()
    {
        while (spawnedWalls.Count > 0)
        {
            GameObject wall = spawnedWalls.Dequeue();

            if (wall != null)
            {
                Destroy(wall);
            }
        }

        spawnedWalls.Clear();
    }

    public void SetWallPosition(Vector2 position)
    {
        if (wallCount >= wallPositions.Length)
        {
            Debug.LogError("Index out of bounds for wall positions.");
            return;
        }

        wallPositions[wallCount] = position;
        wallCount++;

        SpawnWall();
    }

    public void SpawnWall()
    {
        // If both wall positions are set, spawn a wall.
        if (wallPositions[0] != null && wallPositions[1] != null)
        {
            Vector2 wallPointA = wallPositions[0].Value;
            Vector2 wallPointB = wallPositions[1].Value;

            if (Vector2.Distance(wallPointA, wallPointB) <= geometryEpsilon)
            {
                Debug.LogWarning("Wall points are too close. Skipping wall spawn.");
                ClearWalls();
                return;
            }

            // Get all the balls in the scene.
            var ballSpawner = FindAnyObjectByType<BallSpawner>();
            var balls = ballSpawner != null ? ballSpawner.GetBalls() : null;

            bool isVerticalLine = Mathf.Abs(wallPointA.x - wallPointB.x) <= Mathf.Abs(wallPointA.y - wallPointB.y);
            float lineCenter = isVerticalLine ? (wallPointA.x + wallPointB.x) * 0.5f : (wallPointA.y + wallPointB.y) * 0.5f;
            float lineMin = isVerticalLine ? Mathf.Min(wallPointA.y, wallPointB.y) : Mathf.Min(wallPointA.x, wallPointB.x);
            float lineMax = isVerticalLine ? Mathf.Max(wallPointA.y, wallPointB.y) : Mathf.Max(wallPointA.x, wallPointB.x);

            float barThickness = GetCurrentBarThickness(isVerticalLine);
            float halfBarThickness = barThickness * 0.5f;

            Rect activeArea = FindActiveArea((wallPointA + wallPointB) * 0.5f, isVerticalLine, lineCenter, lineMin, lineMax);
            DetermineSideOccupancy(
                balls,
                activeArea,
                isVerticalLine,
                lineCenter,
                halfBarThickness,
                out bool sideAHasBall,
                out bool sideBHasBall
            );

            Rect targetWallRect;

            // Spawn scenarios:
            // 1) Both sides empty => fill the whole available region.
            // 2) Only one side empty => fill that free side plus the bar strip.
            // 3) Both sides occupied => spawn only along the wall line, with bar thickness.
            if (!sideAHasBall && !sideBHasBall)
            {
                targetWallRect = activeArea;
            }
            else if (sideAHasBall && sideBHasBall)
            {
                if (isVerticalLine)
                {
                    targetWallRect = Rect.MinMaxRect(
                        lineCenter - halfBarThickness,
                        lineMin,
                        lineCenter + halfBarThickness,
                        lineMax
                    );
                }
                else
                {
                    targetWallRect = Rect.MinMaxRect(
                        lineMin,
                        lineCenter - halfBarThickness,
                        lineMax,
                        lineCenter + halfBarThickness
                    );
                }
            }
            else
            {
                if (isVerticalLine)
                {
                    // Side A is left, side B is right for vertical lines.
                    targetWallRect = !sideAHasBall
                        ? Rect.MinMaxRect(activeArea.xMin, activeArea.yMin, lineCenter + halfBarThickness, activeArea.yMax)
                        : Rect.MinMaxRect(lineCenter - halfBarThickness, activeArea.yMin, activeArea.xMax, activeArea.yMax);
                }
                else
                {
                    // Side A is bottom, side B is top for horizontal lines.
                    targetWallRect = !sideAHasBall
                        ? Rect.MinMaxRect(activeArea.xMin, activeArea.yMin, activeArea.xMax, lineCenter + halfBarThickness)
                        : Rect.MinMaxRect(activeArea.xMin, lineCenter - halfBarThickness, activeArea.xMax, activeArea.yMax);
                }
            }

            if (!TryGetRectIntersection(targetWallRect, activeArea, out Rect clampedWallRect))
            {
                Debug.LogWarning("Could not create a valid wall rectangle inside the active free area.");
                ClearWalls();
                return;
            }

            GameObject wall = Instantiate(wallPrefab, Vector3.zero, Quaternion.identity);
            var wallRenderer = wall.GetComponentInChildren<SpriteRenderer>();

            if (wallRenderer == null)
            {
                Debug.LogError("Wall prefab is missing a SpriteRenderer in children.");
                Destroy(wall);
                ClearWalls();
                return;
            }

            Rect boardRect = Rect.MinMaxRect(
                gameBoardBounds.min.x,
                gameBoardBounds.min.y,
                gameBoardBounds.max.x,
                gameBoardBounds.max.y
            );

            float wallPaddingWorldUnits = PixelsToWorldUnits(wallPaddingPixelsPerSide, wallRenderer);
            Rect paddedWallRect = ExpandRectInsideBounds(clampedWallRect, wallPaddingWorldUnits, boardRect);

            ApplyWallTransform(wall.transform, wallRenderer, paddedWallRect);
            spawnedWalls.Enqueue(wall);
            var wallBounds = wallRenderer.bounds;

            UpdateFreeAreas(paddedWallRect);

            // Check if there are yummies, multipliers or the shark intersecting the spawned wall.
            // Yummies and multipliers are awarded to the player if they are intersected by the wall.
            // The shark is killed if it is intersected by the wall.
            var yummies = GameObject.FindGameObjectsWithTag("Yummy");
            var multipliers = GameObject.FindGameObjectsWithTag("Multiplier");
            var shark = GameObject.FindGameObjectWithTag("Shark");

            foreach (var yummy in yummies)
            {
                if (yummy != null && wallBounds.Intersects(yummy.GetComponent<SpriteRenderer>().bounds))
                {
                    // Award the yummy to the player.
                    YummyBase yummyBase = yummy.GetComponent<YummyBase>();

                    if (yummyBase != null)
                    {
                        yummyBase.AwardToPlayer(player);
                    }
                    else
                    {
                        Debug.LogError("YummyBase component not found on yummy object.");
                    }
                }
            }

            foreach (var multiplier in multipliers)
            {
                if (multiplier != null && wallBounds.Intersects(multiplier.GetComponent<SpriteRenderer>().bounds))
                {
                    // Award the multiplier to the player.
                    Multiplier multiplierComponent = multiplier.GetComponent<Multiplier>();

                    if (multiplierComponent != null)
                    {
                        multiplierComponent.AwardToPlayer(player);
                    }
                    else
                    {
                        Debug.LogError("Multiplier component not found on multiplier object.");
                    }
                }
            }

            if (shark != null && wallBounds.Intersects(shark.GetComponent<SpriteRenderer>().bounds))
            {
                // Kill the shark.
                shark.GetComponent<Shark>().KillShark();
            }

            // Destroy all bar segments after we are done.
            GameObject[] barSegments = GameObject.FindGameObjectsWithTag("Bar");

            foreach (GameObject barSegment in barSegments)
            {
                Destroy(barSegment);
            }

            // Let the player know that the wall has been spawned.
            int areaPercentage = boardArea > geometryEpsilon
                ? Mathf.RoundToInt((paddedWallRect.width * paddedWallRect.height / boardArea) * 100.0f)
                : 0;
            player.OnWallSpawned(areaPercentage);

            // Clear the pending wall line.
            ClearWalls();
        }
    }

    public void InitializeFreeAreas()
    {
        gameBoardBounds = gameBoardSpriteRenderer.bounds;

        Rect boardRect = Rect.MinMaxRect(
            gameBoardBounds.min.x,
            gameBoardBounds.min.y,
            gameBoardBounds.max.x,
            gameBoardBounds.max.y
        );

        freeAreas.Clear();
        freeAreas.Add(boardRect);
        boardArea = boardRect.width * boardRect.height;
    }

    private float GetCurrentBarThickness(bool isVerticalLine)
    {
        var barSegments = GameObject.FindGameObjectsWithTag("Bar");

        foreach (var barSegment in barSegments)
        {
            if (barSegment == null)
            {
                continue;
            }

            var spriteRenderer = barSegment.GetComponent<SpriteRenderer>();
            if (spriteRenderer == null)
            {
                continue;
            }

            float thickness = isVerticalLine
                ? spriteRenderer.bounds.size.x
                : spriteRenderer.bounds.size.y;

            if (thickness > geometryEpsilon)
            {
                return thickness;
            }
        }

        return fallbackBarThicknessWorldUnits;
    }

    private Rect FindActiveArea(
        Vector2 lineMidpoint,
        bool isVerticalLine,
        float lineCenter,
        float lineMin,
        float lineMax)
    {
        if (freeAreas.Count == 0)
        {
            InitializeFreeAreas();
        }

        for (int i = 0; i < freeAreas.Count; i++)
        {
            if (RectContainsWithTolerance(freeAreas[i], lineMidpoint))
            {
                return freeAreas[i];
            }
        }

        Rect lineRect = isVerticalLine
            ? Rect.MinMaxRect(lineCenter - geometryEpsilon, lineMin, lineCenter + geometryEpsilon, lineMax)
            : Rect.MinMaxRect(lineMin, lineCenter - geometryEpsilon, lineMax, lineCenter + geometryEpsilon);

        float bestOverlap = -1.0f;
        Rect fallback = freeAreas[0];

        for (int i = 0; i < freeAreas.Count; i++)
        {
            if (!TryGetRectIntersection(freeAreas[i], lineRect, out Rect overlap))
            {
                continue;
            }

            float overlapArea = overlap.width * overlap.height;
            if (overlapArea > bestOverlap)
            {
                bestOverlap = overlapArea;
                fallback = freeAreas[i];
            }
        }

        return fallback;
    }

    private void DetermineSideOccupancy(
        Queue<GameObject> balls,
        Rect activeArea,
        bool isVerticalLine,
        float lineCenter,
        float halfBarThickness,
        out bool sideAHasBall,
        out bool sideBHasBall)
    {
        sideAHasBall = false;
        sideBHasBall = false;

        if (balls == null)
        {
            return;
        }

        foreach (var ball in balls)
        {
            if (ball == null || !ball.activeInHierarchy)
            {
                continue;
            }

            Vector2 ballPosition = ball.transform.position;

            if (!RectContainsWithTolerance(activeArea, ballPosition))
            {
                continue;
            }

            if (isVerticalLine)
            {
                if (ballPosition.x < lineCenter - halfBarThickness)
                {
                    sideAHasBall = true;
                }
                else if (ballPosition.x > lineCenter + halfBarThickness)
                {
                    sideBHasBall = true;
                }
                else
                {
                    if (ballPosition.x <= lineCenter)
                    {
                        sideAHasBall = true;
                    }
                    else
                    {
                        sideBHasBall = true;
                    }
                }
            }
            else
            {
                if (ballPosition.y < lineCenter - halfBarThickness)
                {
                    sideAHasBall = true;
                }
                else if (ballPosition.y > lineCenter + halfBarThickness)
                {
                    sideBHasBall = true;
                }
                else
                {
                    if (ballPosition.y <= lineCenter)
                    {
                        sideAHasBall = true;
                    }
                    else
                    {
                        sideBHasBall = true;
                    }
                }
            }

            if (sideAHasBall && sideBHasBall)
            {
                return;
            }
        }
    }

    private void ApplyWallTransform(Transform wallTransform, SpriteRenderer wallRenderer, Rect targetRect)
    {
        Vector2 baseSize = wallRenderer.bounds.size;

        if (baseSize.x <= geometryEpsilon || baseSize.y <= geometryEpsilon)
        {
            Debug.LogError("Cannot scale wall: base sprite size is invalid.");
            return;
        }

        Vector3 currentScale = wallTransform.localScale;
        float scaleX = currentScale.x * (targetRect.width / baseSize.x);
        float scaleY = currentScale.y * (targetRect.height / baseSize.y);

        wallTransform.localScale = new Vector3(scaleX, scaleY, currentScale.z);
        wallTransform.position = new Vector3(targetRect.center.x, targetRect.center.y, wallTransform.position.z);
    }

    private void UpdateFreeAreas(Rect occupiedArea)
    {
        List<Rect> updatedAreas = new List<Rect>();

        for (int i = 0; i < freeAreas.Count; i++)
        {
            Rect freeArea = freeAreas[i];

            if (!TryGetRectIntersection(freeArea, occupiedArea, out Rect overlap))
            {
                updatedAreas.Add(freeArea);
                continue;
            }

            // Left remainder.
            AddValidRect(updatedAreas, Rect.MinMaxRect(freeArea.xMin, freeArea.yMin, overlap.xMin, freeArea.yMax));

            // Right remainder.
            AddValidRect(updatedAreas, Rect.MinMaxRect(overlap.xMax, freeArea.yMin, freeArea.xMax, freeArea.yMax));

            // Bottom remainder.
            AddValidRect(updatedAreas, Rect.MinMaxRect(overlap.xMin, freeArea.yMin, overlap.xMax, overlap.yMin));

            // Top remainder.
            AddValidRect(updatedAreas, Rect.MinMaxRect(overlap.xMin, overlap.yMax, overlap.xMax, freeArea.yMax));
        }

        freeAreas = updatedAreas;
    }

    private void AddValidRect(List<Rect> target, Rect candidate)
    {
        if (candidate.width <= geometryEpsilon || candidate.height <= geometryEpsilon)
        {
            return;
        }

        target.Add(candidate);
    }

    private bool TryGetRectIntersection(Rect a, Rect b, out Rect intersection)
    {
        float xMin = Mathf.Max(a.xMin, b.xMin);
        float xMax = Mathf.Min(a.xMax, b.xMax);
        float yMin = Mathf.Max(a.yMin, b.yMin);
        float yMax = Mathf.Min(a.yMax, b.yMax);

        if (xMax - xMin <= geometryEpsilon || yMax - yMin <= geometryEpsilon)
        {
            intersection = default;
            return false;
        }

        intersection = Rect.MinMaxRect(xMin, yMin, xMax, yMax);
        return true;
    }

    private bool RectContainsWithTolerance(Rect rect, Vector2 point)
    {
        return point.x >= rect.xMin - geometryEpsilon
            && point.x <= rect.xMax + geometryEpsilon
            && point.y >= rect.yMin - geometryEpsilon
            && point.y <= rect.yMax + geometryEpsilon;
    }

    private float PixelsToWorldUnits(float pixels, SpriteRenderer referenceRenderer)
    {
        float pixelsPerUnit = 100.0f;

        if (referenceRenderer != null && referenceRenderer.sprite != null && referenceRenderer.sprite.pixelsPerUnit > 0.0f)
        {
            pixelsPerUnit = referenceRenderer.sprite.pixelsPerUnit;
        }
        else if (gameBoardSpriteRenderer != null && gameBoardSpriteRenderer.sprite != null && gameBoardSpriteRenderer.sprite.pixelsPerUnit > 0.0f)
        {
            pixelsPerUnit = gameBoardSpriteRenderer.sprite.pixelsPerUnit;
        }

        return pixels / pixelsPerUnit;
    }

    private Rect ExpandRectInsideBounds(Rect rect, float padding, Rect boundsRect)
    {
        if (padding <= geometryEpsilon)
        {
            return rect;
        }

        Rect expanded = Rect.MinMaxRect(
            rect.xMin - padding,
            rect.yMin - padding,
            rect.xMax + padding,
            rect.yMax + padding
        );

        if (TryGetRectIntersection(expanded, boundsRect, out Rect clampedExpanded))
        {
            return clampedExpanded;
        }

        return rect;
    }
}
