using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Синглтон. Управляет всеми чанками.
/// Точка входа для постановки/разрушения блоков.
/// </summary>
public class WorldManager : MonoBehaviour
{
    public static WorldManager Instance { get; private set; }

    [Header("References")]
    public BlockRegistry registry; // назначается автоматически если null

    [Header("Chunk Prefab")]
    [Tooltip("Можно оставить пустым — создаётся из кода")]
    public GameObject chunkPrefab;

    // chunkPos (в чанковых координатах) → Chunk
    private readonly Dictionary<Vector3Int, Chunk> _chunks = new();

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

        if (registry == null)
            registry = FindObjectOfType<BlockRegistry>();
    }

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

    /// <summary>Получить blockId по мировым координатам блока. null = пусто.</summary>
    public string GetBlock(Vector3Int worldPos)
    {
        Vector3Int cp = ChunkData.WorldToChunkPos(worldPos);
        if (!_chunks.TryGetValue(cp, out var chunk)) return null;
        return chunk.GetBlock(ChunkData.WorldToLocal(worldPos));
    }

    /// <summary>Поставить блок. Создаёт чанк если нужно.</summary>
    public void PlaceBlock(Vector3Int worldPos, string blockId)
    {
        var chunk = GetOrCreateChunk(ChunkData.WorldToChunkPos(worldPos));
        chunk.SetBlock(ChunkData.WorldToLocal(worldPos), blockId);

        // Соседние чанки на границе тоже надо пересобрать (face culling)
        RebuildNeighboursIfBorder(worldPos);
    }

    /// <summary>Сломать блок. Возвращает blockId удалённого блока (или null).</summary>
    public string BreakBlock(Vector3Int worldPos)
    {
        Vector3Int cp = ChunkData.WorldToChunkPos(worldPos);
        if (!_chunks.TryGetValue(cp, out var chunk)) return null;

        string blockId = chunk.GetBlock(ChunkData.WorldToLocal(worldPos));
        if (blockId == null) return null;

        chunk.RemoveBlock(ChunkData.WorldToLocal(worldPos));
        RebuildNeighboursIfBorder(worldPos);
        return blockId;
    }

    /// <summary>
    /// Получить или создать чанк по чанковым координатам.
    /// Используется генераторами мира и PlaceBlock.
    /// </summary>
    public Chunk GetOrCreateChunk(Vector3Int chunkPos)
    {
        if (_chunks.TryGetValue(chunkPos, out var existing))
            return existing;

        return CreateChunk(chunkPos, new ChunkData(chunkPos));
    }

    /// <summary>
    /// Создать чанк с готовыми данными (используется генераторами мира).
    /// </summary>
    public Chunk CreateChunk(Vector3Int chunkPos, ChunkData data)
    {
        if (_chunks.ContainsKey(chunkPos))
        {
            Debug.LogWarning($"[WorldManager] Chunk {chunkPos} already exists.");
            return _chunks[chunkPos];
        }

        GameObject go;
        if (chunkPrefab != null)
            go = Instantiate(chunkPrefab, transform);
        else
        {
            go = new GameObject($"Chunk_{chunkPos}");
            go.transform.SetParent(transform);
        }

        go.transform.position = ChunkData.ChunkToWorld(chunkPos);

        var chunk = go.GetComponent<Chunk>() ?? go.AddComponent<Chunk>();
        chunk.Initialise(data, registry.ChunkMaterial);

        _chunks[chunkPos] = chunk;
        return chunk;
    }

    // ── Вспомогательные ───────────────────────────────────────────────────────

    /// <summary>
    /// Если изменённый блок находится на границе чанка —
    /// пересобираем соседний чанк, чтобы обновить face culling.
    /// </summary>
    void RebuildNeighboursIfBorder(Vector3Int worldPos)
    {
        Vector3Int local = ChunkData.WorldToLocal(worldPos);

        if (local.x == 0)                    RebuildChunkAt(worldPos + Vector3Int.left);
        if (local.x == ChunkData.SIZE - 1)   RebuildChunkAt(worldPos + Vector3Int.right);
        if (local.y == 0)                    RebuildChunkAt(worldPos + Vector3Int.down);
        if (local.y == ChunkData.HEIGHT - 1) RebuildChunkAt(worldPos + Vector3Int.up);
        if (local.z == 0)                    RebuildChunkAt(worldPos + new Vector3Int(0,0,-1));
        if (local.z == ChunkData.SIZE - 1)   RebuildChunkAt(worldPos + new Vector3Int(0,0,1));
    }

    void RebuildChunkAt(Vector3Int worldPos)
    {
        Vector3Int cp = ChunkData.WorldToChunkPos(worldPos);
        if (_chunks.TryGetValue(cp, out var chunk))
            chunk.RebuildMesh();
    }

    /// <summary>Перевод мировой позиции игрока → мировые координаты блока.</summary>
    public static Vector3Int WorldToBlock(Vector3 worldPosition)
    {
        return new Vector3Int(
            Mathf.FloorToInt(worldPosition.x),
            Mathf.FloorToInt(worldPosition.y),
            Mathf.FloorToInt(worldPosition.z));
    }
}
