-- ============================================================================
-- ETB Forge — Thunderforged / Warforged awareness
-- ============================================================================
-- Thunderforged (Throne of Thunder, patch 5.2) and Warforged (Siege of
-- Orgrimmar, patch 5.4) are the SAME mechanic: a random +6 item-level proc
-- applied to a boss drop at loot-generation time. A forged item shares the
-- base item's itemID AND its transmog appearance — only the item level
-- changes. Tier-set tokens cannot be forged.
--
-- Because forging never changes the appearance, it is irrelevant to transmog
-- collection. It matters for item level / BiS. This module is the single
-- source of truth the rest of the addon uses to answer:
--   * "can a drop of this item, from this instance, be forged?"
--   * "is this specific item link already forged?"
-- ============================================================================

local ADDON_NAME, NS = ...
local ETB = NS.ETB

ETB.Forge = ETB.Forge or {}
local Forge = ETB.Forge

-- Item levels gained by a forged drop.
Forge.ILVL_BONUS = 6

-- ETB instance IDs whose boss loot can be forged, and the forge name used
-- in that instance. Throne of Thunder = Thunderforged, Siege of Orgrimmar =
-- Warforged. (World bosses are intentionally excluded.)
Forge.INSTANCES = {
    [362] = { kind = "Thunderforged", instance = "Throne of Thunder" },
    [369] = { kind = "Warforged",     instance = "Siege of Orgrimmar" },
}

-- Returns the forge descriptor { kind, instance } for an instance, or nil.
function Forge:GetInstanceForge(instID)
    return instID and self.INSTANCES[instID] or nil
end

