---@type Orbit
local Orbit = Orbit
local L = Orbit.L
local OrbitEngine = Orbit.Engine
local LSM = LibStub("LibSharedMedia-3.0")

local SMOOTH_ANIM = Enum.StatusBarInterpolation.ExponentialEaseOut
local UPDATE_INTERVAL = 0.05
local AUGMENTATION_SPEC_ID = Orbit.ResourceBarMixin.AUGMENTATION_SPEC_ID
local TICK_SIZE_DEFAULT = OrbitEngine.TickMixin.TICK_SIZE_DEFAULT
local TICK_SIZE_MAX = OrbitEngine.TickMixin.TICK_SIZE_MAX
local TICK_OVERSHOOT = OrbitEngine.TickMixin.TICK_OVERSHOOT
local TICK_ALPHA_CURVE = OrbitEngine.TickMixin.TICK_ALPHA_CURVE
local TEXT_HEIGHT_PADDING = 2
local TEXT_BOTTOM_OFFSET = -2
local _, PLAYER_CLASS = UnitClass("player")

-- [ POWER TYPE CURVE CONFIG ] -----------------------------------------------------------------------
local POWER_CURVE_CONFIG = {
    { key = "ManaColorCurve", label = L.PLU_PPOWER_MANA, powerType = Enum.PowerType.Mana },
    { key = "RageColorCurve", label = L.PLU_PPOWER_RAGE, powerType = Enum.PowerType.Rage },
    { key = "FocusColorCurve", label = L.PLU_PPOWER_FOCUS, powerType = Enum.PowerType.Focus },
    { key = "EnergyColorCurve", label = L.PLU_PPOWER_ENERGY, powerType = Enum.PowerType.Energy },
    { key = "RunicPowerColorCurve", label = L.PLU_PPOWER_RUNIC, powerType = Enum.PowerType.RunicPower },
    { key = "LunarPowerColorCurve", label = L.PLU_PPOWER_ASTRAL, powerType = Enum.PowerType.LunarPower },
    { key = "FuryColorCurve", label = L.PLU_PPOWER_FURY, powerType = Enum.PowerType.Fury },
    { key = "InsanityColorCurve", label = L.PLU_PPOWER_INSANITY, powerType = Enum.PowerType.Insanity },
    { key = "MaelstromColorCurve", label = L.PLU_PPOWER_MAELSTROM, powerType = Enum.PowerType.Maelstrom },
}

local CLASS_POWER_TYPES = {
    WARRIOR = { Enum.PowerType.Rage },
    PALADIN = { Enum.PowerType.Mana },
    HUNTER = { Enum.PowerType.Focus },
    ROGUE = { Enum.PowerType.Energy },
    PRIEST = { Enum.PowerType.Mana, Enum.PowerType.Insanity },
    DEATHKNIGHT = { Enum.PowerType.RunicPower },
    SHAMAN = { Enum.PowerType.Mana, Enum.PowerType.Maelstrom },
    MAGE = { Enum.PowerType.Mana },
    WARLOCK = { Enum.PowerType.Mana },
    MONK = { Enum.PowerType.Energy, Enum.PowerType.Mana },
    DRUID = { Enum.PowerType.Energy, Enum.PowerType.Rage, Enum.PowerType.LunarPower, Enum.PowerType.Mana },
    DEMONHUNTER = { Enum.PowerType.Fury },
    EVOKER = { Enum.PowerType.Mana },
}

local POWER_TYPE_TO_CURVE_KEY = {}
for _, cfg in ipairs(POWER_CURVE_CONFIG) do
    POWER_TYPE_TO_CURVE_KEY[cfg.powerType] = cfg.key
end

-- [ HELPERS ] ---------------------------------------------------------------------------------------
local CanUseUnitPowerPercent = Orbit.SecretValueUtils.CanUseUnitPowerPercent
local SafeUnitPowerPercent = Orbit.SecretValueUtils.SafeUnitPowerPercent

-- [ PLUGIN REGISTRATION ]----------------------------------------------------------------------------
local SYSTEM_ID = "Orbit_PlayerPower"
local SYSTEM_INDEX = 1

