local _, ns = ...
local L = ns.L or {}

local FEATURE_ID = "auto_sell_junk"
local MERCHANT_OPEN_DELAY_SECONDS = 0.15
local SELL_STEP_DELAY_SECONDS = 0.08
local POOR_QUALITY = Enum and Enum.ItemQuality and Enum.ItemQuality.Poor or 0
local QUEST_ITEM_CLASS_ID = Enum and Enum.ItemClass and (Enum.ItemClass.Questitem or Enum.ItemClass.QuestItem) or nil

local AutoSellJunkRuntime = {
    enabled = false,
    selling = false,
    scanToken = 0,
    sellToken = 0,
    queue = nil,
    soldCount = 0,
    soldValue = 0,
}

local function merchantFrameVisible()
    if not MerchantFrame or type(MerchantFrame.IsVisible) ~= "function" or not MerchantFrame:IsVisible() then
        return false
    end
    if type(MerchantFrame.IsForbidden) == "function" and MerchantFrame:IsForbidden() then
        return false
    end
    return true
end

local function getMerchantItemCount()
    if C_MerchantFrame and type(C_MerchantFrame.GetNumItems) == "function" then
        local ok, count = pcall(C_MerchantFrame.GetNumItems)
        if ok and tonumber(count) then
            return tonumber(count)
        end
    end

    if type(GetMerchantNumItems) == "function" then
        local ok, count = pcall(GetMerchantNumItems)
        if ok and tonumber(count) then
            return tonumber(count)
        end
    end

    return nil
end

local function merchantHasFirstItem()
    if C_MerchantFrame and type(C_MerchantFrame.GetItemInfo) == "function" then
        local ok, info = pcall(C_MerchantFrame.GetItemInfo, 1)
        if ok and type(info) == "table" and info.name ~= nil then
            return true
        end
    end

    if type(GetMerchantItemInfo) == "function" then
        local ok, name = pcall(GetMerchantItemInfo, 1)
        if ok and name ~= nil then
            return true
        end
    end

    return false
end

local function merchantCanBuyFromPlayer()
    if not merchantFrameVisible() then
        return false
    end

    if type(CanMerchantRepair) == "function" then
        local ok, canRepair = pcall(CanMerchantRepair)
        if ok and canRepair == true then
            return true
        end
    end

    local merchantItems = getMerchantItemCount()
    if merchantItems ~= nil then
        return merchantItems > 0
    end

    return merchantHasFirstItem()
end

local function formatMoney(copper)
    copper = math.max(0, math.floor(tonumber(copper) or 0))

    if C_CurrencyInfo and type(C_CurrencyInfo.GetCoinTextureString) == "function" then
        local ok, text = pcall(C_CurrencyInfo.GetCoinTextureString, copper, 12)
        if ok and type(text) == "string" and text ~= "" then
            return text
        end
    end

    if type(GetMoneyString) == "function" then
        local ok, text = pcall(GetMoneyString, copper)
        if ok and type(text) == "string" and text ~= "" then
            return text
        end
    end

    return tostring(copper) .. "c"
end

local function getItemStaticInfo(itemLinkOrID)
    if itemLinkOrID == nil then
        return nil
    end

    if type(GetItemInfo) == "function" then
        local ok, name, link, quality, _, _, _, _, _, icon, sellPrice, classID = pcall(GetItemInfo, itemLinkOrID)
        if ok then
            return {
                name = name,
                link = link,
                quality = quality,
                icon = icon,
                sellPrice = sellPrice,
                classID = classID,
            }
        end
    end

    if C_Item and type(C_Item.GetItemInfo) == "function" then
        local ok, name, link, quality, _, _, _, _, _, icon, sellPrice, classID = pcall(C_Item.GetItemInfo, itemLinkOrID)
        if ok then
            return {
                name = name,
                link = link,
                quality = quality,
                icon = icon,
                sellPrice = sellPrice,
                classID = classID,
            }
        end
    end

    return nil
end