-- ---------------------------------------------------------------------------
-- Authoritative Thunderforged item-ID set (Throne of Thunder, patch 5.2).
-- Derived from AllTheThings 5.1.7 (db/Mists) — the items grouped under ATT's
-- "Thunderforged" header (h(-224)). These are the SEPARATE itemID records the
-- MoP-Classic client assigns to Thunderforged drops; each shares its base
-- item's appearance and name, differing only by +6 item level.
--
-- Replaces the old `itemID >= 96000 and itemID < 98000` heuristic, which was
-- both leaky (the ToT id-range interleaves base and forged items) and wrong
-- (it falsely flagged ~322 base ToT items as forged). Membership here is a
-- fact, not an id-range guess.
-- ---------------------------------------------------------------------------
Forge.FORGED_ITEMIDS = {
    [95996]=true, [95997]=true, [95998]=true, [95999]=true, [96000]=true, [96001]=true,
    [96002]=true, [96003]=true, [96004]=true, [96005]=true, [96006]=true, [96007]=true,
    [96008]=true, [96009]=true, [96010]=true, [96011]=true, [96012]=true, [96013]=true,
    [96014]=true, [96015]=true, [96016]=true, [96017]=true, [96018]=true, [96019]=true,
    [96020]=true, [96021]=true, [96022]=true, [96023]=true, [96024]=true, [96025]=true,
    [96026]=true, [96027]=true, [96028]=true, [96029]=true, [96030]=true, [96031]=true,
    [96032]=true, [96033]=true, [96034]=true, [96035]=true, [96036]=true, [96037]=true,
    [96038]=true, [96039]=true, [96040]=true, [96041]=true, [96042]=true, [96043]=true,
    [96044]=true, [96045]=true, [96046]=true, [96047]=true, [96048]=true, [96049]=true,
    [96050]=true, [96051]=true, [96052]=true, [96053]=true, [96054]=true, [96055]=true,
    [96056]=true, [96057]=true, [96058]=true, [96059]=true, [96060]=true, [96061]=true,
    [96062]=true, [96063]=true, [96064]=true, [96065]=true, [96066]=true, [96067]=true,
    [96068]=true, [96069]=true, [96070]=true, [96071]=true, [96072]=true, [96073]=true,
    [96074]=true, [96075]=true, [96076]=true, [96077]=true, [96078]=true, [96079]=true,
    [96080]=true, [96081]=true, [96082]=true, [96083]=true, [96084]=true, [96085]=true,
    [96086]=true, [96087]=true, [96088]=true, [96089]=true, [96090]=true, [96091]=true,
    [96092]=true, [96093]=true, [96094]=true, [96095]=true, [96096]=true, [96097]=true,
    [96098]=true, [96099]=true, [96100]=true, [96101]=true, [96102]=true, [96103]=true,
    [96104]=true, [96105]=true, [96106]=true, [96107]=true, [96108]=true, [96109]=true,
    [96110]=true, [96111]=true, [96112]=true, [96113]=true, [96114]=true, [96115]=true,
    [96116]=true, [96117]=true, [96118]=true, [96119]=true, [96120]=true, [96121]=true,
    [96122]=true, [96123]=true, [96124]=true, [96125]=true, [96126]=true, [96127]=true,
    [96128]=true, [96129]=true, [96130]=true, [96131]=true, [96132]=true, [96133]=true,
    [96134]=true, [96135]=true, [96136]=true, [96137]=true, [96138]=true, [96139]=true,
    [96140]=true, [96141]=true, [96142]=true, [96143]=true, [96144]=true, [96145]=true,
    [96146]=true, [96147]=true, [96148]=true, [96149]=true, [96150]=true, [96151]=true,
    [96152]=true, [96153]=true, [96154]=true, [96155]=true, [96156]=true, [96157]=true,
    [96158]=true, [96159]=true, [96160]=true, [96161]=true, [96162]=true, [96163]=true,
    [96164]=true, [96165]=true, [96166]=true, [96167]=true, [96168]=true, [96169]=true,
    [96170]=true, [96171]=true, [96172]=true, [96173]=true, [96174]=true, [96175]=true,
    [96176]=true, [96177]=true, [96178]=true, [96179]=true, [96180]=true, [96181]=true,
    [96182]=true, [96183]=true, [96184]=true, [96185]=true, [96186]=true, [96187]=true,
    [96188]=true, [96189]=true, [96190]=true, [96191]=true, [96192]=true, [96193]=true,
    [96230]=true, [96231]=true, [96232]=true, [96233]=true, [96234]=true, [96235]=true,
    [96236]=true, [96237]=true, [96238]=true, [96239]=true, [96240]=true, [96241]=true,
    [96242]=true, [96243]=true, [96244]=true, [96245]=true, [96246]=true, [96247]=true,
    [96248]=true, [96249]=true, [96250]=true, [96330]=true, [96331]=true, [96332]=true,
    [96333]=true, [96334]=true, [96335]=true, [96336]=true, [96337]=true, [96338]=true,
    [96339]=true, [96340]=true, [96341]=true, [96342]=true, [96343]=true, [96344]=true,
    [96345]=true, [96346]=true, [96347]=true, [96348]=true, [96349]=true, [96350]=true,
    [96351]=true, [96352]=true, [96740]=true, [96741]=true, [96742]=true, [96743]=true,
    [96744]=true, [96745]=true, [96746]=true, [96747]=true, [96748]=true, [96749]=true,
    [96750]=true, [96751]=true, [96752]=true, [96753]=true, [96754]=true, [96755]=true,
    [96756]=true, [96757]=true, [96758]=true, [96759]=true, [96760]=true, [96761]=true,
    [96762]=true, [96763]=true, [96764]=true, [96765]=true, [96766]=true, [96767]=true,
    [96768]=true, [96769]=true, [96770]=true, [96771]=true, [96772]=true, [96773]=true,
    [96774]=true, [96775]=true, [96776]=true, [96777]=true, [96778]=true, [96779]=true,
    [96780]=true, [96781]=true, [96782]=true, [96783]=true, [96784]=true, [96785]=true,
    [96786]=true, [96787]=true, [96788]=true, [96789]=true, [96790]=true, [96791]=true,
    [96792]=true, [96793]=true, [96794]=true, [96795]=true, [96796]=true, [96797]=true,
    [96798]=true, [96799]=true, [96800]=true, [96801]=true, [96802]=true, [96803]=true,
    [96804]=true, [96805]=true, [96806]=true, [96807]=true, [96808]=true, [96809]=true,
    [96810]=true, [96811]=true, [96812]=true, [96813]=true, [96814]=true, [96815]=true,
    [96816]=true, [96817]=true, [96818]=true, [96819]=true, [96820]=true, [96821]=true,
    [96822]=true, [96823]=true, [96824]=true, [96825]=true, [96826]=true, [96827]=true,
    [96828]=true, [96829]=true, [96830]=true, [96831]=true, [96832]=true, [96833]=true,
    [96834]=true, [96835]=true, [96836]=true, [96837]=true, [96838]=true, [96839]=true,
    [96840]=true, [96841]=true, [96842]=true, [96843]=true, [96844]=true, [96845]=true,
    [96846]=true, [96847]=true, [96848]=true, [96849]=true, [96850]=true, [96851]=true,
    [96852]=true, [96853]=true, [96854]=true, [96855]=true, [96856]=true, [96857]=true,
    [96858]=true, [96859]=true, [96860]=true, [96861]=true, [96862]=true, [96863]=true,
    [96864]=true, [96865]=true, [96866]=true, [96867]=true, [96868]=true, [96869]=true,
    [96870]=true, [96871]=true, [96872]=true, [96873]=true, [96874]=true, [96875]=true,
    [96876]=true, [96877]=true, [96878]=true, [96879]=true, [96880]=true, [96881]=true,
    [96882]=true, [96883]=true, [96884]=true, [96885]=true, [96886]=true, [96887]=true,
    [96888]=true, [96889]=true, [96890]=true, [96891]=true, [96892]=true, [96893]=true,
    [96894]=true, [96895]=true, [96896]=true, [96897]=true, [96898]=true, [96899]=true,
    [96900]=true, [96901]=true, [96902]=true, [96903]=true, [96904]=true, [96905]=true,
    [96906]=true, [96907]=true, [96908]=true, [96909]=true, [96910]=true, [96911]=true,
    [96912]=true, [96913]=true, [96914]=true, [96915]=true, [96916]=true, [96917]=true,
    [96918]=true, [96919]=true, [96920]=true, [96921]=true, [96922]=true, [96923]=true,
    [96924]=true, [96925]=true, [96926]=true, [96927]=true, [96928]=true, [96929]=true,
    [96930]=true, [96931]=true, [96932]=true, [96933]=true, [96934]=true, [96935]=true,
    [96936]=true, [96937]=true, [96974]=true, [96975]=true, [96976]=true, [96977]=true,
    [96978]=true, [96979]=true, [96980]=true, [96981]=true, [96982]=true, [96983]=true,
    [96984]=true, [96985]=true, [96986]=true, [96987]=true, [96988]=true, [96989]=true,
    [96990]=true, [96991]=true, [96992]=true, [96993]=true, [96994]=true, [97128]=true,
    [97130]=true,
}