local Plugin = Orbit:RegisterPlugin("Player Power", SYSTEM_ID, {
    displayName = L.PLG_NAME_PLAYER_POWER,
    liveToggle = true,
    canvasMode = true,
    defaults = {
        Hidden = false,
        Width = 202,
        Height = 10,
        ShowText = false,
        ManaColorCurve = { pins = { { position = 0, color = { r = 0, g = 0, b = 1, a = 1 } } } },
        RageColorCurve = { pins = { { position = 0, color = { r = 1, g = 0, b = 0, a = 1 } } } },
        FocusColorCurve = { pins = { { position = 0, color = { r = 1, g = 0.5, b = 0.25, a = 1 } } } },
        EnergyColorCurve = { pins = { { position = 0, color = { r = 1, g = 1, b = 0, a = 1 } } } },
        RunicPowerColorCurve = { pins = { { position = 0, color = { r = 0, g = 0.82, b = 1, a = 1 } } } },
        LunarPowerColorCurve = { pins = { { position = 0, color = { r = 0.95, g = 0.9, b = 0.6, a = 1 } } } },
        FuryColorCurve = { pins = { { position = 0, color = { r = 1, g = 0.6, b = 0.2, a = 1 } } } },
        InsanityColorCurve = { pins = { { position = 0, color = { r = 0.6, g = 0.2, b = 1.0, a = 1 } } } },
        MaelstromColorCurve = { pins = { { position = 0, color = { r = 0.65, g = 0.63, b = 0.35, a = 1 } } } },
        EbonMightColorCurve = { pins = { { position = 0, color = { r = 0.2, g = 0.8, b = 0.4, a = 1 } } } },
        Opacity = 100,
        OutOfCombatFade = false,
        ShowOnMouseover = true,
        SmoothAnimation = true,
        FrequentUpdates = false,
        TickSize = TICK_SIZE_DEFAULT,
        DisabledComponents = { "Status" },
        ComponentPositions = {
            Text = { anchorX = "RIGHT", offsetX = 5, anchorY = "CENTER", offsetY = 0, posX = 135, posY = 0, justifyH = "RIGHT", selfAnchorY = "CENTER" },
        },
    },
})

local Frame, PowerBar

-- [ SHARED COMBAT-SAFE VISIBILITY ]------------------------------------------------------------------
local function SafeShow(frame)
    frame.orbitHiddenByAlpha = false
    local opacity = (Plugin:GetSetting(SYSTEM_INDEX, "Opacity") or 100) / 100
    if InCombatLockdown() and frame:IsProtected() then
        frame:SetAlpha(opacity)
    else
        frame:Show()
        frame:SetAlpha(opacity)
    end
end

local function SafeHide(frame)
    if InCombatLockdown() and frame:IsProtected() then
        frame:SetAlpha(0)
        frame.orbitHiddenByAlpha = true
    else
        frame:Hide()
        frame.orbitHiddenByAlpha = false
    end
end

local function SetupCombatCleanup(frame)
    frame:RegisterEvent("PLAYER_REGEN_ENABLED")
    frame:RegisterEvent("PLAYER_REGEN_DISABLED")
    frame:HookScript("OnEvent", function(self, event)
        if event == "PLAYER_REGEN_DISABLED" and not self:IsShown() then
            self:Show()
            self:SetAlpha(0)
            self.orbitHiddenByAlpha = true
        elseif event == "PLAYER_REGEN_ENABLED" and self.orbitHiddenByAlpha then
            self:Hide()
            self.orbitHiddenByAlpha = false
        end
    end)
end

Orbit.PlayerUtilShared = {
    SafeShow = SafeShow,
    SafeHide = SafeHide,
    SetupCombatCleanup = SetupCombatCleanup,
    CanUseUnitPowerPercent = CanUseUnitPowerPercent,
    SafeUnitPowerPercent = SafeUnitPowerPercent,
}

