-- =============================================================
-- [[ 玩家护盾量显示 ]]
-- { Key = "ExTools.PlayerShield", Name = "玩家护盾量", Desc = "在屏幕上显示玩家当前总护盾量，支持显示数值或占总血量百分比。", Category = 4 },
-- =============================================================

local ondev = true
if ondev then
    return
end


local ExwindTools = _G.ExwindTools
if not ExwindTools then return end

local L = (ExwindTools and ExwindTools.L) or setmetatable({}, { __index = function(_, key) return key end })
local EXStaticDB = ExwindTools.DB_Static or _G.EXDB
local UIParent = _G.UIParent
local CreateFrame = _G.CreateFrame
local UnitGetTotalAbsorbs = _G.UnitGetTotalAbsorbs
local UnitHealthMax = _G.UnitHealthMax
local BreakUpLargeNumbers = _G.BreakUpLargeNumbers
local AbbreviateLargeNumbers = _G.AbbreviateLargeNumbers
local AbbreviateNumbers = _G.AbbreviateNumbers
local C_StringUtil = _G.C_StringUtil
local C_Timer = _G.C_Timer
local math_abs = math.abs
local math_floor = math.floor

local EXWIND_MODULE_KEY = "ExTools.PlayerShield"

local function EX_RegisterLayout()
    local layout = {
        { key = "header", type = "header", x = 1, y = 1, w = 53, h = 2, label = L["玩家护盾量"], labelSize = 25 },
        { key = "desc", type = "description", x = 1, y = 4, w = 53, h = 2, label = L["显示玩家当前总护盾量，可切换为显示占总血量的百分比。"] },

        { key = "sub_general", type = "subheader", x = 1, y = 8, w = 53, h = 2, label = L["基础设置"], labelSize = 20 },
        { key = "div_general", type = "divider", x = 1, y = 10, w = 53, h = 1 },
        { key = "enabled", type = "checkbox", x = 1, y = 12, w = 10, h = 2, label = L["启用"] },
        { key = "hideWhenZero", type = "checkbox", x = 13, y = 12, w = 14, h = 2, label = L["零护盾隐藏"] },
        { key = "displayMode", type = "dropdown", x = 29, y = 12, w = 18, h = 2, label = L["显示模式"], items = "数值,总血量百分比" },
        { key = "abbreviateNumber", type = "checkbox", x = 1, y = 16, w = 14, h = 2, label = L["缩写数值"] },
        { key = "decimalPlaces", type = "slider", x = 17, y = 16, w = 14, h = 2, label = L["小数位"], min = 0, max = 2, step = 1, labelPos = "top" },
        { key = "frameScale", type = "slider", x = 33, y = 16, w = 14, h = 2, label = L["缩放"], min = 0.5, max = 3.0, step = 0.1, labelPos = "top" },

        { key = "sub_position", type = "subheader", x = 1, y = 22, w = 53, h = 2, label = L["位置设置"], labelSize = 20 },
        { key = "div_position", type = "divider", x = 1, y = 24, w = 53, h = 1 },
        { key = "xOffset", type = "slider", x = 1, y = 26, w = 16, h = 2, label = L["X 轴偏移"], min = -1000, max = 1000, step = 1, labelPos = "top" },
        { key = "yOffset", type = "slider", x = 19, y = 26, w = 16, h = 2, label = L["Y 轴偏移"], min = -1000, max = 1000, step = 1, labelPos = "top" },
        { key = "btn_resetPos", type = "button", x = 37, y = 26, w = 12, h = 3, label = L["重置位置"] },

        { key = "sub_font", type = "subheader", x = 1, y = 32, w = 53, h = 2, label = L["文字设置"], labelSize = 20 },
        { key = "div_font", type = "divider", x = 1, y = 34, w = 53, h = 1 },
        { key = "font_text", type = "fontgroup", x = 1, y = 36, w = 53, h = 17, label = L["护盾文字样式"], labelSize = 20 },
    }

    ExwindTools:RegisterModuleLayout(EXWIND_MODULE_KEY, layout)
end
EX_RegisterLayout()

if not ExwindTools:IsModuleEnabled(EXWIND_MODULE_KEY) then return end

local EX_DEFAULTS = {
    abbreviateNumber = true,
    decimalPlaces = 1,
    displayMode = "数值",
    enabled = true,
    frameScale = 1.0,
    hideWhenZero = true,
    point = "CENTER",
    relativePoint = "CENTER",
    xOffset = 0,
    yOffset = -120,
    font_text = {
        a = 1,
        b = 1,
        font = "默认",
        g = 0.91372555494308,
        outline = "THICKOUTLINE",
        r = 0.24705883860588,
        shadow = true,
        shadowX = 1,
        shadowY = -1,
        size = 24,
        x = 0,
        y = 0,
    },
}

