using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using Input;
using Map.MapEditor.Action;
using TMPro;

namespace Map.MapEditor
{
    public class MapEditorController : MonoBehaviour
    {
        private GameObject _selectedModel;
        private GameObject _modelToPlaceInstance;
        private GameObject _selectedModelInstance;
        private bool _isPlacingModel;
        private bool _isErasing;
        private bool _isDrawing;

        private Vector3? _lastPlacedPosition;
        private Layer _currentLayer = Layer.Background;

        public float gridSize = 3f;
        public Transform parent;
        public GridGenerator gridGenerator;
        public Button removeObjectButton;
        public Button brushObjectButton;
        public Button eraserObjectButton;
        public Button undoButton;
        public Button redoButton;
        public TMP_Dropdown dropdownLayer;

        public InputProvider inputProvider;

        private readonly Stack<IAction> _undoStack = new();
        private readonly Stack<IAction> _redoStack = new();

        private void Start()
        {
            removeObjectButton.onClick.AddListener(OnPressRemoveObject);
            brushObjectButton.onClick.AddListener(OnPressBrushObject);
            eraserObjectButton.onClick.AddListener(OnPressEraserObject);
            undoButton.onClick.AddListener(OnPressUndoButton);
            redoButton.onClick.AddListener(OnPressRedoButton);

            InitializeDropdownLayer();
            dropdownLayer.onValueChanged.AddListener(OnDropdownLayerChanged);

            inputProvider.Undo += OnPressUndoButton;
            inputProvider.Redo += OnPressRedoButton;
        }

        private void InitializeDropdownLayer()
        {
            dropdownLayer.ClearOptions();
            
            var options = (from Layer layer in Enum.GetValues(typeof(Layer)) select layer.ToString()).ToList();

            dropdownLayer.AddOptions(options);
            dropdownLayer.value = (int)_currentLayer;
        }

        private void OnDropdownLayerChanged(int index)
        {
            _currentLayer = (Layer)index;
        }

        private void OnPressRemoveObject()
        {
            DeleteSelectedModel();
        }

        private void OnPressBrushObject()
        {
            _isDrawing ^= true;
            _isErasing = false;
            CancelPlacement();

            SetButtonColor(brushObjectButton, _isDrawing ? Color.green : Color.white);
            SetButtonColor(eraserObjectButton, Color.white);
        }

        private void OnPressEraserObject()
        {
            _isErasing ^= true;
            _isDrawing = false;
            CancelPlacement();

            SetButtonColor(eraserObjectButton, _isErasing ? Color.red : Color.white);
            SetButtonColor(brushObjectButton, Color.white);
        }

        private void SetButtonColor(Button button, Color color)
        {
            var colors = button.colors;
            colors.normalColor = color;
            colors.highlightedColor = color;
            colors.pressedColor = color;
            colors.selectedColor = color;
            button.colors = colors;
        }

        private void Update()
        {
            if (_isDrawing && _modelToPlaceInstance != null)
            {
                MoveModelToMousePosition(_modelToPlaceInstance);

                if (inputProvider.IsActionButton)
                    PlaceModel();
            }
            else if (_isErasing && inputProvider.IsActionButton)
            {
                EraseModel();
            }

            if (!_isPlacingModel)
            {
                if (inputProvider.IsActionButton)
                    SelectModel();

                if (inputProvider.Delete)
                    DeleteSelectedModel();
            }
        }

        public void SetSelectedModel(GameObject model)
        {
            if (_isErasing || !_isDrawing) return;

            _selectedModel = model;
            StartPlacingModel();
        }

        private void StartPlacingModel()
        {
            if (_modelToPlaceInstance != null)
            {
                Destroy(_modelToPlaceInstance);
            }

            _modelToPlaceInstance = Instantiate(_selectedModel);
            _isPlacingModel = true;
            _lastPlacedPosition = null;
        }

        private void MoveModelToMousePosition(GameObject modelInstance)
        {
            var ray = Camera.main!.ScreenPointToRay(inputProvider.PlacePosition);
            Debug.DrawRay(ray.origin, ray.direction * 1000, Color.red);

            var plane = new Plane(Vector3.up, Vector3.zero);
            if (plane.Raycast(ray, out var hit))
            {
                var point = ray.GetPoint(hit);
                var gridPosition = GetNearestGridPosition(point);
                modelInstance.transform.position = gridPosition;
            }
        }

