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

-- Compatibility for 12.0 / Native Smoothing
local SMOOTH_ANIM = Enum.StatusBarInterpolation and Enum.StatusBarInterpolation.ExponentialEaseOut

-- [ HELPERS ]----------------------------------------------------------------------------------------
local function SafeUnitPowerPercent(unit, resource)
    if type(UnitPowerPercent) ~= "function" or not CurveConstants or not CurveConstants.ScaleTo100 then
        return nil
    end
    local ok, pct = pcall(UnitPowerPercent, unit, resource, false, CurveConstants.ScaleTo100)
    return (ok and pct) or nil
end

-- Create native Blizzard ColorCurve from our pin data (for secret-safe color sampling)
local function CreateNativeColorCurve(curveData)
    if not curveData or not curveData.pins or #curveData.pins == 0 then return nil end
    if not C_CurveUtil or not C_CurveUtil.CreateColorCurve then return nil end
    
    local colorCurve = C_CurveUtil.CreateColorCurve()
    
    for _, pin in ipairs(curveData.pins) do
        local c = pin.color
        colorCurve:AddPoint(pin.position, CreateColor(c.r, c.g, c.b, c.a or 1))
    end
    return colorCurve
end

-- Get power color using native ColorCurve (returns non-secret color)
local function GetPowerColorFromCurve(unit, powerType, curveData)
    local nativeCurve = CreateNativeColorCurve(curveData)
    if not nativeCurve then return nil end
    
    local ok, color = pcall(UnitPowerPercent, unit, powerType, false, nativeCurve)
    return ok and color or nil
end

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

local Plugin = Orbit:RegisterPlugin("Player Power", SYSTEM_ID, {
    canvasMode = true,
    defaults = {
        Enabled = true,
        Hidden = false,
        Width = 200,
        Height = 15,
        UseCustomColor = false,
        BarColor = { r = 1, g = 1, b = 1, a = 1 },
        BarColorCurve = { pins = { { position = 0, color = { r = 1, g = 1, b = 1, a = 1 } } } },
        Opacity = 100,
        OutOfCombatFade = false,
        ShowOnMouseover = true,
    },
}, Orbit.Constants.PluginGroups.UnitFrames)

-- Frame references (created in OnLoad)
local Frame, PowerBar

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

    local systemIndex = SYSTEM_INDEX
    local WL = OrbitEngine.WidgetLogic

    if dialog.Title then
        dialog.Title:SetText("Player Power")
    end

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

    WL:SetTabRefreshCallback(dialog, self, systemFrame)
    local currentTab = WL:AddSettingsTabs(schema, dialog, { "Layout", "Visibility", "Colour" }, "Layout")

    if currentTab == "Layout" then
        local playerPlugin = Orbit:GetPlugin("Orbit_PlayerFrame")
        if not playerPlugin then
            table.insert(schema.controls, {
                type = "checkbox", key = "Enabled", label = "Enable", default = true,
                onChange = function(val)
                    self:SetSetting(systemIndex, "Enabled", val)
                    self:UpdateVisibility()
                end,
            })
        end
        local isAnchored = OrbitEngine.Frame:GetAnchorParent(Frame) ~= nil
        if not isAnchored then
            WL:AddSizeSettings(self, schema, systemIndex, systemFrame, { default = 200 }, nil, nil)
        end
        WL:AddSizeSettings(self, schema, systemIndex, systemFrame, nil, { min = 5, max = 50, default = 15 }, nil)
    elseif currentTab == "Visibility" then
        WL:AddOpacitySettings(self, schema, systemIndex, systemFrame, { step = 5 })
        table.insert(schema.controls, {
            type = "checkbox", key = "OutOfCombatFade", label = "Out of Combat Fade", default = false,
            tooltip = "Hide frame when out of combat with no target",
            onChange = function(val)
                self:SetSetting(systemIndex, "OutOfCombatFade", val)
                if Orbit.OOCFadeMixin then Orbit.OOCFadeMixin:RefreshAll() end
                if dialog.orbitTabCallback then dialog.orbitTabCallback() end
            end,
        })
        if self:GetSetting(systemIndex, "OutOfCombatFade") then
            table.insert(schema.controls, {
                type = "checkbox", key = "ShowOnMouseover", label = "Show on Mouseover", default = true,
                tooltip = "Reveal frame when mousing over it",
                onChange = function(val)
                    self:SetSetting(systemIndex, "ShowOnMouseover", val)
                    self:ApplySettings()
                end,
            })
        end
    elseif currentTab == "Colour" then
        table.insert(schema.controls, {
            type = "checkbox", key = "UseCustomColor", label = "Use Custom Color", default = false,
            onChange = function(val)
                self:SetSetting(systemIndex, "UseCustomColor", val)
                self:UpdateAll()
                if dialog.orbitTabCallback then dialog.orbitTabCallback() end
            end,
        })
        if self:GetSetting(systemIndex, "UseCustomColor") then
            table.insert(schema.controls, {
                type = "colorcurve", key = "BarColorCurve", label = "Bar Color",
                default = { pins = { { position = 0, color = { r = 1, g = 1, b = 1, a = 1 } } } },
                onChange = function(curveData)
                    self:SetSetting(systemIndex, "BarColorCurve", curveData)
                    self:UpdateAll()
                end,
            })
        end
    end

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