local EX_DB = ExwindTools:GetModuleDB(EXWIND_MODULE_KEY, EX_DEFAULTS)
local displayFrame = nil
local displayText = nil
local isEditModeActive = false
local isEditModeVisible = true
local refreshTimer = nil
local REFRESH_THROTTLE = 0.2
local PREVIEW_VALUE = 1250000
local PREVIEW_MAX_HEALTH = 8000000

local function IsSecretValue(value)
    return type(_G.issecretvalue) == "function" and _G.issecretvalue(value)
end

local function NormalizeDecimalPlaces(value)
    local decimals = tonumber(value)
    if not decimals or decimals ~= decimals then
        return 1
    end
    decimals = math_floor(decimals)
    if decimals < 0 then
        return 0
    end
    if decimals > 2 then
        return 2
    end
    return decimals
end

local function RefreshEditOverlay()
    if not displayFrame then return end

    if isEditModeActive and isEditModeVisible then
        ExwindTools:ShowExwindToolsEditOverlay(EXWIND_MODULE_KEY, displayFrame)
    else
        ExwindTools:HideExwindToolsEditOverlay(displayFrame)
    end
end

local function SavePosition()
    if not displayFrame then return end

    local point, _, relativePoint, xOffset, yOffset = displayFrame:GetPoint()
    if point and relativePoint then
        EX_DB.point = point
        EX_DB.relativePoint = relativePoint
        EX_DB.xOffset = xOffset or 0
        EX_DB.yOffset = yOffset or 0
    end

    if ExwindTools.UI and ExwindTools.UI.MainFrame and ExwindTools.UI.MainFrame:IsShown() then
        ExwindTools.UI:RefreshContent()
    end
end

local function StartFrameMove(frame)
    if not frame or not isEditModeActive or not isEditModeVisible then
        return
    end

    frame.isMoving = true
    frame:StartMoving()
end

local function StopFrameMove(frame)
    if not frame or not frame.isMoving then
        return
    end

    frame.isMoving = false
    frame:StopMovingOrSizing()
    SavePosition()
end

local function RestorePosition()
    if not displayFrame then return end

    displayFrame:SetScale(EX_DB.frameScale or 1)
    displayFrame:ClearAllPoints()
    displayFrame:SetPoint(
        EX_DB.point or "CENTER",
        UIParent,
        EX_DB.relativePoint or "CENTER",
        EX_DB.xOffset or 0,
        EX_DB.yOffset or -120
    )
end

local function ApplyFontStyle()
    if not displayText or not EXStaticDB or not EXStaticDB.ApplyFont then
        return
    end

    EXStaticDB:ApplyFont(displayText, EX_DB.font_text)
    displayText:ClearAllPoints()
    displayText:SetPoint("CENTER", displayFrame, "CENTER", EX_DB.font_text.x or 0, EX_DB.font_text.y or 0)
end

local function GetPlayerShieldAmount()
    local amount = nil
    if UnitGetTotalAbsorbs then
        amount = UnitGetTotalAbsorbs("player")
    end
    if IsSecretValue(amount) then
        return amount, true
    end
    if amount == nil then
        return 0, false
    end
    amount = tonumber(amount) or 0
    if amount < 0 then
        return 0, false
    end
    return amount, false
end

local function GetPlayerShieldPercent(amount, amountIsSecret)
    if amountIsSecret then
        return nil, true
    end

    local maxHealth = nil
    if UnitHealthMax then
        maxHealth = UnitHealthMax("player")
    end
    if IsSecretValue(maxHealth) then
        return nil, true
    end
    if maxHealth == nil then
        return 0, false
    end
    maxHealth = tonumber(maxHealth) or 0
    if maxHealth <= 0 then
        return 0, false
    end
    return (amount / maxHealth) * 100, false
end

local function GetPlayerMaxHealth()
    local maxHealth = nil
    if UnitHealthMax then
        maxHealth = UnitHealthMax("player")
    end
    if IsSecretValue(maxHealth) then
        return maxHealth, true
    end
    if maxHealth == nil then
        return 0, false
    end
    return tonumber(maxHealth) or 0, false
end

local function FormatSecretLargeNumberCN(value)
    if not AbbreviateNumbers then
        if AbbreviateLargeNumbers then
            return AbbreviateLargeNumbers(value)
        end
        return string.format("%s", value)
    end

    return AbbreviateNumbers(value, {
        breakpointData = {
            {
                breakpoint = 100000000,
                abbreviation = "亿",
                significandDivisor = 100000000,
                fractionDivisor = 1,
                abbreviationIsGlobal = false,
            },
            {
                breakpoint = 10000,
                abbreviation = "万",
                significandDivisor = 10000,
                fractionDivisor = 1,
                abbreviationIsGlobal = false,
            },
        },
    })
