-------------------------------------------------------------------------------
--  EllesmereUI_koKR_Plus / DamageMeters.lua
--  데미지 미터 숫자를 한국식 단위로 변환 (12.3K→1.2만, 5.6M→560만, 1.2B→12억).
--  SetText 후크로 자동 적용. ※ 크기/스케일·외곽선·그림자 조정 아님 (그런 기능 없음).
--  미로드 시: 변환 안 함(원본 영문 K/M/B 그대로) — 에러·사용량 0.
-------------------------------------------------------------------------------
---@diagnostic disable: undefined-global
local NS = _G.EUIKorPlus
if not NS then return end
local GetDB = NS.GetDB
local function IsDMLoaded()
    if C_AddOns and C_AddOns.IsAddOnLoaded then
        return C_AddOns.IsAddOnLoaded("EllesmereUIDamageMeters")
    end
    if IsAddOnLoaded then return IsAddOnLoaded("EllesmereUIDamageMeters") end
    return false
end

-- 한글 숫자 변환 (소숫점 1자리)
local function _DM_FormatKorean(n)
    local sign = ""
    if n < 0 then sign = "-"; n = -n end
    if n >= 1e8 then return sign .. string.format("%.1f억", n / 1e8) end
    if n >= 1e4 then return sign .. string.format("%.1f만", n / 1e4) end
    if n >= 1e3 then return sign .. string.format("%.1f천", n / 1e3) end
    return sign .. string.format("%.0f", n)
end

-- ── 메모리 수정 (2026-05-17) ──
-- 미터는 전투 중 SetText 를 초당 수백~수천 회 호출. 기존엔 호출마다
-- pcall(function() ... end) / gsub(function() end) 인라인 클로저를 새로
-- 할당 → GC 폭증·메모리 상승. 아래 명명 헬퍼·gsub 콜백을 *1회만* 정의해
-- 핫패스 할당을 제거(taint 방어 pcall 은 그대로 — 인자만 넘김).
local function _safeEq(a, b)    return a == b end
local function _safeIdx(t, k)   return t[k] end
local function _safeMatch(s, p) return s:match(p) end
local function _safeFind(s, p)  return s:find(p) end
local function _kmbRepl(numStr, suffix)
    local num = tonumber(numStr)
    if not num then return nil end
    if suffix == "K" then num = num * 1e3
    elseif suffix == "M" then num = num * 1e6
    elseif suffix == "B" then num = num * 1e9 end
    return _DM_FormatKorean(num)
end

-- "12.3K" / "5.6M" / "1.2B" → 한글 단위 (호출당 클로저 0)
local function _DM_ConvertKMB(text)
    if type(text) ~= "string" then return text end
    local okF, has = pcall(_safeFind, text, "[KMB]")
    if not okF or not has then return text end
    local okG, out = pcall(string.gsub, text,
        "(%-?[%d%.]+)([KMB])%f[%A]", _kmbRepl)
    if okG and type(out) == "string" then return out end
    return text
end

-- 텍스트 1개 번역 (EUI_L → 한글, 그리고 K/M/B → 천/만/억).
-- secret string taint 방어용 pcall — 단, 인라인 클로저 대신 명명 헬퍼 사용.
local function _DM_TranslateText(text)
    if type(text) ~= "string" then return text end
    local okE, isEmpty = pcall(_safeEq, text, "")
    if not okE or isEmpty then return text end
    -- 핫패스 조기 탈출(전투 중 초당 수백~수천 호출): 글자가 전혀 없으면
    -- (순수 숫자/구두점) EUI_L 키·concat 패턴·KMB(K/M/B 글자 필요) 모두
    -- 매칭 불가 → EUI_L 조회 + 정규식 3종 + KMB 전부 건너뜀.
    local okA, hasAlpha = pcall(_safeMatch, text, "%a")
    if okA and not hasAlpha then return text end
    local tr = text
    -- 1. EUI_L 정확 매칭
    if EUI_L then
        local okL, hit = pcall(_safeIdx, EUI_L, tr)
        if okL and hit then tr = hit end
    end
    -- 2. 동적 concat 패턴 (정확 매칭 실패 시)
    if tr == text and EUI_L then
        -- "PlayerName's Death Recap" → "PlayerName 의 죽음 요약"
        local okM, name = pcall(_safeMatch, text, "^(.-)'s Death Recap$")
        if okM and name and name ~= "" then
            tr = name .. " 의 " .. (EUI_L["Death Recap"] or "Death Recap")
        else
            -- "PlayerName's <Type> Breakdown" → "PlayerName 의 <Type> 분석"
            local okM2, n2, typeName = pcall(_safeMatch, text, "^(.-)'s (.-) Breakdown$")
            if okM2 and n2 and n2 ~= "" and typeName then
                local trType = (EUI_L[typeName]) or typeName
                tr = n2 .. " 의 " .. trType .. " " .. (EUI_L["Breakdown"] or "Breakdown")
            else
                -- "Overall <Type>" → "전체 <Type>"
                local okM3, suffix = pcall(_safeMatch, text, "^Overall (.+)$")
                if okM3 and suffix then
                    local trSuffix = (EUI_L[suffix]) or suffix
                    tr = "전체 " .. trSuffix
                end
            end
        end
    end
    -- 3. K/M/B 변환
    local okC, conv = pcall(_DM_ConvertKMB, tr)
    if okC and type(conv) == "string" then tr = conv end
    return tr