-- True if `itemID` is a Thunderforged/Warforged variant record (not a base item).
function Forge:IsForgedVariant(itemID)
    return itemID ~= nil and self.FORGED_ITEMIDS[itemID] == true
end

-- Lazily-built set of itemIDs that cannot be forged (tier-set tokens and the
-- tier pieces they redeem into). Tier gear has no forged variant.
Forge._noForge = nil
function Forge:_ensureNoForgeSet()
    if self._noForge then return end
    local set = {}
    if ETB._TierTokenMapLoader then pcall(ETB._TierTokenMapLoader) end
    local TTM = ETB.TierTokenMap
    if type(TTM) == "table" then
        for tokenID, pieces in pairs(TTM) do
            set[tokenID] = true
            if type(pieces) == "table" then
                for _, pieceID in ipairs(pieces) do
                    if type(pieceID) == "number" then set[pieceID] = true end
                end
            end
        end
    end
    self._noForge = set
end

-- True if a drop of `itemID` from instance `instID` can be forged.
function Forge:CanItemForge(itemID, instID)
    if not self:GetInstanceForge(instID) then return false end
    self:_ensureNoForgeSet()
    return not self._noForge[itemID]
end

-- Forge descriptor for `itemID` dropping from `instID`, or nil:
--   { kind = "Thunderforged"|"Warforged", ilvlBonus = 6 }
function Forge:GetItemForge(itemID, instID)
    local inst = self:GetInstanceForge(instID)
    if not inst then return nil end
    if not self:CanItemForge(itemID, instID) then return nil end
    return { kind = inst.kind, ilvlBonus = self.ILVL_BONUS }