end

local function FormatLargeNumber(value)
    if IsSecretValue(value) then
        return FormatSecretLargeNumberCN(value)
    end
    if value >= 100000000 then
        return string.format(L["%.2f亿"], value / 100000000)
    end
    if value >= 10000 then
        return string.format(L["%d万"], math_floor(value / 10000))
    end
    if BreakUpLargeNumbers then
        return BreakUpLargeNumbers(math_floor(value + 0.5))
    end
    return tostring(math_floor(value + 0.5))
end

local function FormatShieldValue(amount)
    if IsSecretValue(amount) then
        if EX_DB.abbreviateNumber then
            return FormatLargeNumber(amount)
        end
        return string.format("%.0f", amount)
    end
    if EX_DB.abbreviateNumber then
        return FormatLargeNumber(amount)
    end
    if BreakUpLargeNumbers then
        return BreakUpLargeNumbers(math_floor(amount + 0.5))
    end
    return tostring(math_floor(amount + 0.5))
end

local function BuildSecretZeroSafeValueText(amount)
    if not C_StringUtil or type(C_StringUtil.TruncateWhenZero) ~= "function" then
        return nil
    end
    return C_StringUtil.TruncateWhenZero(amount)
end

local function BuildPreviewText()
    if EX_DB.displayMode == "总血量百分比" then
        local decimals = NormalizeDecimalPlaces(EX_DB.decimalPlaces)
        local percent = (PREVIEW_VALUE / PREVIEW_MAX_HEALTH) * 100
        return string.format("%." .. decimals .. "f%%", percent)
    end
    return FormatShieldValue(PREVIEW_VALUE)
end

local function BuildSecretPercentText(amount, maxHealth)
    if not AbbreviateNumbers or type(maxHealth) ~= "number" or maxHealth <= 0 then
        return nil
    end

    local decimals = NormalizeDecimalPlaces(EX_DB.decimalPlaces)
    local fractionDivisor = 1
    if decimals == 1 then
        fractionDivisor = 10
    elseif decimals >= 2 then
        fractionDivisor = 100
    end

    -- AbbreviateNumbers 的小数位不是“先算百分比再保留几位”，
    -- 而是要同步缩小 significandDivisor，让格式化后的商值再按 fractionDivisor 回退。
    local significandDivisor = maxHealth / (100 * fractionDivisor)

    return AbbreviateNumbers(amount, {
        breakpointData = {
            {
                breakpoint = 0,
                abbreviation = "%",
                significandDivisor = significandDivisor,
                fractionDivisor = fractionDivisor,
                abbreviationIsGlobal = false,
            },
        },
    })
end

local function BuildLiveText()
    local shieldAmount, shieldAmountIsSecret = GetPlayerShieldAmount()
    if EX_DB.displayMode == "总血量百分比" then
        local maxHealth, maxHealthIsSecret = GetPlayerMaxHealth()
        if shieldAmountIsSecret then
            if not maxHealthIsSecret then
                local percentText = BuildSecretPercentText(shieldAmount, maxHealth)
                if percentText ~= nil then
                    return shieldAmount, shieldAmountIsSecret, percentText
                end
            end
            return shieldAmount, shieldAmountIsSecret, L["保密"]
        end

        local decimals = NormalizeDecimalPlaces(EX_DB.decimalPlaces)
        local shieldPercent, percentIsSecret = GetPlayerShieldPercent(shieldAmount, false)
        if percentIsSecret or maxHealthIsSecret then
            if not maxHealthIsSecret then
                local percentText = BuildSecretPercentText(shieldAmount, maxHealth)
                if percentText ~= nil then
                    return shieldAmount, shieldAmountIsSecret, percentText
                end
            end
            return shieldAmount, shieldAmountIsSecret, L["保密"]
        end
        return shieldAmount, shieldAmountIsSecret, string.format("%." .. decimals .. "f%%", shieldPercent)
    end
    if shieldAmountIsSecret and EX_DB.hideWhenZero and not EX_DB.abbreviateNumber then
        local zeroSafeText = BuildSecretZeroSafeValueText(shieldAmount)
        if zeroSafeText ~= nil then
            return shieldAmount, shieldAmountIsSecret, zeroSafeText
        end
    end
    return shieldAmount, shieldAmountIsSecret, FormatShieldValue(shieldAmount)
end

local function SetFrameVisible(visible)
    if not displayFrame then return end
    if visible then
        displayFrame:Show()
    else
        displayFrame:Hide()
    end
end