end

local _dmFound = false  -- 명명 DM 프레임을 한 번이라도 스캔했는지
-- 전방 선언 — DM 우클릭/아이콘 메뉴(_edmMenu, local 프레임)는 창 헤더
-- 버튼 클릭으로 열림. 그 버튼은 명명 창의 자식이라 _DM_ScanFrame 이 닿음.
-- 클릭 시 _DM_ScanPopups 를 다음 프레임에 호출 → 메뉴 뜨는 즉시 한글화.
local _DM_ScanPopups
-- 창 안의 모드목록("Damage Done" 등)은 모드 버튼 클릭 시 *이미 열린
-- 창 안에서 새로 생성*됨 → 그때 창 재스캔 필요(팝업 스캔만으론 못 잡음).
local _DM_Rescan

-- FontString SetText hook = 한글(K/M/B→천/만/억) 변환 전용.
-- (외곽선/그림자는 Noodle_SlugBridge 가 글로벌 처리 → 후크 제거됨.)
local function _DM_HookFS(fs)
    if not fs or fs.__cimDMHook or not fs.SetText then return end
    if fs.IsForbidden and fs:IsForbidden() then return end
    if fs.IsProtected and fs:IsProtected() then return end
    fs.__cimDMHook = true
    hooksecurefunc(fs, "SetText", function(self, text)
        -- ★ secure 컨텍스트(StaticPopup_OnClick 등)에서 발화 시 즉시 bail —
        --   우리 작업이 secure chain 으로 taint 전파되면 OnAccept 의 보호함수
        --   (EndBoundTradeable 등) ADDON_ACTION_FORBIDDEN.
        if issecure and issecure() then return end
        if self.__cimDMTr or type(text) ~= "string" then return end
        local tr = _DM_TranslateText(text)
        local okN, same = pcall(_safeEq, tr, text)
        if not okN or same then return end
        self.__cimDMTr = true
        pcall(self.SetText, self, tr)
        self.__cimDMTr = false
    end)
    -- 현재 텍스트 즉시 한글 변환 (1회)
    local okT, txt = pcall(fs.GetText, fs)
    if okT and type(txt) == "string" then
        local tr = _DM_TranslateText(txt)
        local okN, same = pcall(_safeEq, tr, txt)
        if okN and not same then
            fs.__cimDMTr = true
            pcall(fs.SetText, fs, tr)
            fs.__cimDMTr = false
        end
    end
end

-- DM 프레임 트리 walk → 한글 숫자 hook 설치
local function _DM_ScanFrame(frame, depth)
    if not frame or depth > 10 then return end
    if frame.GetRegions then
        local ok, regions = pcall(function() return { frame:GetRegions() } end)
        if ok and type(regions) == "table" then
            for _, rgn in ipairs(regions) do
                local tok, otype = pcall(function() return rgn and rgn.GetObjectType and rgn:GetObjectType() end)
                if tok and otype == "FontString" then
                    _DM_HookFS(rgn)
                end
            end
        end
    end
    if frame.GetChildren then
        local ok, children = pcall(function() return { frame:GetChildren() } end)
        if ok and type(children) == "table" then
            for _, child in ipairs(children) do
                -- 버튼 클릭/마우스오버 후크 (1회) → 다음 프레임에 창 + 팝업 재스캔.
                -- 모드목록("Damage Done" 등)은 클릭 시 새 FontString 생성 → _DM_Rescan
                -- 으로 재walk. 헤더 버튼이 여는 _edmMenu 는 _DM_ScanPopups 가 잡음.
                if not child.__cimDMBtnHook and child.HookScript
                   and child.GetObjectType then
                    local okO, ot = pcall(child.GetObjectType, child)
                    if okO and ot == "Button" then
                        child.__cimDMBtnHook = true
                        local function _re()
                            if _DM_Rescan then _DM_Rescan() end
                            if _DM_ScanPopups then _DM_ScanPopups() end
                        end
                        pcall(child.HookScript, child, "OnClick", function()
                            if not (C_Timer and C_Timer.After) then return end
                            C_Timer.After(0,    _re)
                            C_Timer.After(0.05, _re)
                            C_Timer.After(0.20, _re)
                        end)
                        -- OnEnter 도 후크 → DM 행 mouseover 시 호버 툴팁(익명 TOOLTIP
                        -- frame) FontString lazy 생성 즉시 hook (한글 변환 회귀 방지).
                        pcall(child.HookScript, child, "OnEnter", function()
                            if not (C_Timer and C_Timer.After) then return end
                            C_Timer.After(0,    _re)
                            C_Timer.After(0.05, _re)
                        end)
                    end
                end
                _DM_ScanFrame(child, depth + 1)
            end
        end
    end
