local _, ns = ...
local SlickUI = ns.SlickUI

local VendorAutomation = {
	Selling = false,
}
SlickUI.VendorAutomation = VendorAutomation

VendorAutomation.RepairFundLabels = {
	personal = "Personal Funds",
	guild = "Guild Funds Only",
	guildFirst = "Guild First, Then Personal",
	personalFirst = "Personal First, Then Guild",
}

local function SafeNumber(value)
	if canaccessvalue and not canaccessvalue(value) then return nil end
	if issecretvalue and issecretvalue(value) then return nil end
	return type(value) == "number" and value or nil
end

local function SafeBoolean(value)
	if canaccessvalue and not canaccessvalue(value) then return false end
	if issecretvalue and issecretvalue(value) then return false end
	return value == true
end

local function Settings()
	return SlickUI.DB.vendorAutomation
end

local function MoneyText(value)
	return GetCoinTextureString(math.max(0, math.floor(SafeNumber(value) or 0)))
end

local function MerchantIsOpen()
	return MerchantFrame and MerchantFrame:IsShown() == true
end

local function PoorQuality()
	return Enum and Enum.ItemQuality and Enum.ItemQuality.Poor or 0
end

local function GetSellPrice(itemID)
	if not C_Item or not C_Item.GetItemInfo then return nil end
	return SafeNumber(select(11, C_Item.GetItemInfo(itemID)))
end

function VendorAutomation:BuildGreyQueue()
	local queue = {}
	local lastBag = NUM_TOTAL_EQUIPPED_BAG_SLOTS or NUM_BAG_SLOTS or 4
	local poorQuality = PoorQuality()

	for bag = 0, lastBag do
		local slots = SafeNumber(C_Container.GetContainerNumSlots(bag)) or 0
		for slot = 1, slots do
			local info = C_Container.GetContainerItemInfo(bag, slot)
			if info and not info.isLocked and info.quality == poorQuality and not info.hasNoValue and info.itemID then
				local count = SafeNumber(info.stackCount) or 1
				local sellPrice = GetSellPrice(info.itemID)
				if sellPrice and sellPrice > 0 then
					queue[#queue + 1] = {
						bag = bag,
						slot = slot,
						itemID = info.itemID,
						link = info.hyperlink or (C_Item.GetItemLinkByID and C_Item.GetItemLinkByID(info.itemID)) or ("Item " .. info.itemID),
						count = count,
						value = sellPrice * count,
					}
				end
			end
		end
	end
	return queue
end

function VendorAutomation:ReportSales(sales, totalCount, totalValue)
	if totalCount <= 0 then return end
	for _, sale in ipairs(sales) do
		SlickUI:Print("Sold " .. sale.link .. " x" .. sale.count .. " for " .. MoneyText(sale.value) .. ".")
	end
	SlickUI:Print("Grey-item sales: " .. totalCount .. " item" .. (totalCount == 1 and "" or "s") .. " sold for " .. MoneyText(totalValue) .. " total.")
end

