local game = Game()
local blacklist
local holdMapDesc
local currentPlayer
EID.InsideItemReminder = false

-- Simple function to help with adding properly formatted sections to the desc
local function append(icon, title, newDesc)
	holdMapDesc = holdMapDesc .. (icon or "{{Blank}}") .. " {{ColorEIDObjName}}" .. title .. "#" .. newDesc .. "#"
end

-- Simple function to quickly get an item's RNG seed. We have no use for the RNG object itself because every other function it can do will advance the item's RNG, altering the game state
local function getSeed(id, variant)
	if variant == nil or variant == 100 then return currentPlayer:GetCollectibleRNG(id):GetSeed()
	elseif variant == 350 then return currentPlayer:GetTrinketRNG(id):GetSeed()
	elseif variant == 300 then return currentPlayer:GetCardRNG(id):GetSeed()
	elseif variant == 70 then return currentPlayer:GetPillRNG(id):GetSeed() end
end

-- Helper function to easily add an item's stock description to the desc
-- Don't use if you need to customize the desc! (Like Sanguine Bond's result highlighting)
-- extraIcon is for when we want a special icon before the line (like {{Dice Bag Icon}} {{Dice Item Icon}})
local variantToName = { [70] = "Pill", [100] = "Collectible", [300] = "Card", [350] = "Trinket" }
local function addObjectDesc(type, variant, subtype, extraIcon)
	local objectID = type .. "." .. variant .. "." .. subtype
	if not blacklist[objectID] then
		blacklist[objectID] = true
		local demoDescObj = EID:getDescriptionObj(type, variant, subtype)
		local iconString = "{{" .. variantToName[variant] .. subtype .. "}}"
		if extraIcon then iconString = extraIcon .. " " .. iconString end
		append(iconString, demoDescObj.Name, demoDescObj.Description)
	end
end

-- Data Tables --
-- Rainbow Worm's trinket IDs it grants, in order
local rainbowWormEffects = { [0] = 9, 11, 65, 27, 10, 12, 26, 66, 96, 144 }
-- Mysterious Paper does not play well with displaying Error 404's effect
local mysteriousPaperBlacklist = { [23] = true, [48] = true }


