-- ============================================================
-- Asylum Compagnon — Toolbox.lua
-- Petites automatisations de confort (esprit Leatrix / Auto Sell Grey /
-- Auto Queue) :
--   · vente auto des objets gris chez un marchand
--   · réparation auto (fonds de guilde en priorité)
--   · butin automatique ultra-rapide (auto-loot forcé actif)
--   · acceptation auto des files (donjon / rôle / champ de bataille)
--   · journal de combat auto en donjon/raid (Warcraft Logs)
-- Chaque option s'active dans le menu Toolbox. Tout est pcall-gardé :
-- au pire ça ne fait rien, jamais d'erreur.
-- ============================================================

local ADDON_NAME, NS = ...

local TB = {}
NS.Toolbox = TB

local function on(key) return NS.db and NS.db[key] end
local function say(msg) if NS.Msg then NS.Msg(msg) end end

local function SafeCall(fn, ...)
    if type(fn) ~= "function" then return nil end
    local ok, a, b, c, d, e, f = pcall(fn, ...)
    if not ok then return nil end
    return a, b, c, d, e, f
end

-- ---------- Marchand : vente des gris + réparation ----------
local function SellGrey()
    if not on("tbAutoSell") then return end
    -- API moderne : vend toute la camelote (gris) d'un coup
    if C_MerchantFrame and C_MerchantFrame.SellAllJunkItems then
        pcall(C_MerchantFrame.SellAllJunkItems)
        return
    end
    -- repli : parcours des sacs
    if not (C_Container and C_Container.GetContainerNumSlots) then return end
    for bag = 0, (NUM_BAG_SLOTS or 4) do
        local slots = C_Container.GetContainerNumSlots(bag) or 0
        for slot = 1, slots do
            local i = C_Container.GetContainerItemInfo(bag, slot)
            if i and i.quality == 0 and not i.hasNoValue then
                pcall(C_Container.UseContainerItem, bag, slot)
            end
        end
    end
end

local function Repair()
    if not on("tbAutoRepair") then return end
    if not (CanMerchantRepair and CanMerchantRepair()) then return end
    local cost, canRepair = GetRepairAllCost()
    if not canRepair or not cost or cost <= 0 then return end
    -- fonds de guilde en priorité si l'utilisateur l'a choisi (et si dispo)
    local useGuild = not (NS.db and NS.db.tbRepairGuild == false)
    if useGuild and CanGuildBankRepair and CanGuildBankRepair() then
        local wd = GetGuildBankWithdrawMoney and GetGuildBankWithdrawMoney()
        if wd == -1 or (wd and wd >= cost) then
            pcall(RepairAllItems, true)
            say(NS.L("Équipement réparé (fonds de guilde)."))
            return
        end
    end
    if GetMoney() >= cost then
        pcall(RepairAllItems, false)
        say((NS.L("Équipement réparé : %s")):format(
            (GetCoinTextureString and GetCoinTextureString(cost)) or tostring(cost)))
    end
end

-- ---------- Butin automatique ultra-rapide ----------
-- Implémentation Retail volontairement minimale : pas de fenêtre ajoutée,
-- pas de son et pas d'auto-confirmation spéciale. LOOT_READY rend les données
-- disponibles avant l'affichage de la fenêtre Blizzard ; chaque emplacement
-- est alors pris sur un ticker de 33 ms, indépendamment du framerate.
local FAST_LOOT_INTERVAL = 0.033
local LOOT_SLOT_NONE = Enum and Enum.LootSlotType and Enum.LootSlotType.None or 0
local lootState = {
    ticker = nil,
    generation = 0,
    isLooting = false,
    lastNumLoot = nil,
    initialAutoLoot = nil,
    failed = false,
    frameHidden = false,
    originalParent = nil,
    settingCVar = false,
}
local hiddenLootParent = CreateFrame("Frame", nil, UIParent)
hiddenLootParent:Hide()

local function AutoLootCVarEnabled()
    local value = SafeCall(GetCVarBool, "autoLootDefault")
    if type(value) == "boolean" then return value end
    value = C_CVar and SafeCall(C_CVar.GetCVar, "autoLootDefault")
        or SafeCall(GetCVar, "autoLootDefault")
    return value == true or value == 1 or value == "1"
end

function TB.EnsureAutoLootEnabled()
    if not on("tbFastLoot") then return false end
    if AutoLootCVarEnabled() then return true end
    local setter = C_CVar and C_CVar.SetCVar or SetCVar
    if type(setter) ~= "function" or lootState.settingCVar then return false end
    lootState.settingCVar = true
    local ok = pcall(setter, "autoLootDefault", "1")
    lootState.settingCVar = false
    return ok and AutoLootCVarEnabled()
end

