-- EllesmereUI_koKR_Plus / EquipSetIcon.lua
-- 가방/은행 아이템 좌하단에 장비 세트 아이콘 표시 (GUID 기반).
---@diagnostic disable: undefined-global
local NS = _G.EUIKorPlus
if not NS then return end
local GetDB = NS.GetDB
local GetFont, SolidTex, MakeFont, MakeDescFS = NS.GetFont, NS.SolidTex, NS.MakeFont, NS.MakeDescFS
local MakeBorder, SetBorderColor, MakeStepBtn, MakeSwitch = NS.MakeBorder, NS.SetBorderColor, NS.MakeStepBtn, NS.MakeSwitch
-- ============================================================
--  장비 세트 아이콘 (Baganator API/ItemButton.lua:316 equipment_set_icon
--  참조). 아이템이 장비 세트에 지정돼 있으면 가방/은행 아이템 버튼
--  *좌하단* 에 그 세트 아이콘을 표시. EUI 의 _setGearLookup 은
--  파일로컬·아이콘無 → 독자 빌드.
--  메모리 안전(검증된 기법): 버튼당 텍스처 1회 생성(__cimSetIcon 가드,
--  호출당 할당 0), ticker 없음 — RefreshBags/RefreshBank 후크 +
--  EQUIPMENT_SETS_CHANGED 이벤트 구동, C_Timer.After(0) 1회 코얼레스.
-- ============================================================
-- GUID 기반 (Baganator API/EquipmentSets.lua 정식 방식 미러).
-- itemID 기반은 GetItemIDs 가 ignored 슬롯 등에 엉뚱한 itemID 를 줘
-- 소모품에도 오표시됐음. GUID 는 *물리 아이템 고유 식별자* →
-- 소모품·중복·핀/최근 뷰 어디서도 오표시 0. 위치→GUID 로 세트
-- 소속을 잡고, 버튼도 자기 위치의 GUID 로 대조.
local _eqsetGUIDs = {}        -- itemGUID -> true (가방/은행에 있는 세트 아이템)
local _eqsetDirty = true
local function _EQS_Rebuild()
    wipe(_eqsetGUIDs)
    if not (C_EquipmentSet and C_EquipmentSet.GetEquipmentSetIDs
            and ItemLocation and C_Item and C_Item.GetItemGUID) then return end
    local ids = C_EquipmentSet.GetEquipmentSetIDs()
    if not ids then return end   -- dirty 유지 → 다음 apply 재시도
    for _, setID in ipairs(ids) do
        local locs = C_EquipmentSet.GetItemLocations(setID)
        if locs then
            for _, loc in pairs(locs) do
                if loc and loc ~= -1 and loc ~= 0 and loc ~= 1 then
                    local okD, d = pcall(EquipmentManager_GetLocationData, loc)
                    if okD and type(d) == "table" and d.isBags
                       and type(d.bag) == "number" and type(d.slot) == "number" then
                        local okL, il = pcall(ItemLocation.CreateFromBagAndSlot,
                                              ItemLocation, d.bag, d.slot)
                        if okL and il and C_Item.DoesItemExist(il) then
                            local okG, guid = pcall(C_Item.GetItemGUID, il)
                            if okG and guid then _eqsetGUIDs[guid] = true end
                        end
                    end
                end
            end
        end
    end
    _eqsetDirty = false
end

-- ★★ taint 함정: Blizzard ItemButton 에 btn.__cimSetXXX 같은 사용자 필드 쓰기
--   금지. 쓰는 즉시 secure ItemButton 이 우리 addon 으로 오염 → 우클릭/아이템 사용
--   chain taint → 사용 popup 의 OnAccept 가 보호 함수(EndBoundTradeable, 공허핵
--   사용 등) 호출 시 ADDON_ACTION_FORBIDDEN. Baganator 등 다른 addon 은 외부 API/
--   weak 테이블로 참조 관리. 우리도 외부 weak 테이블 사용.
local _eqsRefs = setmetatable({}, { __mode = "k" })  -- btn → { bg=texture, ic=fontstring }