local function RefreshDisplay()
    refreshTimer = nil
    if not displayFrame or not displayText then return end

    RestorePosition()
    ApplyFontStyle()

    if isEditModeActive then
        if not isEditModeVisible then
            SetFrameVisible(false)
            RefreshEditOverlay()
            return
        end

        displayText:SetText(BuildPreviewText())
        SetFrameVisible(true)
        displayFrame:EnableMouse(true)
        RefreshEditOverlay()
        return
    end

    displayFrame:EnableMouse(false)
    RefreshEditOverlay()

    if not EX_DB.enabled then
        SetFrameVisible(false)
        return
    end

    local shieldAmount, shieldAmountIsSecret, text = BuildLiveText()
    if EX_DB.hideWhenZero and not shieldAmountIsSecret and math_abs(tonumber(shieldAmount) or 0) <= 0.0001 then
        SetFrameVisible(false)
        return
    end

    if text == nil then
        text = ""
    end
    displayText:SetText(text)
    SetFrameVisible(true)
end

local function ScheduleRefresh()
    if refreshTimer then
        return
    end

    if C_Timer and type(C_Timer.NewTimer) == "function" then
        refreshTimer = C_Timer.NewTimer(REFRESH_THROTTLE, RefreshDisplay)
        return
    end

    if C_Timer and type(C_Timer.After) == "function" then
        refreshTimer = true
        C_Timer.After(REFRESH_THROTTLE, RefreshDisplay)
        return
    end

    RefreshDisplay()
end

local function ResetPosition()
    EX_DB.point = EX_DEFAULTS.point
    EX_DB.relativePoint = EX_DEFAULTS.relativePoint
    EX_DB.xOffset = EX_DEFAULTS.xOffset
    EX_DB.yOffset = EX_DEFAULTS.yOffset
    RestorePosition()
    SavePosition()
end

local function CreateDisplayFrame()
    if displayFrame then return end

    local frame = CreateFrame("Frame", "ExwindPlayerShieldFrame", UIParent)
    frame:SetSize(240, 60)
    frame:SetPoint("CENTER", UIParent, "CENTER", EX_DB.xOffset or 0, EX_DB.yOffset or -120)
    frame:SetMovable(true)
    frame:RegisterForDrag("LeftButton")
    frame:SetClampedToScreen(false)

    frame.Text = frame:CreateFontString(nil, "OVERLAY")
    frame.Text:SetPoint("CENTER")
    frame.Text:SetJustifyH("CENTER")

    frame:SetScript("OnMouseDown", function(self, button)
        if button == "LeftButton" then
            StartFrameMove(self)
        elseif button == "RightButton" and ExwindTools.GlobalEditMode then
            ExwindTools:OpenConfig(EXWIND_MODULE_KEY)
        end
    end)

    frame:SetScript("OnMouseUp", function(self, button)
        if button == "LeftButton" then
            StopFrameMove(self)
        end
    end)

    frame:SetScript("OnDragStart", function(self)
        StartFrameMove(self)
    end)

    frame:SetScript("OnDragStop", function(self)
        StopFrameMove(self)
    end)

    displayFrame = frame
    displayText = frame.Text

    ExwindTools:RegisterHUD(EXWIND_MODULE_KEY, frame)
    frame:EnableMouse(false)
end

CreateDisplayFrame()

ExwindTools:RegisterEditModeHandler(EXWIND_MODULE_KEY, {
    EnterEditMode = function()
        isEditModeActive = true
        isEditModeVisible = true
        RefreshDisplay()
    end,
    ExitEditMode = function()
        isEditModeActive = false
        isEditModeVisible = true
        RefreshDisplay()
    end,
    SetEditVisible = function(_, visible)
        isEditModeVisible = (visible ~= false)
        RefreshDisplay()
    end,
})

ExwindTools:RegisterEvent("UNIT_ABSORB_AMOUNT_CHANGED", EXWIND_MODULE_KEY, function(_, unit)
    if unit == "player" then
        ScheduleRefresh()
    end
end)

ExwindTools:RegisterEvent("UNIT_MAXHEALTH", EXWIND_MODULE_KEY, function(_, unit)
    if unit == "player" then
        ScheduleRefresh()
    end
end)

ExwindTools:RegisterEvent("PLAYER_ENTERING_WORLD", EXWIND_MODULE_KEY, function()
    ScheduleRefresh()
end)

ExwindTools:WatchState(EXWIND_MODULE_KEY .. ".DatabaseChanged", EXWIND_MODULE_KEY, function()
    ScheduleRefresh()
end)

ExwindTools:WatchState(EXWIND_MODULE_KEY .. ".ButtonClicked", EXWIND_MODULE_KEY, function(info)
    if not info or info.key ~= "btn_resetPos" then
        return
    end
    ResetPosition()
    ScheduleRefresh()
end)

ScheduleRefresh()
ExwindTools:ReportReady(EXWIND_MODULE_KEY)