-- [ LIFECYCLE ]-------------------------------------------------------------------------------------
function Plugin:OnLoad()
    Frame, PowerBar = OrbitEngine.FrameFactory:CreateWithBar("PlayerPower", self, {
        width = 200,
        height = 15,
        y = -160,
        systemIndex = SYSTEM_INDEX,
        template = "BackdropTemplate",
        anchorOptions = { horizontal = false, vertical = true, mergeBorders = true },
    })
    self.frame = Frame

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

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

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

        -- Appearance
        local textureName = Plugin:GetSetting(SYSTEM_INDEX, "Texture")
        local texturePath = "Interface\\Buttons\\WHITE8x8"
        if textureName and LSM then
            texturePath = LSM:Fetch("statusbar", textureName) or texturePath
        end
        bar:SetStatusBarTexture(texturePath)

        -- Color (use player class color or power color default)
        local powerType = UnitPowerType("player")
        local info = Orbit.Constants.Colors.PowerType[powerType]
        if info then
            bar:SetStatusBarColor(info.r, info.g, info.b)
        else
            bar:SetStatusBarColor(0.5, 0.5, 0.5)
        end

        preview.PowerBar = bar
        return preview
    end

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

    -- Alias
    Frame.PowerBar = PowerBar

    self:ApplySettings()

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

    Frame:SetScript("OnEvent", function(f, event)
        if event == "PLAYER_ENTERING_WORLD" then
            self:UpdateVisibility()
        else
            self:UpdateAll()
        end
    end)

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

    -- Canvas Mode: Register draggable components
    if OrbitEngine.ComponentDrag and Frame.Text then
        OrbitEngine.ComponentDrag:Attach(Frame.Text, Frame, {
            key = "Text",
            onPositionChange = function(component, anchorX, anchorY, offsetX, offsetY, justifyH)
                local positions = self:GetSetting(SYSTEM_INDEX, "ComponentPositions") or {}
                positions.Text = { anchorX = anchorX, anchorY = anchorY, offsetX = offsetX, offsetY = offsetY, justifyH = justifyH }
                self:SetSetting(SYSTEM_INDEX, "ComponentPositions", positions)
            end,
        })
    end

    self:UpdateVisibility()
end

-- [ VISIBILITY ]-------------------------------------------------------------------------------------
function Plugin:IsEnabled()
    local localEnabled = self:GetSetting(SYSTEM_INDEX, "Enabled")
    if localEnabled == false then
        return false
    end

    -- If Orbit_UnitFrames is loaded, also respect its EnablePlayerPower setting
    local playerPlugin = Orbit:GetPlugin("Orbit_PlayerFrame")
    local PLAYER_FRAME_INDEX = Enum.EditModeUnitFrameSystemIndices.Player
    if playerPlugin and playerPlugin.GetSetting then
        local enabled = playerPlugin:GetSetting(PLAYER_FRAME_INDEX, "EnablePlayerPower")
        if enabled ~= nil then
            return enabled == true
        end
    end
    return true
end

function Plugin:UpdateVisibility()
    if not Frame then
        return
    end

    local enabled = self:IsEnabled()
    local isEditMode = EditModeManagerFrame and EditModeManagerFrame.IsEditModeActive and EditModeManagerFrame:IsEditModeActive()

    if isEditMode then
        Frame:Show()
        Frame:SetAlpha(enabled and 1 or 0.5)
        return
    end

    if enabled then
        Frame:Show()
        self:UpdateAll()
        OrbitEngine.FrameAnchor:SetFrameDisabled(Frame, false)
    else
        Frame:Hide()
        OrbitEngine.FrameAnchor:SetFrameDisabled(Frame, true)
    end