function EID:getHoldMapDescription(player, checkingTwin)
	EID.InsideItemReminder = true
	-- Starting Blacklist: Recall, Hold
	blacklist = { ["5.100.714"] = true, ["5.100.715"] = true, }
	holdMapDesc = ""

	currentPlayer = player
	
	-- TODO:
	-- crooked penny cheats. 404/liberty cap/broken syringe/etc. "what item is it"
	-- pandora's box? it shows the whole desc which is kinda useful but too big
	
	-- Tainted ??? Poop Descriptions
	if EID.isRepentance and EID.Config["ItemReminderShowPoopDesc"] > 0 and player:GetPlayerType() == 25 then
		for i = 0, EID.Config["ItemReminderShowPoopDesc"]-1 do
			local poopInfo = EID:getDescriptionEntry("poopSpells")
			local nextPoop = player:GetPoopSpell(i)
			append("{{PoopSpell" .. nextPoop .. "}}", poopInfo[nextPoop][1], poopInfo[nextPoop][2])
		end
	end

	-- Recently Acquired Item Descriptions
	if EID.Config["ItemReminderShowRecentItem"] > 0 then
		local printedItems = 0
		local playerNum = EID:getPlayerID(player)
		if EID.RecentlyTouchedItems[playerNum] then
			for i = #EID.RecentlyTouchedItems[playerNum], 1, -1 do
				if printedItems >= EID.Config["ItemReminderShowRecentItem"] then break end
				printedItems = printedItems + 1
				local recentID = EID.RecentlyTouchedItems[playerNum][i] % 4294967296
				addObjectDesc(5, 100, recentID)
			end
		end
	end
	
	-- Echo Chamber Description
	if EID.isRepentance and player:HasCollectible(700) then
		local playerID = EID:getPlayerID(player)
		local pickupHistory = EID.PlayerItemInteractions[playerID].pickupHistory
		-- Dead Tainted Lazarus exception
		if player:GetPlayerType() == 38 then
			pickupHistory = EID.PlayerItemInteractions[playerID].altPickupHistory or pickupHistory
		end
		if pickupHistory then
			local pickupNames = ""
			local pickupsToPrint = 3
			for i = 1, #pickupHistory do
				if pickupHistory[i][4] then -- Echo chamber was owned before this card/pill was used and it's not a mimic usage
					if pickupHistory[i][1] == "pill" then
						local name = EID:getPillName(pickupHistory[i][3], false)
						if (pickupHistory[i][2] == 14) then name = EID:getPillName(9999, false) end
						pickupNames = pickupNames .. "{{Pill" .. pickupHistory[i][2] .. "}} " .. name .. "#"
						pickupsToPrint = pickupsToPrint - 1
					else
						local name = EID:getObjectName(5, 300, pickupHistory[i][3])
						pickupNames = pickupNames .. "{{Card" .. pickupHistory[i][3] .. "}} " .. name .. "#"
						pickupsToPrint = pickupsToPrint - 1
					end
					if (pickupsToPrint == 0) then break end
				end
			end
			if pickupNames ~= "" then
				append("{{Collectible700}}", EID:getObjectName(5, 100, 700), pickupNames)
			end
		end
	end
	
	-- Bag of Crafting
	if EID.isRepentance and player:HasCollectible(710) then
		local floorQuery = EID.BoC.FloorOverride or EID.BoC.FloorQuery
		local inventoryQuery= EID.BoC.InventoryOverride or EID.BoC.InventoryQuery
		local bagItems = EID.BoC.BagItemsOverride or EID.BoC.BagItems
		local total = #floorQuery + #inventoryQuery+#bagItems
		local text = total.." of 8 items available"
		if total < 8 then text = text .. "#{{Warning}} Needs at least 8 to show crafting recipes!" end
		blacklist["5.100.710"] = true
		append("{{Collectible710}}", EID:getObjectName(5,100,710), text)
	end
	
	-- Active Item Descriptions
	if EID.Config["ItemReminderShowActiveDesc"] > 0 then
		for i = 0, EID.Config["ItemReminderShowActiveDesc"]-1 do
			-- the modulo is to convert negative IDs (glitched items) to positive IDs
			local heldActive = player:GetActiveItem(i) % 4294967296
			if heldActive > 0 and not blacklist["5.100." .. heldActive] then
				-- Metronome result
				if heldActive == 488 and EID.Config["ItemReminderShowRNGCheats"] then
					blacklist["5.100.488"] = true
					addObjectDesc(5, 100, EID:MetronomePrediction(getSeed(488)), "{{Collectible488}}")
				-- Teleport! location
				elseif heldActive == 44 and EID.Config["ItemReminderShowRNGCheats"] then
					blacklist["5.100.44"] = true
					-- The result preview changes as soon as we activate Teleport, which looks awkward, so try to not display the result while mid-teleport. Doesn't work perfectly and only in Rep
					if not EID.isRepentance or player:GetSprite():GetAnimation() ~= "TeleportUp" then
						append("{{Collectible44}}", EID:getObjectName(5,100,44) .. EID:getDescriptionEntry("HoldMapHeader"), EID:Teleport1Prediction(getSeed(44)))
					end
				-- Teleport 2.0 location
				elseif heldActive == 419 then
					blacklist["5.100.419"] = true
					append("{{Collectible419}}", EID:getObjectName(5,100,419) .. EID:getDescriptionEntry("HoldMapHeader"), EID:Teleport2Prediction())
				-- D Infinity
				elseif heldActive == 489 then
					blacklist["5.100.489"] = true
					addObjectDesc(5, 100, EID:CurrentDInfinity(getSeed(489), player), "{{Collectible489}}")
				-- D1
				elseif heldActive == 476 and EID.Config["ItemReminderShowRNGCheats"] then
					blacklist["5.100.476"] = true
					append("{{Collectible476}}", EID:getObjectName(5,100,476) .. EID:getDescriptionEntry("HoldMapHeader"), EID:D1Prediction(getSeed(476)))
				-- Void
				elseif heldActive == 477 then
					local absorbedActives = ""
					local printNormalDesc = true
					local absorbedItems = EID.absorbedItems[tostring(EID:getPlayerID(player))]
					if absorbedItems then
						local countItems = 0
						for _, _ in pairs(absorbedItems) do
							countItems = countItems + 1
						end
						if countItems > 0 then
							if countItems > 5 then
								absorbedActives = "{{Blank}} "
							end
							for k, _ in pairs(absorbedItems) do
								absorbedActives = absorbedActives .. "{{Collectible" .. k .. "}} "
								if countItems <= 5 then
									absorbedActives = absorbedActives .. EID:getObjectName(5, 100, tonumber(k)) .. "#"
								end
							end
							blacklist["5.100.477"] = true
							append("{{Collectible477}}", EID:getObjectName(5, 100, 477) .. EID:getDescriptionEntry("HoldMapHeader"), absorbedActives)
							printNormalDesc = false
						end
					end
					if printNormalDesc then addObjectDesc(5, 100, heldActive) end
				else
					addObjectDesc(5, 100, heldActive)
				end
			end
		end
	end
	
	-- Pocket Item Descriptions
	-- Annoying because there's no easy way to just get the info of a slot
	if EID.Config["ItemReminderShowPocketDesc"] > 0 then
		-- I don't think we can actually know what slot the player is on, so, save these to display (if they exist) for when Card and Pill in a slot are both 0, to attempt to always show them in slot order
		local dicePrinted = false
		local diceBag = EID.isRepentance and player:GetActiveItem(3) or 0
		local pocketPrinted = false
		local pocketActive = EID.isRepentance and player:GetActiveItem(2) or 0
		for i = 0, EID.Config["ItemReminderShowPocketDesc"]-1 do
			local heldCard = player:GetCard(i)
			local heldPill = player:GetPill(i)
			if heldCard > 0 then
				addObjectDesc(5, 300, heldCard)
			elseif heldPill > 0 then
				-- Check if our held pill is identified
				EID.pillPlayer = player
				local identified = game:GetItemPool():IsPillIdentified(heldPill)
				if EID.isRepentance and heldPill % PillColor.PILL_GIANT_FLAG == PillColor.PILL_GOLD then identified = true end
				if (identified or EID.Config["ShowUnidentifiedPillDescriptions"]) then
					addObjectDesc(5, 70, heldPill)
				end
				EID.pillPlayer = nil
			elseif diceBag > 0 and not dicePrinted then
				dicePrinted = true
				addObjectDesc(5, 100, diceBag, "{{Trinket154}}")
			elseif pocketActive > 0 and not pocketPrinted then
				pocketPrinted = true
				addObjectDesc(5, 100, pocketActive)
				-- we'll have to add tainted char specific text for their actives with unique effects for that character!
			end
		end
	end
	
	-- Trinket Descriptions
	if EID.Config["ItemReminderShowTrinketDesc"] > 0 then
		for t = 0, EID.Config["ItemReminderShowTrinketDesc"]-1 do
			-- account for Golden Trinket IDs
			local heldTrinketTrue = player:GetTrinket(t)
			local heldTrinket = heldTrinketTrue
			if EID.isRepentance then heldTrinket = heldTrinketTrue & TrinketType.TRINKET_ID_MASK end
			if heldTrinket > 0 and not blacklist["5.350." .. heldTrinket] then
				if heldTrinket == 4 then
					-- Broken Remote has two possible effects depending on if its doubled
					if player:GetTrinketMultiplier(heldTrinket) > 1 then
						-- Teleport 2.0
						blacklist["5.350.4"] = true
						append("{{Trinket4}}", EID:getObjectName(5,350,4) .. EID:getDescriptionEntry("HoldMapHeader"), EID:Teleport2Prediction())
					elseif EID.Config["ItemReminderShowRNGCheats"] then
						-- Teleport (requires cheating option on)
						blacklist["5.350.4"] = true
						if not EID.isRepentance or player:GetSprite():GetAnimation() ~= "TeleportUp" then
							append("{{Trinket4}}", EID:getObjectName(5,350,4) .. EID:getDescriptionEntry("HoldMapHeader"), EID:Teleport1Prediction(getSeed(44)))
						end
					end
				-- Rainbow Worm
				elseif EID.Config["ItemReminderShowHiddenInfo"] and heldTrinket == 64 then
					blacklist["5.350.64"] = true
					local rainbowWormEffect = rainbowWormEffects[math.floor(game.TimeCounter / 30 / 3) % (EID.isRepentance and 10 or 8)]
					addObjectDesc(5, 350, rainbowWormEffect, "{{Trinket64}}")
				-- 404 Error
				-- Unfortunately, includes other temporary trinket givers, such as Glitched Items. We'd need to predict 404's result using RNG to actually know which it specifically is granting
				-- And unfortunately, HasTrinket can't differentiate between real and fake trinkets in AB+
				elseif EID.Config["ItemReminderShowHiddenInfo"] and EID.isRepentance and heldTrinket == 75 then
					blacklist["5.350.75"] = true
					-- Don't display Mysterious Paper's 1-frame temporary trinket granting
					local hasPaper = player:HasTrinket(21)
					for i = 1, TrinketType.NUM_TRINKETS - 1 do
						local tempTrinketFound = player:HasTrinket(i, true) ~= player:HasTrinket(i, false)
						if tempTrinketFound and (not mysteriousPaperBlacklist[i] or not hasPaper) then
							addObjectDesc(5, 350, i, "{{Trinket75}}")
						end
					end
				-- Modeling Clay
				elseif heldTrinket == 166 then
					local modelingClayItem = player:GetModelingClayEffect()
					if modelingClayItem > 0 then
						addObjectDesc(5, 100, modelingClayItem, "{{Trinket166}}")
						blacklist["5.350.166"] = true
					else
						addObjectDesc(5, 350, heldTrinket)
					end
				else
					addObjectDesc(5, 350, heldTrinket)
				end
				
			end
		end
		-- Gulped/unslotted Modeling Clay
		-- (Hidden information, because Modeling Clay does not visually show its item when gulped)
		if EID.isRepentance and EID.Config["ItemReminderShowHiddenInfo"] and not blacklist["5.350.166"] and player:GetModelingClayEffect() > 0 then
			local modelingClayItem = player:GetModelingClayEffect()
			if modelingClayItem > 0 then
				addObjectDesc(5, 100, modelingClayItem, "{{Trinket166}}")
				blacklist["5.350.166"] = true
			end
		end
	end
	
	-- Zodiac's effect for the current floor
	if EID.Config["ItemReminderShowHiddenInfo"] and player:HasCollectible(CollectibleType.COLLECTIBLE_ZODIAC) then
		local zodiacItem = player:GetZodiacEffect()
		if zodiacItem > 0 then
			addObjectDesc(5, 100, zodiacItem, "{{Collectible392}}")
			blacklist["5.100.392"] = true
		end
	end
	
	-- Finally, check the twin player of this controller
	-- If both twins have a desc, show their player icon / name to separate the two descs
	if EID.isRepentance and not checkingTwin then
		local twin = player:GetOtherTwin()
		local mainTwinDesc = holdMapDesc
		local otherTwinDesc = ""
		if twin then otherTwinDesc = EID:getHoldMapDescription(twin, true) end
		if otherTwinDesc ~= "" then
			-- Only the other twin had a desc
			if mainTwinDesc == "" then holdMapDesc = otherTwinDesc
			else
				-- Both twins had a desc; merge them with player icon headers
				holdMapDesc = (EID:getIcon("Player"..player:GetPlayerType()) ~= EID.InlineIcons["ERROR"] and "{{Player"..player:GetPlayerType().."}}" or "{{CustomTransformation}}") .. " {{ColorGray}}" .. player:GetName() .. "#" .. mainTwinDesc .. "#"
				holdMapDesc = holdMapDesc .. (EID:getIcon("Player"..twin:GetPlayerType()) ~= EID.InlineIcons["ERROR"] and "{{Player"..twin:GetPlayerType().."}}" or "{{CustomTransformation}}") .. " {{ColorGray}}" .. twin:GetName() .. "#" .. otherTwinDesc
			end
		else
			-- Only the main twin had a desc
			holdMapDesc = mainTwinDesc
		end
	end
	
	EID.InsideItemReminder = false
	return holdMapDesc
end