end

-- 익명 UIParent 자식의 FontString 만 K/M/B 변환 hook (DM 툴팁 _ttFrame 등 대응).
-- 이름 있는 다른 애드온 프레임 (Blizzard 액션바 등) 은 건들지 않음.
local function _DM_HookUnnamedFS(frame, depth)
    if not frame or depth > 6 then return end
    if frame.IsForbidden and frame:IsForbidden() then return end
    if frame.IsProtected and frame:IsProtected() then return end
    -- OnShow hook (1회) — 창이 열리는 순간 재스캔
    if depth == 0 and not frame.__cimDMTipHook and frame.HookScript then
        frame.__cimDMTipHook = true
        pcall(frame.HookScript, frame, "OnShow", function(self)
            _DM_HookUnnamedFS(self, 0)
        end)
    end
    if frame.GetRegions then
        local ok, regions = pcall(function() return { frame:GetRegions() } end)
        if ok and type(regions) == "table" then
            for _, rgn in ipairs(regions) do
                -- GetObjectType 을 pcall 로 감싸 비위젯 테이블 크래시 방지
                local tok, otype = pcall(function() return rgn and rgn.GetObjectType and rgn:GetObjectType() end)
                if tok and otype == "FontString" then
                    _DM_HookFS(rgn)
                end
            end
        end
    end
    if frame.GetChildren then
        local ok, children = pcall(function() return { frame:GetChildren() } end)
        if ok and type(children) == "table" then
            for _, child in ipairs(children) do
                _DM_HookUnnamedFS(child, depth + 1)
            end
        end
    end
end

-- 익명 팝업(DM 툴팁 _ttFrame / 커스텀 우클릭·아이콘 메뉴 _edmMenu)만
-- 좁혀 스캔. forward-declared 된 _DM_ScanPopups 에 정의 할당 (여기서야
-- _DM_HookUnnamedFS 가 보임). 메뉴 버튼 클릭 후크에서 호출돼 메뉴가
-- 뜨는 즉시 한글화 → '전체 설정 열어야 나옴' 해결. ticker 아님.
-- ★★ Blizzard StaticPopup_Game (GameDialog) 가 익명 DIALOG strata 프레임을
--    생성 → 이전엔 _DM_HookUnnamedFS 가 그 안 FontString 까지 hook 설치 →
--    OnAccept 의 보호함수(EndBoundTradeable 등) 호출 시 taint 차단.
--    해결: DIALOG/FULLSCREEN_DIALOG 제외, TOOLTIP strata 만 스캔 (DM 툴팁 _ttFrame 전용).
--    커스텀 우클릭 메뉴(_edmMenu) 는 별도 경로(_DM_ScanFrame)가 이미 처리.
_DM_ScanPopups = function()
    if not (UIParent and UIParent.GetChildren) then return end
    for _, child in ipairs({ UIParent:GetChildren() }) do
        if type(child) == "table" then
            local named = false
            if type(child.GetName) == "function" then
                local okN, nm = pcall(child.GetName, child)
                named = okN and type(nm) == "string"
            end
            if not named then
                local okS, shown = pcall(child.IsShown, child)
                if okS and shown then
                    -- TOOLTIP 만. DIALOG/FULLSCREEN_DIALOG 는 Blizzard 보호 popup
                    -- (GameDialog 등) 일 수 있어 hook 시 taint 위험.
                    local okT, isMatch = pcall(function() return child:GetFrameStrata() == "TOOLTIP" end)
                    if okT and isMatch then
                        local okH, h = pcall(child.GetHeight, child)
                        if okH and h and h > 0 and h <= 700 then
                            _DM_HookUnnamedFS(child, 0)
                        end
                    end
                end
            end
        end
    end
