using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using Cirno.Scripts;
using Cirno.Scripts.Resources;

public partial class InventoryManager : Node2D
{
	public static InventoryManager Instance { get; private set; }
	
	public ItemsDatabase ItemsDatabase { get; set; }
	
	public bool RedKeycard { get; set; }

	private Dictionary<string, ItemContainer> _itemsDict = new();

	public List<ItemContainer> Items => _itemsDict.Where(x => x.Value.Count > 0).Select(x => x.Value).ToList();

	[Signal]
	public delegate void ItemAddedEventHandler(LootItem item, int currentAmount);

	[Signal]
	public delegate void ItemRemovedEventHandler(string itemKey, int currentAmount);

	/// <summary>
	/// Use when equipping a weapon from the inventory screen
	/// </summary>
	[Signal] public delegate void WeaponEquipEventHandler(string itemKey);
	
	/// <summary>
	/// Use when updating the weapon shown on the hud
	/// </summary>
	[Signal] public delegate void WeaponUpdateEventHandler(string itemKey);

	[Signal]
	public delegate void ItemUsedEventHandler(LootItem itemKey, int totalCount);

	[Signal]
	public delegate void TotalAmmoChangedEventHandler(StringName ammoKey, int count);
	
	[Signal]
	public delegate void LoadedAmmoChangedEventHandler(StringName weaponKey, int count);
	
	public override void _Ready()
	{
		Instance = this;
		GD.Print("Loading items");
		Load(GlobalState.Session.Items);
	}

	// Called every frame. 'delta' is the elapsed time since the previous frame.
	public override void _Process(double delta)
	{
	}

	public bool TryGetItem(string key, out ItemContainer item)
	{
		return _itemsDict.TryGetValue(key, out item);
	}

	public LootItem FindItemInDb(string key)
	{
		return ItemsDatabase.GetLootItem(key);
	}

	public bool HasItems(IList<string> itemKeys)
	{
		return itemKeys.Aggregate(false, (current, item) => current || GetItemCount(item) > 0);
	}

	public int GetItemCount(string itemKey)
	{
		return _itemsDict.TryGetValue(itemKey, out var itm) ? itm.Count : 0;
	}

	public int RemoveItem(LootItem item)
	{
		return RemoveItem(item.ItemKey, 9999);
	}

	public int RemoveItem(string itemKey, int amount)
	{
		if (!_itemsDict.TryGetValue(itemKey, out var itm)) return 0;

		int removed = Math.Min(itm.Count, amount);

		itm.Count -= amount;

		if (itm.Count < 0)
		{
			itm.Count = 0;
			//_itemsDict.Remove(itemKey);
		}

		EmitSignal(SignalName.ItemRemoved, itemKey, itm.Count);

		if (itm.Item.Item is ItemTypes.Ammo)
		{
			EmitSignal(SignalName.TotalAmmoChanged, itemKey, itm.Count);
		}
		
		return removed;

	}

	public bool CanAddItem(string itemKey)
	{
		
		var found = TryGetItem(itemKey, out var itm);
		if (found)
		{
			return (itm.Count < itm.Item.Max);
		}

		var dbItem = FindItemInDb(itemKey);
		return dbItem != null;
	}

	public bool AddItem(LootItem item)
	{
		//var item = new LootItem() { Item = type, Amount = amount };

		if (!_itemsDict.TryGetValue(item.ItemKey, out var itm))
		{
			_itemsDict.Add(item.ItemKey, new ItemContainer()
			{
				Item = item,
				Count = item.Amount,
			});
			GD.Print($"Added new ({item.ItemKey}) {item.Item} x{item.Amount}");

			EmitSignal(SignalName.ItemAdded, item, item.Amount);
		}
		else
		{
			if (itm.Count < item.Max)
			{
				itm.Count += item.Amount;
			}
			else
			{
				if (item.PickupIfMaxed)
				{
					itm.Count = item.Max;
				}
				else
				{
					return false;
				}
			}
			EmitSignal(SignalName.ItemAdded, item, itm.Count);
		}

		return true;
	}
	
	public bool UseItem(StringName itemKey)
	{
		if (!_itemsDict.TryGetValue(itemKey, out var itm)) return false;

		if (itm.Count == 0) return false;
		GD.Print($"Used item in manager {itemKey}");

		switch (itm.Item.Item)
		{
			case ItemTypes.Medkit:
			case ItemTypes.FrogBomb:
			case ItemTypes.Bomb:
			case ItemTypes.Mine:
			case ItemTypes.Battery:
				EmitSignal(SignalName.ItemUsed, itm.Item, itm.Count);
				if (itm.Item.ConsumeOnUse)
				{
					RemoveItem(itm.Item.ItemKey, 1);
				}
				break;
			case ItemTypes.Weapon:
				// Equip weapon
				EmitSignal(SignalName.WeaponEquip, itm.Item.ItemKey);
				break;

		}

		return true;
	}

	public void NotifyLoadedAmmoChange(StringName weaponDataItemKey, int loadedAmmo)
	{
		EmitSignal(SignalName.LoadedAmmoChanged, weaponDataItemKey, loadedAmmo);
	}

	public Godot.Collections.Dictionary<string, int> Save()
	{
		Godot.Collections.Dictionary<string, int> dict = new();
		foreach (var item in _itemsDict)
		{
			dict.Add(item.Key, item.Value.Count);
		}

		return dict;
	}

	public void Load(Godot.Collections.Dictionary<string, int> items)
	{
		ItemsDatabase = ResourceLoader.Load<ItemsDatabase>("uid://cdi84udi6gldt");
		
		_itemsDict.Clear();

		_itemsDict = items.ToDictionary(x => x.Key, x => new ItemContainer()
		{
			Item = ItemsDatabase.LootItems.FirstOrDefault(y => y.ItemKey == x.Key), 
			Count = x.Value
		});
		
		GD.Print($"Items after loading: {_itemsDict.Count}");
	}

	public void UpdateEquippedWeapon(string itemKey)
	{
		EmitSignalWeaponUpdate(itemKey);
	}
}

public class ItemContainer
{
	public LootItem Item { get; set; }
	public int Count { get; set; }
}