function VendorAutomation:SellGreys(callback)
	if self.Selling then return end
	local queue = self:BuildGreyQueue()
	if #queue == 0 then
		if callback then callback() end
		return
	end

	self.Selling = true
	local index, totalCount, totalValue = 1, 0, 0
	local sales = {}
	local function Finish(interrupted)
		VendorAutomation.Selling = false
		VendorAutomation:ReportSales(sales, totalCount, totalValue)
		if interrupted then SlickUI:Print("Grey-item selling stopped because the merchant window closed.") end
		if callback then C_Timer.After(0.20, callback) end
	end
	local function SellNext()
		if not MerchantIsOpen() then Finish(true) return end
		local sale = queue[index]
		if not sale then Finish(false) return end
		local info = C_Container.GetContainerItemInfo(sale.bag, sale.slot)
		if info and info.itemID == sale.itemID and info.quality == PoorQuality() then
			local succeeded = pcall(C_Container.UseContainerItem, sale.bag, sale.slot)
			if succeeded then
				totalCount = totalCount + sale.count
				totalValue = totalValue + sale.value
				sales[#sales + 1] = sale
			end
		end
		index = index + 1
		C_Timer.After(0.08, SellNext)
	end
	SellNext()
end

function VendorAutomation:CanAttemptGuildRepair()
	if type(CanGuildBankRepair) ~= "function" or not SafeBoolean(CanGuildBankRepair()) then return false end
	if type(GetGuildBankWithdrawMoney) ~= "function" then return true end
	local allowance = SafeNumber(GetGuildBankWithdrawMoney())
	-- A protected/secret allowance cannot be compared safely. In that case let
	-- Blizzard attempt the repair and verify the remaining cost afterward.
	return allowance == nil or allowance == -1 or allowance > 0
end

function VendorAutomation:Repair()
	if not Settings().autoRepair or not MerchantIsOpen() then return end
	if type(CanMerchantRepair) ~= "function" or not SafeBoolean(CanMerchantRepair()) then return end
	local cost = SafeNumber(GetRepairAllCost())
	if not cost or cost <= 0 then return end

	local mode = Settings().repairFunds
	local guildAvailable = self:CanAttemptGuildRepair()
	local primaryGuild = mode == "guild" or mode == "guildFirst"
	local fallbackGuild = mode == "personalFirst" and guildAvailable
	local allowFallback = mode == "guildFirst" or mode == "personalFirst"

	if mode == "guild" and not guildAvailable then
		SlickUI:Print("Automatic repair skipped: guild repair funds or permission are unavailable. Repair cost: " .. MoneyText(cost) .. ".")
		return
	end
	if primaryGuild and not guildAvailable then primaryGuild = false end

	local function VerifyFinal(primaryWasGuild, fallbackWasUsed, fallbackCost)
		if not MerchantIsOpen() then return end
		local remaining = SafeNumber(GetRepairAllCost())
		if remaining and remaining <= 0 then
			if fallbackWasUsed then
				local firstName = primaryWasGuild and "Guild" or "Personal"
				local secondName = primaryWasGuild and "personal" or "guild"
				SlickUI:Print(firstName .. " funds could not cover the full repair. Paid the remaining " .. MoneyText(fallbackCost) .. " from " .. secondName .. " funds.")
			else
				SlickUI:Print("Repaired all items for " .. MoneyText(cost) .. " using " .. (primaryWasGuild and "guild" or "personal") .. " funds.")
			end
		elseif remaining then
			SlickUI:Print("Automatic repair could not cover the remaining " .. MoneyText(remaining) .. " from the selected funding sources.")
		end
	end

	RepairAllItems(primaryGuild)
	C_Timer.After(0.25, function()
		if not MerchantIsOpen() then return end
		local remaining = SafeNumber(GetRepairAllCost())
		if not remaining or remaining <= 0 then
			VerifyFinal(primaryGuild, false)
			return
		end

		local useFallback = allowFallback and ((primaryGuild and true) or fallbackGuild)
		if not useFallback then
			VerifyFinal(primaryGuild, false)
			return
		end

		RepairAllItems(not primaryGuild)
		C_Timer.After(0.25, function()
			VerifyFinal(primaryGuild, true, remaining)
		end)
	end)
end

function VendorAutomation:ProcessMerchant()
	if Settings().sellGreys then
		self:SellGreys(function() VendorAutomation:Repair() end)
	else
		self:Repair()
	end
end

function VendorAutomation:Initialize()
	local frame = CreateFrame("Frame")
	frame:RegisterEvent("MERCHANT_SHOW")
	frame:SetScript("OnEvent", function()
		C_Timer.After(0.10, function() VendorAutomation:ProcessMerchant() end)
	end)
	self.EventFrame = frame
end

SlickUI:RegisterModule("VendorAutomation", VendorAutomation)
