using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Синглтон. Собирает все BlockDefinition, строит Texture2DArray,
/// создаёт один shared Material для всех чанков.
/// Вызвать Initialise() до создания любого чанка.
/// </summary>
public class BlockRegistry : MonoBehaviour
{
    public static BlockRegistry Instance { get; private set; }

    [Header("All block definitions in the game")]
    public List<BlockDefinition> definitions = new();

    [Header("Shader")]
    public Shader voxelShader; // назначить Blocks/VoxelLit через Inspector

    // Доступно после Initialise()
    public Material ChunkMaterial  { get; private set; }
    public Texture2DArray TexArray { get; private set; }

    private readonly Dictionary<string, BlockDefinition> _byId     = new();
    private readonly Dictionary<string, BlockDefinition> _byItemId = new();

    void Awake()
    {
        if (Instance != null) { Destroy(gameObject); return; }
        Instance = this;
        DontDestroyOnLoad(gameObject);
        Initialise();
    }

    public void Initialise()
    {
        _byId.Clear();
        _byItemId.Clear();

        if (definitions == null || definitions.Count == 0)
        {
            Debug.LogWarning("[BlockRegistry] No BlockDefinitions assigned.");
            return;
        }

        // Собираем уникальные текстуры (top/bottom/side каждого блока)
        // Порядок: для блока i → top=i*3, bottom=i*3+1, side=i*3+2
        int layerCount = definitions.Count * 3;

        // Авто-определяем разрешение по первой найденной текстуре (берём из rect спрайта)
        int res = 16;
        foreach (var d in definitions)
        {
            Sprite s = d.topTexture ?? d.sideTexture ?? d.bottomTexture;
            if (s != null) { res = (int)s.rect.width; break; }
        }

        TexArray = new Texture2DArray(res, res, layerCount,
                                      TextureFormat.RGBA32, true, false);
        TexArray.filterMode = FilterMode.Point; // пиксельный стиль
        TexArray.wrapMode   = TextureWrapMode.Repeat;

        for (int i = 0; i < definitions.Count; i++)
        {
            var def = definitions[i];

            def.topIndex    = i * 3;
            def.bottomIndex = i * 3 + 1;
            def.sideIndex   = i * 3 + 2;

            CopyTexture(def.topTexture,    TexArray, def.topIndex,    res);
            CopyTexture(def.bottomTexture, TexArray, def.bottomIndex, res);
            CopyTexture(def.sideTexture,   TexArray, def.sideIndex,   res);

            _byId[def.blockId] = def;

            if (def.linkedItem != null)
                _byItemId[def.linkedItem.id] = def;
        }

        TexArray.Apply(true, true); // загрузить на GPU, сделать non-readable

        // Создаём один материал для всех чанков
        if (voxelShader == null)
            voxelShader = Shader.Find("Blocks/VoxelLit");

        ChunkMaterial = new Material(voxelShader);
        ChunkMaterial.SetTexture("_MainTexArray", TexArray);

        Debug.Log($"[BlockRegistry] Initialised {definitions.Count} blocks, {layerCount} texture layers.");
    }

    // ── Lookups ───────────────────────────────────────────────────────────────

    public BlockDefinition GetById(string blockId)
    {
        _byId.TryGetValue(blockId, out var def);
        return def;
    }

    /// <summary>Находит BlockDefinition по id связанного ItemData (для постановки блока).</summary>
    public BlockDefinition GetByItemId(string itemDataId)
    {
        _byItemId.TryGetValue(itemDataId, out var def);
        return def;
    }

    // ── Утилиты ───────────────────────────────────────────────────────────────

    /// <summary>
    /// Копирует один спрайт (или его часть из атласа) в слой Texture2DArray.
    /// Использует Graphics.Blit с UV offset/scale — Read/Write не нужен.
    /// Если src null — заполняет пурпурным (ошибка-текстура).
    /// </summary>
    static void CopyTexture(Sprite src, Texture2DArray dst, int layerIndex, int res)
    {
        if (src == null)
        {
            var pixels = new Color[res * res];
            for (int p = 0; p < pixels.Length; p++)
                pixels[p] = (p / res + p) % 2 == 0 ? Color.magenta : Color.black;
            dst.SetPixels(pixels, layerIndex);
            return;
        }

        // Вычисляем UV offset/scale этого спрайта внутри атласа.
        // sprite.rect — пиксельные координаты (origin = bottom-left атласа).
        float atlasW = src.texture.width;
        float atlasH = src.texture.height;
        var scale  = new Vector2(src.rect.width  / atlasW, src.rect.height / atlasH);
        var offset = new Vector2(src.rect.x      / atlasW, src.rect.y      / atlasH);

        var rt = RenderTexture.GetTemporary(res, res, 0, RenderTextureFormat.ARGB32);
        var prevFilter = src.texture.filterMode;
        src.texture.filterMode = FilterMode.Point;
        Graphics.Blit(src.texture, rt, scale, offset);
        src.texture.filterMode = prevFilter;

        var prev = RenderTexture.active;
        RenderTexture.active = rt;
        var tmp = new Texture2D(res, res, TextureFormat.RGBA32, false);
        tmp.ReadPixels(new Rect(0, 0, res, res), 0, 0);
        tmp.Apply();
        RenderTexture.active = prev;
        RenderTexture.ReleaseTemporary(rt);

        dst.SetPixels(tmp.GetPixels(), layerIndex);
        Object.Destroy(tmp);
    }
}