local function ControllableLootFrame()
    local frame = _G.LootFrame
    if not frame or type(frame.SetParent) ~= "function" then return nil end
    -- Si une UI tierce a repris LOOT_OPENED, ne pas toucher à son affichage.
    if type(frame.IsEventRegistered) == "function" then
        local registered = SafeCall(frame.IsEventRegistered, frame, "LOOT_OPENED")
        if registered == false then return nil end
    end
    return frame
end

local function HideDefaultLootFrame()
    if not on("tbFastLoot") or lootState.frameHidden then return end
    local frame = ControllableLootFrame()
    if not frame then return end
    lootState.originalParent = lootState.originalParent
        or SafeCall(frame.GetParent, frame) or UIParent
    if pcall(frame.SetParent, frame, hiddenLootParent) then
        lootState.frameHidden = true
    end
end

local function RestoreDefaultLootFrame(showNow)
    if not lootState.frameHidden then return end
    local frame = _G.LootFrame
    if not frame or type(frame.SetParent) ~= "function" then
        lootState.frameHidden = false
        return
    end
    pcall(frame.SetParent, frame, lootState.originalParent or UIParent)
    lootState.frameHidden = false
    if showNow then
        if type(frame.SetFrameStrata) == "function" then pcall(frame.SetFrameStrata, frame, "HIGH") end
        if type(frame.Show) == "function" then pcall(frame.Show, frame) end
    end
end

local function CancelFastLoot()
    lootState.generation = lootState.generation + 1
    local ticker = lootState.ticker
    lootState.ticker = nil
    if ticker and type(ticker.Cancel) == "function" then pcall(ticker.Cancel, ticker) end
end

local function LootOneSlot(slot)
    local slotType = SafeCall(GetLootSlotType, slot)
    if slotType == nil or slotType == LOOT_SLOT_NONE then return true end
    local _, _, _, _, _, locked = SafeCall(GetLootSlotInfo, slot)
    if locked then return false end
    return type(LootSlot) == "function" and pcall(LootSlot, slot)
end

local function HasLootRemaining()
    local count = SafeCall(GetNumLootItems)
    if type(count) ~= "number" then return false end
    for slot = 1, count do
        local slotType = SafeCall(GetLootSlotType, slot)
        if slotType ~= nil and slotType ~= LOOT_SLOT_NONE then return true end
    end
    return false
end

local function FinishFastLoot(generation)
    if generation ~= lootState.generation or not lootState.isLooting then return end
    lootState.ticker = nil
    if lootState.failed or HasLootRemaining() then
        -- Sacs pleins, objet verrouillé ou restriction : rendre immédiatement
        -- la fenêtre native, déjà alimentée par LOOT_OPENED.
        RestoreDefaultLootFrame(true)
    end
end

local function StartFastLoot(numItems)
    CancelFastLoot()
    lootState.failed = false
    local generation = lootState.generation
    local slot = numItems
    if not (C_Timer and C_Timer.NewTicker) then
        for index = numItems, 1, -1 do
            if not LootOneSlot(index) then lootState.failed = true end
        end
        FinishFastLoot(generation)
        return
    end
    lootState.ticker = C_Timer.NewTicker(FAST_LOOT_INTERVAL, function()
        if generation ~= lootState.generation or not lootState.isLooting then return end
        if slot >= 1 then
            if not LootOneSlot(slot) then lootState.failed = true end
            slot = slot - 1
        else
            CancelFastLoot()
            FinishFastLoot(lootState.generation)
        end
    end, numItems + 1)
end

local function FastLootRequested(autoLoot)
    if lootState.initialAutoLoot ~= nil then return lootState.initialAutoLoot end
    local modified = SafeCall(IsModifiedClick, "AUTOLOOTTOGGLE") == true
    lootState.initialAutoLoot = autoLoot == true
        or (autoLoot ~= true and AutoLootCVarEnabled() ~= modified)
    return lootState.initialAutoLoot
end

local function OnLootReady(autoLoot)
    if not on("tbFastLoot") then return end
    TB.EnsureAutoLootEnabled()
    lootState.isLooting = true
    HideDefaultLootFrame()
    local numItems = SafeCall(GetNumLootItems)
    if type(numItems) ~= "number" or numItems <= 0 or lootState.lastNumLoot == numItems then return end
    if FastLootRequested(autoLoot) then
        StartFastLoot(numItems)
    else
        -- La touche d'inversion d'auto-loot conserve son comportement natif.
        RestoreDefaultLootFrame(true)
    end
    lootState.lastNumLoot = numItems
end

local function OnLootClosed()
    CancelFastLoot()
    lootState.isLooting = false
    lootState.lastNumLoot = nil
    lootState.initialAutoLoot = nil
    lootState.failed = false
    if on("tbFastLoot") then HideDefaultLootFrame() else RestoreDefaultLootFrame(false) end
end

