using Godot;
using System.Collections.Generic;
using AceFieldNewHorizon.Scripts.Tiles;

namespace AceFieldNewHorizon.Scripts.System;

public partial class NaturalResourceGenerator : Node2D
{
	[Export] public GridManager Grid { get; set; }
	[Export] public BuildingRegistry Registry { get; set; }
		
	[Export] public int MapWidth = 100;
	[Export] public int MapHeight = 100;
	[Export] public float StoneDensity = 0.1f;  // 10% chance for stone
	[Export] public float IronDensity = 0.03f;  // 3% chance for iron (within stone)
	[Export] public int MinStoneVeinSize = 1;
	[Export] public int MaxStoneVeinSize = 5;
	[Export] public int MinIronVeinSize = 1;
	[Export] public int MaxIronVeinSize = 3;
	[Export] public int Seed;

	private RandomNumberGenerator _rng;
	private readonly List<Vector2I> _groundTiles = [];
	private readonly List<Vector2I> _stoneTiles = [];
	private readonly List<Vector2I> _ironTiles = [];

	public override void _Ready()
	{
		_rng = new RandomNumberGenerator();
		_rng.Seed = (ulong)(Seed != 0 ? Seed : (int)GD.Randi());
			
		GenerateTerrain();
		PlaceResources();
	}

	private void GenerateTerrain()
	{
		// First pass: Generate base ground tiles
		for (int x = 0; x < MapWidth; x++)
		{
			for (int y = 0; y < MapHeight; y++)
			{
				var cell = new Vector2I(x, y);
				_groundTiles.Add(cell);
				PlaceTile("ground", cell);
			}
		}
	}

	private void PlaceResources()
	{
		// Create a copy of ground tiles for iteration
		var groundTilesToProcess = new List<Vector2I>(_groundTiles);
			
		// Place stone veins
		foreach (var cell in groundTilesToProcess)
		{
			if (_rng.Randf() < StoneDensity)
			{
				var veinSize = _rng.RandiRange(MinStoneVeinSize, MaxStoneVeinSize);
				PlaceVein(cell, "stone", veinSize, _stoneTiles);
			}
		}

		// Create a copy of stone tiles for iteration
		var stoneTilesToProcess = new List<Vector2I>(_stoneTiles);
			
		// Place iron veins within stone
		foreach (var stoneCell in stoneTilesToProcess)
		{
			if (_rng.Randf() < IronDensity)
			{
				var veinSize = _rng.RandiRange(MinIronVeinSize, MaxIronVeinSize);
				PlaceVein(stoneCell, "ore_iron", veinSize, _ironTiles);
			}
		}
	}

	private void PlaceVein(Vector2I startCell, string tileType, int maxVeinSize, ICollection<Vector2I> tileList)
	{
		var queue = new Queue<Vector2I>();
		var placed = new HashSet<Vector2I>();
		queue.Enqueue(startCell);

		int placedCount = 0;
			
		while (queue.Count > 0 && placedCount < maxVeinSize)
		{
			var cell = queue.Dequeue();
			if (placed.Contains(cell)) continue;
			if (!IsInBounds(cell)) continue;

			switch (tileType)
			{
				// For iron, make sure we're placing on stone
				case "ore_iron" when !_stoneTiles.Contains(cell):
					continue;
				// Remove from previous layer if needed
				case "ore_iron" when _stoneTiles.Contains(cell):
					_stoneTiles.Remove(cell);
					break;
				case "stone" when _groundTiles.Contains(cell):
					_groundTiles.Remove(cell);
					break;
			}

			PlaceTile(tileType, cell);
			tileList.Add(cell);
			placed.Add(cell);
			placedCount++;

			// Add adjacent cells to queue
			for (var dx = -1; dx <= 1; dx++)
			{
				for (var dy = -1; dy <= 1; dy++)
				{
					if (dx == 0 && dy == 0) continue; // Skip self
					var neighbor = new Vector2I(cell.X + dx, cell.Y + dy);
					if (!placed.Contains(neighbor) && IsInBounds(neighbor))
					{
						queue.Enqueue(neighbor);
					}
				}
			}
		}
	}

	private bool IsInBounds(Vector2I cell)
	{
		return cell.X >= 0 && cell.X < MapWidth && 
			   cell.Y >= 0 && cell.Y < MapHeight;
	}

	private void PlaceTile(string tileType, Vector2I cell)
	{
		var building = Registry.GetBuilding(tileType);
		if (building == null) return;

		var scene = building.Scene;
		var instance = (BaseTile)scene.Instantiate();
		
		// Match PlacementManager's positioning logic
		var rotatedSize = building.GetRotatedSize(0f);
		var offset = GridUtils.GetCenterOffset(rotatedSize, 0f);
		instance.Position = GridUtils.GridToWorld(cell) + offset;
		
		instance.ZIndex = (int)building.Layer;
		AddChild(instance);

		// Make sure to use the building's size from the registry
		Grid.OccupyArea(cell, instance, building.Size, 0f, building.Layer);
	}
}