end

-- [ SETTINGS APPLICATION ]--------------------------------------------------------------------------
function Plugin:ApplySettings()
    if not Frame then
        return
    end

    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 = Orbit.Skin:GetAdaptiveTextSize(height, 18, 26, 1)
    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)

    -- Border
    Frame:SetBorder(borderSize)

    -- Backdrop Color
    local backdropColor = self:GetSetting(systemIndex, "BackdropColour")
    if backdropColor and Frame.bg then
        Frame.bg:SetColorTexture(backdropColor.r, backdropColor.g, backdropColor.b, backdropColor.a or 0.9)
    elseif Frame.bg then
        local c = Orbit.Constants.Colors.Background
        Frame.bg:SetColorTexture(c.r, c.g, c.b, c.a or 0.9)
    end

    -- 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:GetSetting(systemIndex, "ComponentPositions") or {}
        local textPos = positions.Text or {}
        local overrides = textPos.overrides or {}

        -- Apply font override
        local fontPath = LSM:Fetch("font", fontName)
        if overrides.Font and LSM then
            fontPath = LSM:Fetch("font", overrides.Font) or fontPath
        end

        -- Apply size override
        local finalSize = overrides.FontSize or textSize

        -- Apply font flags override
        local flags = Orbit.Skin:GetFontOutline()
        if overrides.ShowShadow then
            flags = ""
        end

        Frame.Text:SetFont(fontPath, finalSize, flags)

        -- Apply shadow if needed
        if overrides.ShowShadow then
            Frame.Text:SetShadowOffset(1, -1)
            Frame.Text:SetShadowColor(0, 0, 0, 1)
        else
            Frame.Text:SetShadowOffset(0, 0)
        end

        -- Apply color override
        if overrides.CustomColor and overrides.CustomColorValue and type(overrides.CustomColorValue) == "table" then
            local c = overrides.CustomColorValue
            Frame.Text:SetTextColor(c.r or 1, c.g or 1, c.b or 1, c.a or 1)
        end

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

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

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

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

    -- Apply Out of Combat Fade (with hover detection based on setting)
    if Orbit.OOCFadeMixin then
        local enableHover = self:GetSetting(systemIndex, "ShowOnMouseover") ~= false
        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

    -- Check for Augmentation Evoker Ebon Might (takes priority over normal power)
    local _, class = UnitClass("player")
    local spec = GetSpecialization()
    local specID = spec and GetSpecializationInfo(spec)

    if class == "EVOKER" and specID == 1473 then
        local current, max = Orbit.ResourceBarMixin:GetEbonMightState()
        if current and max and max > 0 then
            PowerBar:SetMinMaxValues(0, max)
            PowerBar:SetValue(current, SMOOTH_ANIM)

            -- Use Ebon Might color
            local color = Orbit.Colors.PlayerResources and Orbit.Colors.PlayerResources.EbonMight
            if color then
                PowerBar:SetStatusBarColor(color.r, color.g, color.b)
            else
                PowerBar:SetStatusBarColor(0.4, 0.6, 0.3) -- Fallback green
            end

            if Frame.Text:IsShown() then
                Frame.Text:SetFormattedText("%.0f", current)
            end
            return
        end
        -- If Ebon Might not active, fall through to show Mana
    end

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

    PowerBar:SetMinMaxValues(0, max)
    PowerBar:SetValue(cur, SMOOTH_ANIM)

    -- Color (use native ColorCurve with UnitPowerPercent for secret-safe gradient)
    local useCustomColor = self:GetSetting(SYSTEM_INDEX, "UseCustomColor")
    local curveData = self:GetSetting(SYSTEM_INDEX, "BarColorCurve")

    if useCustomColor and curveData then
        local color = GetPowerColorFromCurve("player", powerType, curveData)
        if color then
            PowerBar:GetStatusBarTexture():SetVertexColor(color:GetRGBA())
        else
            local fallback = OrbitEngine.WidgetLogic:GetFirstColorFromCurve(curveData)
            if fallback then PowerBar:SetStatusBarColor(fallback.r, fallback.g, fallback.b) end
        end
    else
        local info = Orbit.Constants.Colors.PowerType[powerType]
        if info then
            PowerBar:SetStatusBarColor(info.r, info.g, info.b)
        else
            PowerBar:SetStatusBarColor(0.5, 0.5, 0.5)
        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