local function _EQS_ApplyBtn(btn)
    if not (type(btn) == "table" and btn.GetID and btn.CreateTexture) then return end
    local refs = _eqsRefs[btn]
    local ic = refs and refs.ic
    if GetDB().eqsetIcon == false then
        if ic then ic:Hide() end
        return
    end
    -- 메모리 절감: 숨은 풀 버튼은 ItemLocation/GUID 할당 없이 즉시 종료
    if btn.IsShown and not btn:IsShown() then
        if ic then ic:Hide() end
        if refs and refs.bg then refs.bg:Hide() end
        return
    end
    -- 버튼 위치의 *아이템 GUID* 로 대조 (Baganator 방식). GUID 는 물리
    -- 아이템 고유 → 소모품·중복·핀/최근 뷰 어디서도 오표시 0.
    local okS, slot = pcall(btn.GetID, btn)
    local p = btn.GetParent and btn:GetParent()
    local okB, bag
    if p and p.GetID then okB, bag = pcall(p.GetID, p) end
    local inSet = false
    if okS and okB and type(slot) == "number" and type(bag) == "number"
       and slot >= 1 and ItemLocation and C_Item and C_Item.GetItemGUID then
        local okL, il = pcall(ItemLocation.CreateFromBagAndSlot,
                              ItemLocation, bag, slot)
        if okL and il and C_Item.DoesItemExist(il) then
            local okG, guid = pcall(C_Item.GetItemGUID, il)
            if okG and guid and _eqsetGUIDs[guid] == true then inSet = true end
        end
    end
    if inSet then
        if not ic then
            local bg = btn:CreateTexture(nil, "OVERLAY", nil, 6)
            bg:SetSize(16, 15)
            bg:SetPoint("BOTTOMLEFT", btn, "BOTTOMLEFT", 2, 1)
            bg:SetColorTexture(0, 0, 0, 0.9)
            ic = btn:CreateFontString(nil, "OVERLAY")
            ic:SetFont(GetFont(), 11, "OUTLINE")
            ic:SetPoint("CENTER", bg, "CENTER", 0, 0)
            ic:SetTextColor(1, 1, 1, 1)
            ic:SetShadowColor(0, 0, 0, 1)
            ic:SetShadowOffset(1, -1)
            ic:SetText("셋")
            _eqsRefs[btn] = { bg = bg, ic = ic }
        end
        if _eqsRefs[btn] and _eqsRefs[btn].bg then _eqsRefs[btn].bg:Show() end
        ic:Show()
    else
        if ic then ic:Hide() end
        if _eqsRefs[btn] and _eqsRefs[btn].bg then _eqsRefs[btn].bg:Hide() end
    end
end

-- 버그+사용량 수정(2026-05-17): 아이템 버튼 루트는 EUI_MainBagFrame
-- (=EUI_Bags) / EUI_ReagentBagFrame / EUI_Bank (소스: slotParent =
-- CreateFrame("Frame", nil, EUI_Bags|EUI_BagsReagent|EUI_Bank), btn =
-- ItemButton). 옛 코드는 EUI_BagsWindow(틀린 프레임)를 깊이9로 헛돌아
-- 안 보이고 사용량 스파이크. → 정확 루트 + 얕은 깊이(버튼은 root→
-- slotParent→ItemButton ≈ 2~3단계)로 비용 대폭 절감.
local function _EQS_Walk(frame, depth)
    if not frame or depth > 5 then return end
    if frame.SetItemButtonTexture and frame.GetID then
        _EQS_ApplyBtn(frame)
    end
    if frame.GetChildren then
        local kids = { frame:GetChildren() }
        for i = 1, #kids do _EQS_Walk(kids[i], depth + 1) end
    end
end

local _eqsScheduled, _eqsLast
local _EQS_ROOTS = { "EUI_MainBagFrame", "EUI_ReagentBagFrame",
                     "EUI_Bank", "EUI_BankFrame" }
local function _EQS_ApplyAll()
    _eqsScheduled = nil
    _eqsLast = (GetTime and GetTime()) or 0
    -- ★ 메모리 핵심: 가방/은행 창이 *하나도 안 보이면* 재빌드·walk 전부
    --   건너뜀. RefreshInventory 는 창 닫혀 있어도 빈번 호출되므로,
    --   이 게이트가 없으면 평상시에도 깊은 walk+할당이 계속 누적됨.
    local anyShown = false
    for i = 1, #_EQS_ROOTS do
        local fr = _G[_EQS_ROOTS[i]]
        if type(fr) == "table" and fr.IsShown and fr:IsShown() then
            anyShown = true
            break
        end
    end
    if not anyShown then return end
    if _eqsetDirty or next(_eqsetGUIDs) == nil then _EQS_Rebuild() end
    for i = 1, #_EQS_ROOTS do
        local fr = _G[_EQS_ROOTS[i]]
        if type(fr) == "table" and fr.IsShown and fr:IsShown()
           and fr.GetChildren then
            _EQS_Walk(fr, 0)
        end
    end
end

local function _EQS_Schedule()
    if _eqsScheduled then return end
    _eqsScheduled = true
    if C_Timer and C_Timer.After then
        -- 스로틀: 마지막 적용 후 0.3초 이내면 남은 시간만큼 미뤄 한 번만
        -- (RefreshInventory 연타 → 0.3초당 최대 1회 walk). 코얼레스 유지.
        local now = (GetTime and GetTime()) or 0
        local since = _eqsLast and (now - _eqsLast) or 999
        local delay = (since < 0.3) and (0.3 - since) or 0
        C_Timer.After(delay, _EQS_ApplyAll)
    else
        _EQS_ApplyAll()
    end