-- [ SETTINGS UI ]------------------------------------------------------------------------------------
function Plugin:AddSettings(dialog, systemFrame)
    if not Frame then
        return
    end

    local systemIndex = SYSTEM_INDEX
    local SB = OrbitEngine.SchemaBuilder

    if dialog.Title then
        dialog.Title:SetText(L.PLU_PPOWER_TITLE)
    end

    local schema = { hideNativeSettings = true, controls = {} }

    SB:SetTabRefreshCallback(dialog, self, systemFrame)
    local currentTab = SB:AddSettingsTabs(schema, dialog, { L.PLU_PPOWER_TAB_LAYOUT, L.PLU_PPOWER_TAB_BEHAVIOUR, L.PLU_PPOWER_TAB_COLOUR }, L.PLU_PPOWER_TAB_LAYOUT)

    if currentTab == L.PLU_PPOWER_TAB_LAYOUT then
        local isAnchored = OrbitEngine.Frame:GetAnchorParent(Frame) ~= nil
        if not isAnchored then
            SB:AddSizeSettings(self, schema, systemIndex, systemFrame, { default = 200 }, nil, nil)
        end
        SB:AddSizeSettings(self, schema, systemIndex, systemFrame, nil, { min = 4, max = 25, default = 12 }, nil)
        table.insert(schema.controls, {
            type = "slider",
            key = "TickSize",
            label = L.PLU_PPOWER_TICK,
            min = 0,
            max = TICK_SIZE_MAX,
            step = 2,
            default = TICK_SIZE_DEFAULT,
            tooltip = L.PLU_PPOWER_TICK_TT,
            onChange = function(val)
                self:SetSetting(systemIndex, "TickSize", val)
                self:ApplySettings()
            end,
        })
    elseif currentTab == L.PLU_PPOWER_TAB_BEHAVIOUR then
        table.insert(schema.controls, {
            type = "checkbox",
            key = "SmoothAnimation",
            label = L.PLU_PPOWER_SMOOTH,
            default = true,
            tooltip = L.PLU_PPOWER_SMOOTH_TT,
            onChange = function(val)
                self:SetSetting(systemIndex, "SmoothAnimation", val)
                self:RefreshPerfCache()                                 -- cache stays coherent with the toggle
            end,
        })
        table.insert(schema.controls, {
            type = "checkbox",
            key = "FrequentUpdates",
            label = L.PLU_PPOWER_FREQ_UPDATE,
            default = false,
            tooltip = L.PLU_PPOWER_FREQ_UPDATE_TT,
            onChange = function(val)
                self:SetSetting(systemIndex, "FrequentUpdates", val)
                self:RefreshFrequentUpdates()
            end,
        })
    elseif currentTab == L.PLU_PPOWER_TAB_COLOUR then
        local classPowerTypes = CLASS_POWER_TYPES[PLAYER_CLASS] or {}
        local classPowerLookup = {}
        for _, pt in ipairs(classPowerTypes) do
            classPowerLookup[pt] = true
        end
        for _, cfg in ipairs(POWER_CURVE_CONFIG) do
            if classPowerLookup[cfg.powerType] then
                table.insert(schema.controls, {
                    type = "colorcurve",
                    key = cfg.key,
                    label = cfg.label,
                    onChange = function(curveData)
                        self:SetSetting(systemIndex, cfg.key, curveData)
                        self:UpdateAll()
                    end,
                })
            end
        end
        if PLAYER_CLASS == "EVOKER" then
            table.insert(schema.controls, {
                type = "colorcurve",
                key = "EbonMightColorCurve",
                label = L.PLU_PPOWER_EBON_MIGHT,
                onChange = function(curveData)
                    self:SetSetting(systemIndex, "EbonMightColorCurve", curveData)
                    self:UpdateAll()
                end,
            })
        end
    end

    OrbitEngine.Config:Render(dialog, systemFrame, self, schema)
end