        private Vector3 GetNearestGridPosition(Vector3 hitPoint)
        {
            var x = Mathf.Round(hitPoint.x / gridSize) * gridSize;
            var y = Mathf.Round(hitPoint.y / gridSize) * gridSize;
            var z = Mathf.Round(hitPoint.z / gridSize) * gridSize;

            
            return new Vector3(x, y, z);
        }


        private void PlaceModel()
        {
            var gridPosition = GetNearestGridPosition(_modelToPlaceInstance.transform.position);
            var modelBounds = GetModelBounds(_modelToPlaceInstance);

            var adjustedPosition = new Vector3(gridPosition.x, gridPosition.y, gridPosition.z);

            if (IsPositionWithinGrid(adjustedPosition, modelBounds) &&
                !(_lastPlacedPosition.HasValue && _lastPlacedPosition.Value == adjustedPosition))
            {
                var placedModel = Instantiate(_selectedModel, adjustedPosition, _modelToPlaceInstance.transform.rotation,
                    parent);
                gridGenerator.SetObjectAtCell(adjustedPosition, placedModel, _currentLayer);

                AddAction(_selectedModel, adjustedPosition, placedModel);

                foreach (Transform child in placedModel.transform)
                {
                    child.gameObject.AddComponent<MeshCollider>();
                }

                _lastPlacedPosition = adjustedPosition;
            }
            else
            {
                Debug.LogWarning(“Model placement is outside grid bounds or already placed at this position.”);
            }
        }
        private Bounds GetModelBounds(GameObject model)
        {
            var rendererComponent = model.GetComponent<Renderer>();
            if (rendererComponent != null)
            {
                return rendererComponent.bounds;
            }

            var colliderComponent = model.GetComponent<Collider>();
            if (colliderComponent != null)
            {
                return colliderComponent.bounds;
            }

            return new Bounds(model.transform.position, Vector3.one * gridSize);
        }

        private bool IsPositionWithinGrid(Vector3 position, Bounds modelBounds)
        {
            var halfSize = modelBounds.size / 2;
            var min = gridGenerator.GridMin + halfSize;
            var max = gridGenerator.GridMax - halfSize;

            return position.x >= min.x && position.x <= max.x && position.z >= min.z && position.z <= max.z;
        }

        private void CancelPlacement()
        {
            _isPlacingModel = false;
            if (_modelToPlaceInstance != null)
            {
                Destroy(_modelToPlaceInstance);
                _modelToPlaceInstance = null;
            }
        }

        private void SelectModel()
        {
            var ray = Camera.main!.ScreenPointToRay(inputProvider.PlacePosition);
            if (Physics.Raycast(ray, out var hit))
            {
                if (_selectedModelInstance != null &&
                    _selectedModelInstance.TryGetComponent<Outline>(out var component))
                {
                    Destroy(component);
                }

                var selectedObject = hit.collider.gameObject;
                selectedObject.AddComponent<Outline>();

                _selectedModelInstance = selectedObject;
            }
        }

        private void DeleteSelectedModel()
        {
            if (_selectedModelInstance != null)
            {
                var position = _selectedModelInstance.transform.position;
                gridGenerator.RemoveObjectAtCell(position, _currentLayer);

                AddAction(_selectedModelInstance, position);

                Destroy(_selectedModelInstance);
                _selectedModelInstance = null;
            }
        }

        private void EraseModel()
        {
            var ray = Camera.main!.ScreenPointToRay(inputProvider.PlacePosition);
            if (Physics.Raycast(ray, out var hit))
            {
                var selectedObject = hit.collider.gameObject;
                var position = GetNearestGridPosition(selectedObject.transform.position);

                if (!gridGenerator.IsObjectOnLayer(position, _currentLayer, selectedObject)) return;
                
                gridGenerator.RemoveObjectAtCell(position, _currentLayer);
                AddAction(selectedObject, position);
                Destroy(selectedObject);
            }
        }