end

local function _EQS_Install()
    if _G.__cimEqsetHooked then return end
    local w = _G.EUI_BagsWindow
    if type(w) ~= "table" or type(w.RefreshBags) ~= "function" then return end
    _G.__cimEqsetHooked = true
    hooksecurefunc(w, "RefreshBags", _EQS_Schedule)
    -- 핵심: 탭/카테고리 전환·대부분의 가방 재렌더는 RefreshBags 가 아니라
    -- EUI_Bags:RefreshInventory() (EllesmereUIBags.lua:3693, 탭버튼
    -- OnClick→3349). 이걸 안 잡아 "탭 이동 시 업뎃 안 됨"이었음.
    for _, gn in ipairs({ "EUI_Bags", "EUI_BagsReagent" }) do
        local fr = _G[gn]
        if type(fr) == "table" and type(fr.RefreshInventory) == "function" then
            hooksecurefunc(fr, "RefreshInventory", _EQS_Schedule)
        end
    end
    local bk = _G.EUI_Bank
    if type(bk) == "table" and type(bk.RefreshBank) == "function" then
        hooksecurefunc(bk, "RefreshBank", _EQS_Schedule)
    end
    -- 타이밍 개선: 가방/은행 프레임이 *열릴 때* 즉시 스케줄 (RefreshBags
    -- 만 의존하면 첫 표시가 늦음). 프레임당 OnShow 1회 후크(가드).
    for _, gn in ipairs({ "EUI_MainBagFrame", "EUI_ReagentBagFrame",
                          "EUI_Bank", "EUI_BankFrame" }) do
        local fr = _G[gn]
        if type(fr) == "table" and fr.HookScript and not fr.__cimEqsShowHook then
            fr.__cimEqsShowHook = true
            pcall(fr.HookScript, fr, "OnShow", _EQS_Schedule)
        end
    end
    local ef = CreateFrame("Frame")
    ef:RegisterEvent("EQUIPMENT_SETS_CHANGED")          -- 세트 정의 자체 변경
    -- 특성/로드아웃 전환 → 자동 장비교체 흐름. 본체 BAG_UPDATE 만으론 "Set Gear"
    -- 분류·세트 표식이 갱신 누락 → 아래 4개 이벤트에서 디바운스 0.2초 후 가방창
    -- RefreshBags() 강제 + _eqsetDirty 표시(다음 _EQS_Schedule 시 캐시 재빌드).
    -- (이전엔 Locale/Bags.lua 에 분산돼 있던 코드 — 같은 처리는 한 곳으로 통합.)
    ef:RegisterEvent("EQUIPMENT_SWAP_FINISHED")
    ef:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
    ef:RegisterEvent("TRAIT_CONFIG_UPDATED")
    ef:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
    local _refreshQueued = false
    ef:SetScript("OnEvent", function(_, event)
        _eqsetDirty = true
        if event == "EQUIPMENT_SETS_CHANGED" then
            -- 세트 정의 변경은 즉시 처리(빈도 낮음, RefreshBags 불필요)
            _EQS_Schedule()
            return
        end
        -- 그 외: 디바운스 0.2초 — 장비 한 벌 swap 시 PLAYER_EQUIPMENT_CHANGED 가
        -- 슬롯 수만큼 연속 발생. 1회로 합쳐 RefreshBags 호출(→ _EQS_Schedule 후크).
        if _refreshQueued then return end
        _refreshQueued = true
        C_Timer.After(0.2, function()
            _refreshQueued = false
            local w = _G.EUI_BagsWindow
            if type(w) == "table" and type(w.RefreshBags) == "function" then
                pcall(w.RefreshBags, w)   -- → hooksecurefunc 로 _EQS_Schedule 자동
            else
                _EQS_Schedule()           -- 가방창 없으면 직접 스케줄
            end
        end)
    end)
    _EQS_Schedule()
end


if NS.RegisterSection then
    NS.RegisterSection({ order = 50, col = 1, row = 4, title = "장비 세트 아이콘",
        desc = "가방·은행 아이템 좌하단에 세트 아이콘 표시.",
        build = function(popup, descFS)
            local sw = MakeSwitch(descFS, -10, "세트 아이콘 표시", "eqsetIcon", _EQS_Schedule)
            sw:ClearAllPoints(); sw:SetPoint("TOPLEFT", descFS, "BOTTOMLEFT", 0, -10)
        end })
end
if NS.RegisterInstall then
    NS.RegisterInstall(function() _EQS_Install(); _EQS_Schedule() end)
end