function TB.ApplyFastLootSetting()
    if on("tbFastLoot") then
        TB.EnsureAutoLootEnabled()
        HideDefaultLootFrame()
    else
        CancelFastLoot()
        RestoreDefaultLootFrame(lootState.isLooting)
    end
end

-- ---------- Files d'attente : acceptation auto ----------
local function AcceptQueues(event)
    if not on("tbAutoQueue") then return end
    -- NB : AcceptProposal / CompleteLFGRoleCheck ne sont PAS protégées (Leatrix &
    -- co les utilisent). Le « ADDON_ACTION_BLOCKED » observé venait d'une
    -- CONTAMINATION (taint) : notre lecture d'aura « secrète » du Bloodlust
    -- souillait l'exécution de l'addon → l'appel se faisait bloquer. Le correctif
    -- Bloodlust (0.34.6) supprime la source du taint ; ces appels repassent donc.
    if event == "LFG_PROPOSAL_SHOW" then
        if AcceptProposal then pcall(AcceptProposal) end
    elseif event == "LFG_ROLE_CHECK_SHOW" then
        if CompleteLFGRoleCheck then pcall(CompleteLFGRoleCheck, true) end
    elseif event == "UPDATE_BATTLEFIELD_STATUS" then
        local maxID = (GetMaxBattlefieldID and GetMaxBattlefieldID()) or 2
        for i = 1, maxID do
            if GetBattlefieldStatus and GetBattlefieldStatus(i) == "confirm" then
                pcall(AcceptBattlefieldPort, i, 1)
            end
        end
    end
end

-- ---------- Journal de combat auto (Warcraft Logs) ----------
-- Démarre le log en entrant en donjon/raid, l'arrête en sortant, et active le
-- « journal de combat avancé » (meilleures données pour Warcraft Logs).
-- NB : le fichier Logs/WoWCombatLog.txt ne peut PAS être vidé par un addon
-- (aucune API de fichier) ; le nettoyage se fait hors-jeu (voir README).
local weStartedLog = false
local function WantLog(kind)
    if kind == "party" or kind == "scenario" then return on("tbAutoLogDungeon") end
    if kind == "raid" then return on("tbAutoLogRaid") end
    return false
end
function TB.UpdateAutoLog()
    local inInstance, itype = IsInInstance()
    local want = inInstance and WantLog(itype) or false
    if want then
        if not weStartedLog then
            if SetCVar then pcall(SetCVar, "advancedCombatLogging", 1) end
            if LoggingCombat then pcall(LoggingCombat, true) end
            weStartedLog = true
            say(NS.L("Journal de combat activé (Warcraft Logs)."))
        end
    elseif weStartedLog then
        if LoggingCombat then pcall(LoggingCombat, false) end
        weStartedLog = false
        say(NS.L("Journal de combat arrêté."))
    end
end

-- ---------- Événements ----------
local f = CreateFrame("Frame")
f:RegisterEvent("MERCHANT_SHOW")
f:RegisterEvent("LFG_PROPOSAL_SHOW")
f:RegisterEvent("LFG_ROLE_CHECK_SHOW")
f:RegisterEvent("UPDATE_BATTLEFIELD_STATUS")
f:SetScript("OnEvent", function(_, event)
    if event == "MERCHANT_SHOW" then
        SellGrey()
        Repair()
    else
        AcceptQueues(event)
    end
end)

-- LOOT_READY arrive avant l'affichage natif et LOOT_OPENED sert de repli si
-- le premier événement n'a pas fourni de données. Le CVar est réappliqué à la
-- connexion et si un autre réglage tente de le couper pendant la session.
local lootEvents = CreateFrame("Frame")
lootEvents:RegisterEvent("PLAYER_LOGIN")
lootEvents:RegisterEvent("CVAR_UPDATE")
lootEvents:RegisterEvent("LOOT_READY")
lootEvents:RegisterEvent("LOOT_OPENED")
lootEvents:RegisterEvent("LOOT_CLOSED")
lootEvents:SetScript("OnEvent", function(_, event, arg1)
    if event == "PLAYER_LOGIN" then
        if C_Timer and C_Timer.After then
            C_Timer.After(0, function() TB.ApplyFastLootSetting() end)
        else
            TB.ApplyFastLootSetting()
        end
    elseif event == "CVAR_UPDATE" then
        local cvar = type(arg1) == "string" and arg1:gsub("_", ""):lower() or ""
        if cvar == "autolootdefault" and on("tbFastLoot") and not lootState.settingCVar then
            TB.EnsureAutoLootEnabled()
        end
    elseif event == "LOOT_READY" or event == "LOOT_OPENED" then
        OnLootReady(arg1)
    elseif event == "LOOT_CLOSED" then
        OnLootClosed()
    end
end)

-- entrée/sortie d'instance → journal de combat auto
local logFrame = CreateFrame("Frame")
logFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
logFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
logFrame:SetScript("OnEvent", function() TB.UpdateAutoLog() end)