-- [ LIFECYCLE ]--------------------------------------------------------------------------------------
function Plugin:OnLoad()
    Frame, PowerBar = OrbitEngine.FrameFactory:CreateWithBar("PlayerPower", self, {
        width = 200,
        height = 12,
        y = -160,
        systemIndex = SYSTEM_INDEX,
        template = "BackdropTemplate",
        anchorOptions = { horizontal = false, vertical = true, mergeBorders = { x = false, y = true } },
    })
    Frame.orbitWidthSync = true
    Frame.orbitResizeBounds = { minW = 100, maxW = 600, minH = 4, maxH = 25 }
    self.frame = Frame
    self.mountedConfig = { frame = Frame }

    -- [ CANVAS PREVIEW ] -------------------------------------------------------------------------------
    function Frame:CreateCanvasPreview(options)
        local scale = options.scale or 1
        local borderSize = options.borderSize or OrbitEngine.Pixel:DefaultBorderSize(scale)

        -- Base container
        local preview = OrbitEngine.Preview.Frame:CreateBasePreview(self, scale, options.parent, borderSize)

        -- Create Power Bar visual
        local bar = CreateFrame("StatusBar", nil, preview)
        bar:SetPoint("TOPLEFT", preview, "TOPLEFT", 0, 0)
        bar:SetPoint("BOTTOMRIGHT", preview, "BOTTOMRIGHT", 0, 0)
        bar:SetMinMaxValues(0, 1)
        bar:SetValue(1)

        local textureName = Plugin:GetSetting(SYSTEM_INDEX, "Texture")
        bar:SetStatusBarTexture(LSM:Fetch("statusbar", textureName))
        Orbit.Skin:RegisterMaskedSurface(preview, bar:GetStatusBarTexture())

        -- Color (use per-power-type curve for preview)
        local powerType = UnitPowerType("player")
        local curveKey = POWER_TYPE_TO_CURVE_KEY[powerType]
        local curveData = curveKey and Plugin:GetSetting(SYSTEM_INDEX, curveKey)
        local color = curveData and OrbitEngine.ColorCurve:GetFirstColorFromCurve(curveData)
        if color then
            bar:SetStatusBarColor(color.r, color.g, color.b)
        end

        preview.PowerBar = bar
        Orbit.Skin:UpdateRoundedMask(preview, false)
        return preview
    end

    -- Text overlay
    if not Frame.Text then
        OrbitEngine.FrameFactory:AddText(Frame, { point = "BOTTOM", relativePoint = "BOTTOM", x = 0, y = -2, useOverlay = true })
    end

    -- Alias
    Frame.PowerBar = PowerBar

    -- Tick mark (geometry-clipped, secret-safe)
    OrbitEngine.TickMixin:Create(Frame, PowerBar)

    self:ApplySettings()

    -- Events
    Frame:RegisterUnitEvent("UNIT_POWER_UPDATE", "player")
    Frame:RegisterUnitEvent("UNIT_MAXPOWER", "player")
    Frame:RegisterUnitEvent("UNIT_DISPLAYPOWER", "player")
    Frame:RegisterUnitEvent("UNIT_AURA", "player")
    Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
    Frame:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
    Frame:RegisterEvent("PET_BATTLE_OPENING_START")
    Frame:RegisterEvent("PET_BATTLE_CLOSE")

    self:RefreshFrequentUpdates()

    Frame:SetScript("OnEvent", function(f, event)
        if event == "PET_BATTLE_OPENING_START" or event == "PET_BATTLE_CLOSE" then
            Orbit:SafeAction(function()
                self:UpdateVisibility()
            end)
        elseif event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_SPECIALIZATION_CHANGED" then
            self:UpdateVisibility()
            self:RefreshOnUpdate()
        else
            self:UpdateAll()
        end
    end)

    -- Edit Mode
    OrbitEngine.EditMode:RegisterCallbacks({
        Enter = function()
            self:UpdateVisibility()
        end,
        Exit = function()
            self:UpdateVisibility()
        end,
    }, self)

    -- Canvas Mode: Register draggable components
    if OrbitEngine.ComponentDrag and Frame.Text then
        OrbitEngine.ComponentDrag:Attach(Frame.Text, Frame, {
            key = "Text",
            onPositionChange = OrbitEngine.ComponentDrag:MakePositionCallback(self, SYSTEM_INDEX, "Text"),
        })
    end

    SetupCombatCleanup(Frame)

    self:UpdateVisibility()
    self:RefreshOnUpdate()
end

function Plugin:RefreshFrequentUpdates()
    if not Frame then return end
    if self:GetSetting(SYSTEM_INDEX, "FrequentUpdates") then
        Frame:RegisterUnitEvent("UNIT_POWER_FREQUENT", "player")
    else
        Frame:UnregisterEvent("UNIT_POWER_FREQUENT")
    end
end

