using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// MonoBehaviour-обёртка над ChunkData.
/// Хранит данные блоков и собирает процедурный меш с face culling.
/// </summary>
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshCollider))]
public class Chunk : MonoBehaviour
{
    public ChunkData Data { get; private set; }

    MeshFilter   _filter;
    MeshRenderer _renderer;
    MeshCollider _collider;
    Mesh         _mesh;

    // ── 6 направлений граней ──────────────────────────────────────────────────
    // Порядок: Top, Bottom, Front(Z+), Back(Z-), Right(X+), Left(X-)
    static readonly Vector3Int[] Directions =
    {
        Vector3Int.up, Vector3Int.down,
        new(0,0,1), new(0,0,-1),
        new(1,0,0), new(-1,0,0)
    };

    // Вершины каждой грани (относительно нижнего-левого угла блока)
    // Порядок по часовой стрелке, смотрим снаружи
    static readonly Vector3[][] FaceVerts =
    {
        // Top (Y+)
        new[] { new Vector3(0,1,0), new Vector3(0,1,1), new Vector3(1,1,1), new Vector3(1,1,0) },
        // Bottom (Y-)
        new[] { new Vector3(0,0,0), new Vector3(1,0,0), new Vector3(1,0,1), new Vector3(0,0,1) },
        // Front (Z+)
        new[] { new Vector3(1,0,1), new Vector3(0,0,1), new Vector3(0,1,1), new Vector3(1,1,1) },
        // Back (Z-)
        new[] { new Vector3(0,0,0), new Vector3(1,0,0), new Vector3(1,1,0), new Vector3(0,1,0) },
        // Right (X+)
        new[] { new Vector3(1,0,0), new Vector3(1,0,1), new Vector3(1,1,1), new Vector3(1,1,0) },
        // Left (X-)
        new[] { new Vector3(0,0,1), new Vector3(0,0,0), new Vector3(0,1,0), new Vector3(0,1,1) }
    };

    // UV для каждой из 4 вершин грани
    static readonly Vector2[] FaceUVs =
    {
        new(0,0), new(1,0), new(1,1), new(0,1)
    };

    // ── Инициализация ─────────────────────────────────────────────────────────

    void Awake()
    {
        _filter   = GetComponent<MeshFilter>();
        _renderer = GetComponent<MeshRenderer>();
        _collider = GetComponent<MeshCollider>();

        _mesh = new Mesh { name = "ChunkMesh" };
        _mesh.MarkDynamic(); // оптимизация для часто изменяемых мешей
        _filter.sharedMesh = _mesh;
    }

    public void Initialise(ChunkData data, Material chunkMaterial)
    {
        Data = data;
        _renderer.sharedMaterial = chunkMaterial;
        RebuildMesh();
    }

    // ── Публичный API ─────────────────────────────────────────────────────────

    public void SetBlock(Vector3Int localPos, string blockId)
    {
        Data.SetBlock(localPos, blockId);
        RebuildMesh();
    }

    public void RemoveBlock(Vector3Int localPos)
    {
        Data.SetBlock(localPos, null);
        RebuildMesh();
    }

    public string GetBlock(Vector3Int localPos) => Data.GetBlock(localPos);

    // ── Построение меша ───────────────────────────────────────────────────────

    public void RebuildMesh()
    {
        if (Data == null) return;

        var verts    = new List<Vector3>();
        var tris     = new List<int>();
        var uvs      = new List<Vector2>(); // UV0: координаты на грани 0-1
        var texCoord1 = new List<Vector2>(); // UV1: x = индекс в Texture2DArray

        for (int x = 0; x < ChunkData.SIZE;   x++)
        for (int y = 0; y < ChunkData.HEIGHT;  y++)
        for (int z = 0; z < ChunkData.SIZE;    z++)
        {
            string blockId = Data.GetBlock(x, y, z);
            if (blockId == null) continue;

            var def = BlockRegistry.Instance?.GetById(blockId);
            if (def == null) continue;

            for (int f = 0; f < 6; f++)
            {
                Vector3Int neighbour = new Vector3Int(x, y, z) + Directions[f];

                // Face culling: пропускаем грань если сосед — твёрдый блок
                if (HasSolidNeighbour(neighbour)) continue;

                // Индекс текстуры для этой грани
                int texIndex = f == 0 ? def.topIndex :
                               f == 1 ? def.bottomIndex :
                                        def.sideIndex;

                int baseVert = verts.Count;
                var origin   = new Vector3(x, y, z);

                for (int v = 0; v < 4; v++)
                {
                    verts.Add(origin + FaceVerts[f][v]);
                    uvs.Add(FaceUVs[v]);
                    texCoord1.Add(new Vector2(texIndex, 0));
                }

                // Два треугольника на грань
                tris.Add(baseVert + 0);
                tris.Add(baseVert + 1);
                tris.Add(baseVert + 2);
                tris.Add(baseVert + 0);
                tris.Add(baseVert + 2);
                tris.Add(baseVert + 3);
            }
        }

        _mesh.Clear();
        _mesh.SetVertices(verts);
        _mesh.SetTriangles(tris, 0);
        _mesh.SetUVs(0, uvs);
        _mesh.SetUVs(1, texCoord1);
        _mesh.RecalculateNormals();
        _mesh.RecalculateBounds();

        // Обновляем коллайдер (нужен для рейкаста)
        _collider.sharedMesh = null;
        _collider.sharedMesh = _mesh;

        Data.ClearDirty();
    }

    // ── Face culling ──────────────────────────────────────────────────────────

    bool HasSolidNeighbour(Vector3Int localPos)
    {
        // Сосед внутри этого чанка
        if (ChunkData.InBounds(localPos.x, localPos.y, localPos.z))
        {
            string id = Data.GetBlock(localPos);
            if (id == null) return false;
            var def = BlockRegistry.Instance?.GetById(id);
            return def != null && def.isSolid;
        }

        // Сосед в соседнем чанке — спрашиваем WorldManager
        if (WorldManager.Instance == null) return false;

        Vector3Int worldOrigin = ChunkData.ChunkToWorld(Data.ChunkPos);
        Vector3Int worldNeighbour = worldOrigin + localPos;
        string neighbourId = WorldManager.Instance.GetBlock(worldNeighbour);
        if (neighbourId == null) return false;
        var neighbourDef = BlockRegistry.Instance?.GetById(neighbourId);
        return neighbourDef != null && neighbourDef.isSolid;
    }
}
