-- luacheck: globals Character Game MyModGlobal CLIENT
if not CLIENT then return end
local utils = require("Cyka.utils")
local dump = require("Cyka.dump")

---@return {item: Barotrauma.Item, fabricator: Barotrauma.FabricatorComponent}, string?
local function getOpenFabricator()
	-- Get the controlled character
	local controlledCharacter = Character.Controlled
	if not controlledCharacter then return nil, "No controlled character found" end

	-- Check if the character has a selected item
	local selectedItem = controlledCharacter.SelectedItem
	if not selectedItem then return nil, "No selected item found" end

	-- Check if the selected item has a Fabricator component
	local fabricator = Game.GetFabricatorComponent(selectedItem)
	if not fabricator then return nil, "No fabricator component found" end

	return {
		item = selectedItem,
		fabricator = fabricator
	}
end

--- Recipes can have multiple inputs, for example ammo
--- Can be made either out of copper iron or steel, 1 of either
---@class RecipeInfo
---@field targetItem {identifier: string, name: string, amount: number}
---@field requiredItems {amount: number, minCondition: number, maxCondition: number, prefabs: string[]}[]

---@param fabricator Barotrauma.FabricatorComponent
---@return RecipeInfo, string?
local function getSelectedRecipeRequirements(fabricator)
	-- local openFabricator, err = getOpenFabricator()
	-- if err then return nil, err end
	-- local fabricator = openFabricator.fabricator

	local selectedRecipe = fabricator.SelectedItem
	if not selectedRecipe then return nil, "No selected recipe found" end

	local requiredItems = {}
	for requiredItem in selectedRecipe.RequiredItems do
		local itemInfo = {
			amount = tonumber(requiredItem.Amount),
			minCondition = tonumber(requiredItem.MinCondition),
			maxCondition = tonumber(requiredItem.MaxCondition),
			prefabs = {}
		}

		for prefab in requiredItem.ItemPrefabs do
			itemInfo.prefabs[#itemInfo.prefabs + 1] = prefab.Identifier.Value
		end

		requiredItems[#requiredItems + 1] = itemInfo
	end

	return {
		targetItem = {
			identifier = selectedRecipe.TargetItem.Identifier,
			name = selectedRecipe.TargetItem.Name,
			amount = selectedRecipe.Amount
		},
		requiredItems = requiredItems
	}
end

---@param character Barotrauma.Character
local function tryStackFabricator(character)
	---@type Barotrauma.CharacterInventory
	local inventory = character.Inventory
	if not inventory then
		MyModGlobal.debugPrint("Character inventory is nil.")
		return
	end
	---@type Barotrauma.ItemInventory.Slot
	local bagSlot = inventory.slots[MyModGlobal.BAG_SLOT]
	if not bagSlot then
		MyModGlobal.debugPrint("Bag slot not found.")
		return
	end
	if #bagSlot.items == 0 then
		MyModGlobal.debugPrint("Bag slot is empty.")
		return
	end
	---@type Barotrauma.Item
	local bagItem = bagSlot.items[1]
	if not bagItem then
		MyModGlobal.debugPrint("Bag item not found.")
		return
	end

	local fabricator, err = getOpenFabricator()
	if err then
		print(string.format("Error getting open fabricator: %s", err))
		return
	end

	local recipe
	recipe, err = getSelectedRecipeRequirements(fabricator.fabricator)
	if err then
		print(string.format("Error getting selected recipe requirements: %s", err))
		return
	end
	-- MyModGlobal.DumpTable(recipe)

	-- TODO: Maybe make it so every press cycles the input
	-- For recipes that have multiple prefabs
	-- But then again what if it has 3 items with 4 prefabs each..
	-- Is that 4 iterations or 3*4 iterations?
	-- We can not use #toFind because we can remove 0th item
	-- Which means it's no longer contiguous
	-- Which means #toFind returns 0
	local toFind = recipe.requiredItems
	local remaining = #toFind
	---@type Barotrauma.Item[]
	local toGet = {}
	---@type fun(item: Barotrauma.Item): boolean, boolean
	local filter = function(item)
		-- TODO: Take into account minCondition and maxCondition
		local found = false
		-- MyModGlobal.DumpTable(toFind)
		-- toFind are all items we need to find
		for i, itemInfo in pairs(toFind) do
			-- prefabs are all items that satisfy the requirements
			for _, prefab in ipairs(itemInfo.prefabs) do
				-- MyModGlobal.debugPrint(string.format("Checking %s against %s", item.Prefab.Identifier.Value, prefab))
				if item.Prefab.Identifier.Value == prefab then
					-- MyModGlobal.debugPrint(string.format("That'll do %s %s", item.Prefab.Identifier.Value, prefab))
					toGet[#toGet + 1] = item
					itemInfo.amount = itemInfo.amount - 1
					found = true
					break
				end
			end
			if itemInfo.amount <= 0 then
				-- MyModGlobal.debugPrint(string.format("Removing %s from toFind", itemInfo.prefabs[1]))
				toFind[i] = nil
				remaining = remaining - 1
			end
			if found then break end
		end
		-- MyModGlobal.DumpTable(toFind)
		-- MyModGlobal.debugPrint(string.format("Found %s %s", item.Prefab.Identifier.Value, tostring(remaining)))
		return found, remaining == 0
	end
	-- dump(itemsOnSubmarine)
	-- MyModGlobal.DumpTable(toGet)

	local items, _ = utils.enqueueAllOwnedItems({}, filter)
	-- if err then
	-- 	print(string.format("Error enqueueing all owned items: %s", err))
	-- 	return
	-- end
	-- dump(items)

	-- MyModGlobal.DumpTable(items)
	-- TODO: This might explode... Oh well?
	local inputInventory = fabricator.item.OwnInventories[1]
	for iinventory in fabricator.item.OwnInventories do
		if #iinventory.slots > 1 then
			inputInventory = iinventory
			break
		end
	end

	local slot = -1
	local previous = nil
	for _, item in ipairs(items) do
		if previous ~= item.Prefab.Identifier then slot = slot + 1 end
		local moved = inputInventory.TryPutItem(item, slot, false, true, nil)
		if not moved then
			MyModGlobal.debugPrint(string.format("Failed to move %s", item.Prefab.Identifier.Value))
		end
		previous = item.Prefab.Identifier
	end
end

return {
	tryStackFabricator = tryStackFabricator
}