local function getContainerItemInfo(bag, slot)
    if not C_Container or type(C_Container.GetContainerItemInfo) ~= "function" then
        return nil
    end

    local ok, info = pcall(C_Container.GetContainerItemInfo, bag, slot)
    if ok and type(info) == "table" then
        return info
    end
    return nil
end

local function getContainerItemID(bag, slot, info)
    if type(info) == "table" and tonumber(info.itemID) then
        return tonumber(info.itemID)
    end

    if C_Container and type(C_Container.GetContainerItemID) == "function" then
        local ok, itemID = pcall(C_Container.GetContainerItemID, bag, slot)
        if ok and tonumber(itemID) then
            return tonumber(itemID)
        end
    end

    return nil
end

local function isSellableJunkItem(bag, slot)
    local info = getContainerItemInfo(bag, slot)
    if not info or info.isLocked == true or info.hasNoValue == true then
        return nil
    end

    local itemID = getContainerItemID(bag, slot, info)
    local itemRef = info.hyperlink or itemID
    if itemRef == nil then
        return nil
    end

    local staticInfo = getItemStaticInfo(itemRef)
    local quality = tonumber(info.quality)
    if quality == nil and staticInfo then
        quality = tonumber(staticInfo.quality)
    end
    if quality ~= POOR_QUALITY then
        return nil
    end

    if staticInfo and QUEST_ITEM_CLASS_ID and tonumber(staticInfo.classID) == QUEST_ITEM_CLASS_ID then
        return nil
    end

    local sellPrice = staticInfo and tonumber(staticInfo.sellPrice) or 0
    if sellPrice <= 0 then
        return nil
    end

    local stackCount = math.max(1, tonumber(info.stackCount) or 1)
    return {
        bag = bag,
        slot = slot,
        itemID = itemID,
        link = info.hyperlink or (staticInfo and staticInfo.link),
        name = staticInfo and staticInfo.name or tostring(itemID or ""),
        stackCount = stackCount,
        value = sellPrice * stackCount,
    }
end