end

-- Amber-colored display label, e.g. "Thunderforged (+6 ilvl)".
function Forge:GetLabel(kind)
    if not kind then return "" end
    return "|cffff8000" .. kind .. " (+" .. self.ILVL_BONUS .. " ilvl)|r"
end

-- Resolve the forge KIND ("Thunderforged" / "Warforged") for a bare itemID by
-- finding the raid instance it drops from (JLoot -> JEncounters -> instID, with
-- a BiSNpcBridge fallback). Returns nil when the item is not a forge-capable
-- raid drop. Lets consumers that hold only an itemID (e.g. the ERB BiS list)
-- label forge status correctly per tier: Siege drops resolve to "Warforged",
-- Throne of Thunder drops to "Thunderforged". This is the categorical path that
-- covers Siege of Orgrimmar, whose Warforged items carry no separate itemID
-- record (unlike Throne of Thunder's Thunderforged item set).
function Forge:GetForgeKindByItem(itemID)
    if not itemID then return nil end
    if ETB._BossLootLoader then
        local inCombat = (InCombatLockdown and InCombatLockdown())
            or (UnitAffectingCombat and UnitAffectingCombat("player"))
        if inCombat or (ETB.IsLowMemoryMode and ETB:IsLowMemoryMode()) then
            return nil
        end
        pcall(ETB._BossLootLoader)
        ETB._BossLootLoader = nil
    end
    local JL, JE = ETB.JLoot, ETB.JEncounters
    local instID
    local encs = JL and JL[itemID]
    if encs then
        local list = (type(encs) == "table" and type(encs[1]) ~= "table") and { encs } or encs
        if type(list) == "table" then
            for _, pair in ipairs(list) do
                local encID = type(pair) == "table" and pair[1] or pair
                local enc = encID and JE and JE[encID]
                if enc and enc[2] and self.INSTANCES[enc[2]] then instID = enc[2]; break end
            end
        end
    end
    if not instID then
        local bridge = ETB.ERB and ETB.ERB.BiSNpcBridge
        local npcs = bridge and bridge[itemID]
        if npcs and ETB.JNpcToEnc and JE then
            for _, npc in ipairs(npcs) do
                local encID = ETB.JNpcToEnc[npc]
                local enc = encID and JE[encID]
                if enc and enc[2] and self.INSTANCES[enc[2]] then instID = enc[2]; break end
            end
        end
    end
    if not instID then return nil end
    if not self:CanItemForge(itemID, instID) then return nil end
    local f = self:GetInstanceForge(instID)
    return f and f.kind or nil
end

-- ---------------------------------------------------------------------------
-- Detection of an already-forged item instance.
-- The forged designation prints as a line near the top of the item tooltip,
-- so a hidden scanning tooltip reliably detects it across client versions.
-- The scanning tooltip is private and never shown; it is named only because
-- reading tooltip lines requires named FontStrings, and it never interacts
-- with the protected transmog/collection UI.
-- ---------------------------------------------------------------------------
local scanTip
function Forge:DetectForgedLink(itemLink)
    if not itemLink then return nil end
    if not scanTip then
        scanTip = CreateFrame("GameTooltip", "ETBForgeScanTooltip",
                              UIParent, "GameTooltipTemplate")
        scanTip:SetOwner(UIParent, "ANCHOR_NONE")
    end
    scanTip:ClearLines()
    local ok = pcall(function() scanTip:SetHyperlink(itemLink) end)
    if not ok then return nil end
    local maxLine = math.min(scanTip:NumLines(), 6)
    for i = 2, maxLine do
        local fs = _G["ETBForgeScanTooltipTextLeft" .. i]
        local text = fs and fs:GetText()
        if text then
            if text:find("Thunderforged", 1, true) then return "Thunderforged" end
            if text:find("Warforged", 1, true) then return "Warforged" end
        end
    end
    return nil
end