        private void AddAction(GameObject model, Vector3 position, GameObject instance = null)
        {
            var action = new Action.Action(model, position, parent, _currentLayer, gridGenerator);

            if (instance is not null)
                action.SetInstance(instance);

            _undoStack.Push(action);
            _redoStack.Clear();
        }

        private void OnPressUndoButton()
        {
            if (_undoStack.Count <= 0) return;

            var action = _undoStack.Pop();
            action.Undo();
            _redoStack.Push(action);
        }

        private void OnPressRedoButton()
        {
            if (_redoStack.Count <= 0) return;

            var action = _redoStack.Pop();
            action.Redo();
            _undoStack.Push(action);
        }
    }
}


using System.Collections.Generic;
using UnityEngine;

namespace Map.MapEditor
{
    public class GridGenerator : MonoBehaviour
    {
        public float cellSize = 3.0f;
        public LineRenderer cellPrefab;

        public int gridWidth;
        public int gridLength;

        public Vector3 GridMin { get; private set; }
        public Vector3 GridMax { get; private set; }

        private Dictionary<Vector3, Dictionary<Layer, GameObject>> _occupiedCells = new();

        public void GenerateGrid(int width, int length)
        {
            gridWidth = width;
            gridLength = length;

            foreach (Transform child in transform)
            {
                Destroy(child.gameObject);
            }

            var cellPosition = transform.position;
            GridMin = cellPosition;
            GridMax = cellPosition + new Vector3((width + 1) * cellSize, 0, (length + 1) * cellSize);

            for (var x = 0; x <= width; x++)
            {
                var cell = Instantiate(cellPrefab, transform);
                cell.positionCount = 2;
                cell.SetPosition(0, new Vector3(cellPosition.x + x * cellSize, 0, cellPosition.z));
                cell.SetPosition(1, new Vector3(cellPosition.x + x * cellSize, 0, cellPosition.z + length * cellSize));
            }

            for (var z = 0; z <= length; z++)
            {
                var cell1 = Instantiate(cellPrefab, transform);
                cell1.positionCount = 2;
                cell1.SetPosition(0, new Vector3(cellPosition.x, 0, cellPosition.z + z * cellSize));
                cell1.SetPosition(1, new Vector3(cellPosition.x + width * cellSize, 0, cellPosition.z + z * cellSize));
            }
        }

        public bool TryGetOccupiedObject(Vector3 position, Layer layer, out GameObject occupiedObject)
        {
            occupiedObject = null;
            return _occupiedCells.TryGetValue(position, out var layerDict) && layerDict.TryGetValue(layer, out occupiedObject);
        }
        
        public bool IsObjectOnLayer(Vector3 position, Layer layer, GameObject obj)
        {
            if (TryGetOccupiedObject(position, layer, out var occupiedObject))
            {
                return occupiedObject == obj;
            }
            return false;
        }


        public void SetObjectAtCell(Vector3 position, GameObject obj, Layer layer)
        {
            if (!_occupiedCells.ContainsKey(position))
            {
                _occupiedCells[position] = new Dictionary<Layer, GameObject>();
            }

            if (_occupiedCells[position].TryGetValue(layer, out var existingObj))
            {
                Destroy(existingObj);
            }

            _occupiedCells[position][layer] = obj;
        }

        public void RemoveObjectAtCell(Vector3 position, Layer layer)
        {
            if (_occupiedCells.TryGetValue(position, out var layerDict) && layerDict.TryGetValue(layer, out var obj))
            {
                Destroy(obj);
                layerDict.Remove(layer);

                if (layerDict.Count == 0)
                {
                    _occupiedCells.Remove(position);
                }
            }
        }
    }
}


namespace Map.MapEditor
{
    public enum Layer
    {
        Background,
        Foreground
    }
}

короче, нужно чтобы ты сделал, когда выбран слой foreground то он рисовал объекты только на существующих объектах. Т.е бекграунд это проще говоря основна, а фореграунд, это уже то что на основе. И нужно чтобы он рисовал идеально на другом слое, т.е прям вот идет блок

_ он должен нарисовать блок на блоке т.е  получится так =