-- Refreshed on PLAYER_SPECIALIZATION_CHANGED + ApplySettings; the 20Hz Aug-Evoker OnUpdate reads only cached fields (no per-tick GetSpecialization / GetSetting walk).
function Plugin:RefreshPerfCache()
    self._cachedSmoothing = (self:GetSetting(SYSTEM_INDEX, "SmoothAnimation") ~= false) and SMOOTH_ANIM or nil
    local _, class = UnitClass("player")
    local spec = GetSpecialization()
    local specID = spec and GetSpecializationInfo(spec)
    self._cachedIsAugEvoker = (class == "EVOKER" and specID == AUGMENTATION_SPEC_ID)
end

function Plugin:RefreshOnUpdate()
    self:RefreshPerfCache()
    Frame:SetScript("OnUpdate", self._cachedIsAugEvoker and function(_, elapsed)
        Frame.elapsed = (Frame.elapsed or 0) + elapsed
        if Frame.elapsed >= UPDATE_INTERVAL then
            Frame.elapsed = 0
            local p = Orbit.Profiler
            local s = p and p:Begin()
            self:UpdateAll()
            if p then p:End(self, "OnUpdate", s) end
        end
    end or nil)
end

function Plugin:UpdateVisibility()
    if not Frame then return end
    if not Orbit:IsPluginEnabled(self.name) then SafeHide(Frame); return end

    if C_PetBattles and C_PetBattles.IsInBattle() then
        SafeHide(Frame)
        return
    end

    local isEditMode = Orbit:IsEditMode()
    if isEditMode then
        OrbitEngine.FrameAnchor:SetFrameDisabled(Frame, false)
        SafeShow(Frame)
        return
    end
    SafeShow(Frame)
    self:UpdateAll()
    OrbitEngine.FrameAnchor:SetFrameDisabled(Frame, false)
end

-- [ SETTINGS APPLICATION ]---------------------------------------------------------------------------
function Plugin:ApplySettings()
    if not Frame then
        return
    end
    if InCombatLockdown() then
        Orbit.CombatManager:QueueUpdate(function() self:ApplySettings() end)
        return
    end

    self:RefreshPerfCache()
    local systemIndex = SYSTEM_INDEX
    local width = self:GetSetting(systemIndex, "Width")
    local height = self:GetSetting(systemIndex, "Height")
    local borderSize = self:GetSetting(systemIndex, "BorderSize")
    local textureName = self:GetSetting(systemIndex, "Texture")
    local textSize = 18
    local fontName = self:GetSetting(systemIndex, "Font")
    local isAnchored = OrbitEngine.Frame:GetAnchorParent(Frame) ~= nil

    Frame:SetHeight(height)
    if not isAnchored then
        Frame:SetWidth(width)
    end

    -- Texture
    Orbit.Skin:SkinStatusBar(PowerBar, textureName, nil, true)
    local tickSize = self:GetSetting(systemIndex, "TickSize") or TICK_SIZE_DEFAULT
    OrbitEngine.TickMixin:Apply(Frame, tickSize, height)

    -- Border
    Frame:SetBorder(borderSize)

    -- Backdrop Color (gradient-aware)
    Orbit.Skin:ApplyGradientBackground(Frame, Orbit.db.GlobalSettings.UnitFrameBackdropColourCurve, Orbit.Constants.Colors.Background)

    -- Text (controlled via Canvas Mode)
    if OrbitEngine.ComponentDrag:IsDisabled(Frame.Text) then
        Frame.Text:Hide()
    else
        Frame.Text:Show()

        -- Get Canvas Mode overrides
        local positions = self:GetComponentPositions(systemIndex)
        local textPos = positions.Text or {}
        local overrides = textPos.overrides or {}

        -- Apply font, size, and color overrides
        local fontPath = LSM:Fetch("font", fontName)
        OrbitEngine.OverrideUtils.ApplyOverrides(Frame.Text, overrides, { fontSize = textSize, fontPath = fontPath })

        -- Read back final size for position calculation
        local _, finalSize = Frame.Text:GetFont()
        finalSize = finalSize or textSize

        Frame.Text:ClearAllPoints()
        if height > (finalSize + TEXT_HEIGHT_PADDING) then
            Frame.Text:SetPoint("CENTER", Frame.Overlay, "CENTER", 0, 0)
        else
            Frame.Text:SetPoint("BOTTOM", Frame.Overlay, "BOTTOM", 0, TEXT_BOTTOM_OFFSET)
        end
        Frame.Text:SetJustifyH("CENTER")
    end

    -- Restore Position
    OrbitEngine.Frame:RestorePosition(Frame, self, systemIndex)

    -- Restore component positions (Canvas Mode)
    local savedPositions = self:GetComponentPositions(systemIndex)
    if savedPositions and OrbitEngine.ComponentDrag then
        OrbitEngine.ComponentDrag:RestoreFramePositions(Frame, savedPositions)
    end

    if OrbitEngine.Frame.ForceUpdateSelection then
        OrbitEngine.Frame:ForceUpdateSelection(Frame)
    end

    -- Apply Opacity (hover fade enforcement)
    local baseAlpha = (self:GetSetting(systemIndex, "Opacity") or 100) / 100
    Orbit.Animation:ApplyHoverFade(Frame, baseAlpha, 1, Orbit:IsEditMode())

    -- Apply Out of Combat Fade (with hover detection based on setting)
    local enableHover = self:GetSetting(systemIndex, "ShowOnMouseover") ~= false
    if Orbit.OOCFadeMixin then Orbit.OOCFadeMixin:ApplyOOCFade(Frame, self, systemIndex, "OutOfCombatFade", enableHover) end

    self:UpdateVisibility()