local function buildSellQueue()
    local queue = {}
    if not C_Container or type(C_Container.GetContainerNumSlots) ~= "function" then
        return queue
    end

    local lastBag = tonumber(NUM_BAG_SLOTS) or 4
    for bag = 0, lastBag do
        local okSlots, slots = pcall(C_Container.GetContainerNumSlots, bag)
        slots = okSlots and tonumber(slots) or 0
        for slot = 1, slots do
            local entry = isSellableJunkItem(bag, slot)
            if entry then
                queue[#queue + 1] = entry
            end
        end
    end

    return queue
end

local function notifySold(count, value)
    if not ns.Print or count <= 0 or value <= 0 then
        return
    end

    ns:Print(string.format(
        L.FEATURE_AUTO_SELL_JUNK_SOLD_FORMAT or "Sold %d junk items for %s.",
        count,
        formatMoney(value)
    ))
end

function AutoSellJunkRuntime:FinishSelling()
    local soldCount = tonumber(self.soldCount) or 0
    local soldValue = tonumber(self.soldValue) or 0

    self.selling = false
    self.queue = nil
    self.soldCount = 0
    self.soldValue = 0

    notifySold(soldCount, soldValue)
end

function AutoSellJunkRuntime:SellNext(token)
    if token ~= self.sellToken or not self.enabled or not self.selling then
        return
    end

    if not C_Container or type(C_Container.UseContainerItem) ~= "function" then
        self:FinishSelling()
        return
    end

    if not merchantCanBuyFromPlayer() then
        self:FinishSelling()
        return
    end

    local entry = self.queue and table.remove(self.queue, 1) or nil
    if not entry then
        self:FinishSelling()
        return
    end

    local currentEntry = isSellableJunkItem(entry.bag, entry.slot)
    if currentEntry then
        local ok = pcall(C_Container.UseContainerItem, entry.bag, entry.slot)
        if ok then
            self.soldCount = (tonumber(self.soldCount) or 0) + currentEntry.stackCount
            self.soldValue = (tonumber(self.soldValue) or 0) + currentEntry.value
        end
    end

    if C_Timer and type(C_Timer.After) == "function" then
        C_Timer.After(SELL_STEP_DELAY_SECONDS, function()
            AutoSellJunkRuntime:SellNext(token)
        end)
    else
        self:SellNext(token)
    end
end

function AutoSellJunkRuntime:StartSelling()
    if not self.enabled or self.selling then
        return
    end
    if type(InCombatLockdown) == "function" and InCombatLockdown() then
        return
    end
    if not merchantCanBuyFromPlayer() then
        return
    end

    local queue = buildSellQueue()
    if #queue == 0 then
        return
    end

    self.queue = queue
    self.soldCount = 0
    self.soldValue = 0
    self.selling = true
    self.sellToken = self.sellToken + 1
    self:SellNext(self.sellToken)
end

function AutoSellJunkRuntime:QueueSelling(delay)
    if not self.enabled then
        return
    end

    self.scanToken = self.scanToken + 1
    local token = self.scanToken
    local function runIfCurrent()
        if token == AutoSellJunkRuntime.scanToken then
            AutoSellJunkRuntime:StartSelling()
        end
    end

    if C_Timer and type(C_Timer.After) == "function" then
        C_Timer.After(tonumber(delay) or MERCHANT_OPEN_DELAY_SECONDS, runIfCurrent)
    else
        runIfCurrent()
    end
end

function AutoSellJunkRuntime:EnsureEventFrame()
    if self.eventFrame then
        return self.eventFrame
    end

    self.eventFrame = CreateFrame("Frame")
    self.eventFrame:SetScript("OnEvent", function(_, event)
        if event == "MERCHANT_SHOW" then
            AutoSellJunkRuntime:QueueSelling(MERCHANT_OPEN_DELAY_SECONDS)
        elseif event == "MERCHANT_CLOSED" then
            AutoSellJunkRuntime.sellToken = AutoSellJunkRuntime.sellToken + 1
            AutoSellJunkRuntime.selling = false
            AutoSellJunkRuntime.queue = nil
            AutoSellJunkRuntime.soldCount = 0
            AutoSellJunkRuntime.soldValue = 0
        end
    end)

    return self.eventFrame
end

function AutoSellJunkRuntime:SetEnabled(enabled)
    enabled = enabled == true
    self.enabled = enabled

    local eventFrame = self:EnsureEventFrame()
    eventFrame:UnregisterAllEvents()

    if not enabled then
        self.sellToken = self.sellToken + 1
        self.selling = false
        self.queue = nil
        return
    end

    eventFrame:RegisterEvent("MERCHANT_SHOW")
    eventFrame:RegisterEvent("MERCHANT_CLOSED")

    if merchantFrameVisible() then
        self:QueueSelling(0.05)
    end
end

ns:RegisterFeatureDefinition(FEATURE_ID, {
    id = FEATURE_ID,
    order = 23,
    category = "inventory",
    title = function() return L.FEATURE_AUTO_SELL_JUNK_TITLE or "Auto-Sell Plunder" end,
    description = function() return L.FEATURE_AUTO_SELL_JUNK_DESCRIPTION or "Automatically sells poor-quality junk when a normal merchant can buy items." end,
    note = function() return L.FEATURE_AUTO_SELL_JUNK_NOTE or "Only sells gray poor-quality items with vendor value. Skips quest items and merchants that do not expose a normal sellable merchant frame." end,
    previewLabel = function() return L.FEATURE_AUTO_SELL_JUNK_PREVIEW_LABEL or "Merchant" end,
    previewNote = function() return L.FEATURE_AUTO_SELL_JUNK_PREVIEW_NOTE or "Open a valid merchant and Vaultloom sells gray junk from your bags in a short safe queue." end,
    requiresReload = false,
    apply = function(isEnabled)
        AutoSellJunkRuntime:SetEnabled(isEnabled == true)
    end,
})