end

-- DM 분석 결과(2026-05-17): 창 프레임은 전역 명명 "EllesmereUIDMFrame"..i
-- (i=1..MAX_WINDOWS=5). 이전엔 UIParent 전체를 1회 walk → 나중에 추가된
-- 창(메인 외 2~5번)은 안 잡혔고 비용도 큼. 이제 1~5번을 *직접 타겟* →
-- 추가창 전부 커버 + 저비용. 툴팁/메뉴는 익명(strata TOOLTIP)이라 좁힌
-- 팝업 스캔으로만(블리자드 무관 프레임 제외).
local DM_MAX_WINDOWS = 5
local function ApplyDamageMeters()
    if not IsDMLoaded() then return end
    -- 1) 명명 창 1..5 직접 타겟 (추가 생성 창 포함)
    for i = 1, DM_MAX_WINDOWS do
        local fr = _G["EllesmereUIDMFrame" .. i]
        if type(fr) == "table" and fr.GetObjectType then
            _dmFound = true
            _DM_ScanFrame(fr, 0)
            if not fr.__cimDMShowHooked and fr.HookScript then
                fr.__cimDMShowHooked = true
                pcall(fr.HookScript, fr, "OnShow", function(self)
                    _DM_ScanFrame(self, 0)
                end)
            end
        end
    end
    -- 2) 익명 팝업(DM 툴팁/메뉴) 스캔 — 좁힌 strata 휴리스틱(별도 함수).
    _DM_ScanPopups()
end
-- forward-declared _DM_Rescan 에 할당 (버튼 클릭 후크가 창 재walk 용으로 호출).
_DM_Rescan = ApplyDamageMeters

-- ticker 는 새 창 발견 시 1회 스캔만 — 창당 __cimDMScanned 가드로 idempotent.
local function _DM_EnsureWindows()
    if not IsDMLoaded() then return end
    for i = 1, DM_MAX_WINDOWS do
        local fr = _G["EllesmereUIDMFrame" .. i]
        if type(fr) == "table" and fr.GetObjectType and not fr.__cimDMScanned then
            fr.__cimDMScanned = true
            _dmFound = true
            _DM_ScanFrame(fr, 0)
            if not fr.__cimDMShowHooked and fr.HookScript then
                fr.__cimDMShowHooked = true
                pcall(fr.HookScript, fr, "OnShow", function(self)
                    _DM_ScanFrame(self, 0)
                end)
            end
        end
    end
end

-- 메모리 증가 수정 (2026-05-17): 기존 _dmTicker 가 전투 종료마다 0.5초×10회
-- ApplyDamageMeters 를 호출 → 매번 UIParent 전체 + 익명 자식 깊이6 재귀 walk
-- (임시 테이블·OnShow 클로저 누적) → 메모리 급증. 폴링 ticker 전면 제거.
-- 대체: ① 로그인 1회 스캔 ② DM 프레임 OnShow hook(1회 설치) ③ FontString
-- SetText hook(__cimDMHook, 1회) — 이 셋이 새 창/새 텍스트를 모두 커버하므로
-- 상시/주기 폴링 불필요. StartDMTicker/StopDMTicker 는 no-op 스텁으로 남김
-- (호출부 호환).
local _dmInCombat = false
local function StopDMTicker() end
local function StartDMTicker() end

-- ── DM 초기화 재시도 (신규 창 2~5 lazy 생성 포착, 60초) ──
local function _DM_InitRetry()
    if _dmInitTicker or _dmInitDone then return end
    if not (C_Timer and C_Timer.NewTicker) then
        if C_Timer and C_Timer.After then
            C_Timer.After(2.0, _DM_EnsureWindows)
            C_Timer.After(6.0, _DM_EnsureWindows)
        end
        _dmInitDone = true
        return
    end
    local tries = 0
    _dmInitTicker = C_Timer.NewTicker(1.0, function(tk)
        tries = tries + 1
        _DM_EnsureWindows()  -- 경량: 새 창만 1회 스캔, 기존창 즉시 skip
        if tries >= 60 then  -- 60초간 지속 → 추가 창 2~5 생성도 포착
            tk:Cancel()
            _dmInitTicker = nil
            _dmInitDone = true
        end
    end)
end

-- ============================================================
--  진입점 등록 — 로그인 시 1회 스캔 + 신규 창(2~5) 포착 ticker.
--  (자동 변환이라 팝업 섹션 없음. 토글이 필요하면 추후 추가.)
-- ============================================================
if NS.RegisterInstall then
    NS.RegisterInstall(function()
        ApplyDamageMeters()
        _DM_InitRetry()
    end)
end