end

function Plugin:UpdateAll()
    if not Frame or not PowerBar or not Frame:IsShown() then
        return
    end

    -- Aug Evoker → Ebon Might bar; _cachedIsAugEvoker is refreshed on spec change so this hot path never calls GetSpecialization. GetEbonMightState returns 0/max unconditionally so no gating needed.
    if self._cachedIsAugEvoker then
        local current, max = Orbit.ResourceBarMixin:GetEbonMightState()
        PowerBar:SetMinMaxValues(0, max)
        local smoothing = self._cachedSmoothing
        PowerBar:SetValue(current, smoothing)
        OrbitEngine.TickMixin:Update(Frame, current, max, smoothing)

        local curveData = self:GetSetting(SYSTEM_INDEX, "EbonMightColorCurve")
        local color = curveData and OrbitEngine.ColorCurve:GetFirstColorFromCurve(curveData)
        if color then
            PowerBar:SetStatusBarColor(color.r, color.g, color.b)
        end

        if Frame.Text:IsShown() then
            Frame.Text:SetFormattedText("%.1f", current)
        end
        return
    end

    local powerType, powerToken = UnitPowerType("player")
    local cur = UnitPower("player", powerType)
    local max = UnitPowerMax("player", powerType)

    PowerBar:SetMinMaxValues(0, max)
    local smoothing = self._cachedSmoothing
    PowerBar:SetValue(cur, smoothing)
    OrbitEngine.TickMixin:Update(Frame, cur, max, smoothing)
    if TICK_ALPHA_CURVE and CanUseUnitPowerPercent then
        Frame.TickMark:SetAlpha(UnitPowerPercent("player", powerType, false, TICK_ALPHA_CURVE))
    end

    -- Color: reset vertex tint then apply per-power-type curve
    PowerBar:GetStatusBarTexture():SetVertexColor(1, 1, 1, 1)
    local curveKey = POWER_TYPE_TO_CURVE_KEY[powerType]
    local curveData = curveKey and self:GetSetting(SYSTEM_INDEX, curveKey)

    if curveData then
        local nativeCurve = OrbitEngine.ColorCurve:ToNativeColorCurve(curveData)
        if nativeCurve and CanUseUnitPowerPercent then
            local ok, color = pcall(UnitPowerPercent, "player", powerType, false, nativeCurve)
            if ok and color then
                PowerBar:GetStatusBarTexture():SetVertexColor(color:GetRGBA())
            end
        else
            local color = OrbitEngine.ColorCurve:GetFirstColorFromCurve(curveData)
            if color then
                PowerBar:SetStatusBarColor(color.r, color.g, color.b)
            end
        end
    end

    -- Text
    if Frame.Text:IsShown() then
        if powerToken == "MANA" then
            local percent = SafeUnitPowerPercent("player", powerType)
            if percent then
                Frame.Text:SetFormattedText("%.0f", percent)
            else
                Frame.Text:SetText(cur)
            end
        else
            Frame.Text:SetText(cur)
        end
    end
end
