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

/// <summary>
/// GAME 1303 Tier 5 — The dachshund. A chain of segments that follows the head's path,
/// grows when you eat, and ends the game if the head runs into the body.
/// Weenie(dog) Maze's core mechanic.
///
/// HOW IT WORKS
///  The head records the cell it leaves at the end of every step. Segment 1 moves to
///  where the head was, segment 2 to where segment 1 was, and so on — the classic snake
///  trick, done as a queue of positions rather than per-segment logic.
///
/// SETUP
///  1. Head object: Sprite + Collider2D (trigger) + GridMover (Mode = Continuous) +
///     this script. Tag "Player".
///  2. Body Segment Prefab: a sprite with a Collider2D set to Is Trigger, on a layer
///     called "SnakeBody". No script needed on it.
///  3. Tail Prefab (optional): a different sprite for the last segment.
///  4. Starting Length: how many segments to spawn at the start (3 is traditional).
///  5. Grace Segments: how many segments right behind the head can't kill you. 1 or 2
///     stops unfair deaths on tight turns. Jax's "without running into yourself" is the
///     fail condition — wire OnSelfCollision to GameManager.GameOver.
///  6. Growth: call Grow() from the treat's Collectible.OnCollected event, or wire
///     CollectionTracker.OnCountChanged if you prefer.
///
/// The wiener-dog joke works nicely here: start at length 3 and the "growing" is the dog
/// getting longer, so make the body sprite a stretched middle section.
/// </summary>
public class SnakeBody : MonoBehaviour
{
    [Header("Prefabs")]
    public GameObject bodySegmentPrefab;
    public GameObject tailPrefab;

    [Header("Length")]
    public int startingLength = 3;
    public int segmentsPerGrow = 1;
    [Tooltip("Segments right behind the head that can't kill you (fairness on tight turns).")]
    public int graceSegments = 1;
    [Tooltip("Max length; 0 = unlimited.")]
    public int maxLength = 0;

    [Header("Collision")]
    public bool selfCollisionKills = true;
    [Tooltip("How close the head must be to a segment to count as a hit (fraction of a cell).")]
    [Range(0.1f, 0.9f)] public float hitTolerance = 0.5f;

    [Header("Events")]
    public UnityEvent<int> OnLengthChanged;
    public UnityEvent OnGrew;
    public UnityEvent OnSelfCollision;

    public int Length => segments.Count;

    private readonly List<Transform> segments = new List<Transform>();
    private readonly List<Vector3> path = new List<Vector3>();   // cells the head has left, newest first
    private GridMover head;
    private int pendingGrowth;
    private bool dead;

    void Awake()
    {
        head = GetComponent<GridMover>();
        if (head == null) { Debug.LogError("SnakeBody needs a GridMover on the same object."); enabled = false; }
    }

    void OnEnable()  { if (head != null) head.OnStepCompleted.AddListener(OnHeadStepped); }
    void OnDisable() { if (head != null) head.OnStepCompleted.RemoveListener(OnHeadStepped); }

    void Start()
    {
        // Seed the path behind the head so the starting segments have somewhere to sit
        Vector3 back = new Vector3(-head.Direction.x, -head.Direction.y, 0f) * head.cellSize;
        for (int i = 0; i < startingLength + 2; i++)
            path.Add(transform.position + back * (i + 1));

        for (int i = 0; i < startingLength; i++) AddSegment();
        OnLengthChanged?.Invoke(Length);
    }

    void OnHeadStepped(Vector3 arrivedAt)
    {
        if (dead) return;

        // The head just left PreviousCell — that's where segment 1 goes.
        path.Insert(0, head.PreviousCell);

        // Grow by leaving the tail where it is for a step
        if (pendingGrowth > 0)
        {
            AddSegment();
            pendingGrowth--;
            OnGrew?.Invoke();
            OnLengthChanged?.Invoke(Length);
        }

        // Move every segment one position along the path
        for (int i = 0; i < segments.Count; i++)
            if (i < path.Count) segments[i].position = path[i];

        // Trim history we no longer need
        int keep = segments.Count + 2;
        while (path.Count > keep) path.RemoveAt(path.Count - 1);

        CheckSelfCollision(arrivedAt);
        UpdateTailSprite();
    }

    void CheckSelfCollision(Vector3 headPos)
    {
        if (!selfCollisionKills || dead) return;
        float threshold = head.cellSize * hitTolerance;

        for (int i = graceSegments; i < segments.Count; i++)
        {
            if (segments[i] == null) continue;
            if (Vector3.Distance(headPos, segments[i].position) < threshold)
            {
                dead = true;
                head.Halt();
                OnSelfCollision?.Invoke();
                return;
            }
        }
    }

    void AddSegment()
    {
        if (bodySegmentPrefab == null) return;
        if (maxLength > 0 && segments.Count >= maxLength) return;

        int index = segments.Count;
        Vector3 spawnAt = index < path.Count ? path[index] : transform.position;
        var seg = Instantiate(bodySegmentPrefab, spawnAt, Quaternion.identity, transform.parent);
        seg.name = $"Segment_{index + 1}";
        segments.Add(seg.transform);
    }

    void UpdateTailSprite()
    {
        if (tailPrefab == null || segments.Count == 0) return;
        // Simple approach: the last segment keeps a distinct sprite, applied once.
        var last = segments[segments.Count - 1];
        if (last != null && last.name.StartsWith("Segment_"))
        {
            var srPrefab = tailPrefab.GetComponent<SpriteRenderer>();
            var srLast = last.GetComponent<SpriteRenderer>();
            if (srPrefab != null && srLast != null && srLast.sprite != srPrefab.sprite)
            {
                // restore the previous one to a body sprite
                if (segments.Count > 1)
                {
                    var prev = segments[segments.Count - 2].GetComponent<SpriteRenderer>();
                    var body = bodySegmentPrefab.GetComponent<SpriteRenderer>();
                    if (prev != null && body != null) prev.sprite = body.sprite;
                }
                srLast.sprite = srPrefab.sprite;
            }
        }
    }

    /// <summary>Wire the treat's Collectible.OnCollected here.</summary>
    public void Grow() => pendingGrowth += segmentsPerGrow;

    public void Grow(int amount) => pendingGrowth += amount;

    /// <summary>Back to starting length (for a retry without reloading the scene).</summary>
    public void ResetSnake()
    {
        foreach (var s in segments) if (s != null) Destroy(s.gameObject);
        segments.Clear();
        path.Clear();
        pendingGrowth = 0;
        dead = false;

        Vector3 back = new Vector3(-head.Direction.x, -head.Direction.y, 0f) * head.cellSize;
        for (int i = 0; i < startingLength + 2; i++)
            path.Add(transform.position + back * (i + 1));
        for (int i = 0; i < startingLength; i++) AddSegment();

        head.ResetSpeed();
        OnLengthChanged?.Invoke(Length);
    }
}