-- [ ORBIT FRAME PERSISTENCE ]-----------------------------------------------------------------------
-- Handles saving and restoring frame positions and anchors

local _, Orbit = ...
local Engine = Orbit.Engine

---@class OrbitFramePersistence
Engine.FramePersistence = {}
local Persistence = Engine.FramePersistence

-- Restore frame position or anchor from plugin settings
function Persistence:RestorePosition(frame, plugin, systemIndex)
    if not frame or not plugin then
        return false
    end

    -- Safety: Do not restore position while user is actively dragging the frame.
    if frame.orbitIsDragging then
        return false
    end

    -- Safety: Do not attempt to move protected frames during combat
    if InCombatLockdown() and frame:IsProtected() then
        return false
    end

    systemIndex = systemIndex or 1

    -- Try to restore ephemeral state first (Edit Mode Dirty State)
    if Engine.PositionManager then
        local anchor = Engine.PositionManager:GetAnchor(frame)
        if anchor and anchor.target then
            local targetFrame = _G[anchor.target]
            if targetFrame then
                Engine.FrameAnchor:CreateAnchor(frame, targetFrame, anchor.edge, anchor.padding or 0, nil, anchor.align, true)
                return true
            end
        end

        local pos = Engine.PositionManager:GetPosition(frame)
        if pos and pos.point then
            local x, y = pos.x, pos.y
            if Engine.Pixel then
                x, y = Engine.Pixel:SnapPosition(x, y, pos.point, frame:GetWidth(), frame:GetHeight(), frame:GetEffectiveScale())
            end
            frame:ClearAllPoints()
            frame:SetPoint(pos.point, x, y)
            return true
        end
    end

    -- Try to restore SavedVariables Anchor first
    local anchor = plugin:GetSetting(systemIndex, "Anchor")
    if anchor and anchor.target then
        local targetFrame = _G[anchor.target]
        if targetFrame then
            Engine.FrameAnchor:CreateAnchor(frame, targetFrame, anchor.edge, anchor.padding or 0, nil, anchor.align, true)
            return true
        end
    end

    -- Restore Position
    local pos = plugin:GetSetting(systemIndex, "Position")
    if pos and pos.point then
        if Engine.FrameAnchor and Engine.FrameAnchor:GetAnchorParent(frame) then
            Engine.FrameAnchor:BreakAnchor(frame, true)
        end
        local x, y = pos.x, pos.y
        if Engine.Pixel then
            x, y = Engine.Pixel:SnapPosition(x, y, pos.point, frame:GetWidth(), frame:GetHeight(), frame:GetEffectiveScale())
        end
        frame:ClearAllPoints()
        frame:SetPoint(pos.point, x, y)
        return true
    end

    -- Fallback: Reset to Default Position
    if frame.defaultPosition then
        local x = frame.defaultPosition.x
        local y = frame.defaultPosition.y
        if Engine.Pixel then
            x, y = Engine.Pixel:SnapPosition(x, y, frame.defaultPosition.point, frame:GetWidth(), frame:GetHeight(), frame:GetEffectiveScale())
        end

        frame:ClearAllPoints()
        frame:SetPoint(frame.defaultPosition.point, frame.defaultPosition.relativeTo, frame.defaultPosition.relativePoint, x, y)
        return true
    end

    return false
end

-- Attach listener for standard Orbit position/anchor saving
function Persistence:AttachSettingsListener(frame, plugin, systemIndex)
    if not frame or not plugin then
        return
    end
    systemIndex = systemIndex or 1

    -- Ensure the frame identifies its system for plugin lookup
    if not frame.system then
        frame.system = plugin.system or plugin.name
    end

    -- Ensure PositionManager has access to the plugin for saving
    -- (Fixes data loss for frames created manually without FrameFactory)
    frame.orbitPlugin = plugin
    frame.systemIndex = systemIndex

    -- Shared logic to refresh dialog (Trailing Debounce)
    local refreshTimer
    local function RefreshDialog()
        -- Reset timer on every call (Trailing Debounce)
        -- We only update the dialog when the user STOPS moving/nudging for 0.2s
        if refreshTimer then
            refreshTimer:Cancel()
        end

        refreshTimer = C_Timer.NewTimer(0.2, function()
            refreshTimer = nil
            -- Use Orbit's own settings dialog instead of Blizzard's
            if Orbit.SettingsDialog and Orbit.SettingsDialog:IsShown() then
                -- Only refresh if this frame is currently selected
                if Engine.FrameSelection:GetSelectedFrame() == frame then
                    local context = {
                        system = plugin.name or frame.orbitName,
                        systemIndex = systemIndex,
                        systemFrame = frame,
                    }
                    if plugin.system then
                        context.system = plugin.system
                    end
                    Orbit.SettingsDialog:UpdateDialog(context)
                end
            end
        end)
    end

    Engine.FrameSelection:Attach(frame, function(f, point, x, y)
        if Engine.PositionManager then
            if point == "ANCHORED" then
                -- Retrieve padding/align from anchor to pass to PositionManager
                local padding = 0
                local align = nil
                local fallback = nil
                if Engine.FrameAnchor then
                    if Engine.FrameAnchor.anchors[f] then
                        padding = Engine.FrameAnchor.anchors[f].padding or 0
                        align = Engine.FrameAnchor.anchors[f].align
                    end
                    local targetFrame = _G[x]
                    if targetFrame then
                        local rootParent = targetFrame
                        if Engine.FrameAnchor.GetRootParent then
                            rootParent = Engine.FrameAnchor:GetRootParent(targetFrame)
                        end
                        if rootParent and rootParent:GetName() then
                            fallback = rootParent:GetName()
                        end
                    end
                end
                Engine.PositionManager:SetAnchor(f, x, y, padding, align, fallback)
            else
                Engine.PositionManager:SetPosition(f, point, x, y)
            end
            Engine.PositionManager:MarkDirty(f)
            -- Immediate write for spec-scoped frames (FlushToStorage's SetSetting chain is unreliable)
            local p = f.orbitPlugin
            local sysIdx = f.systemIndex
            if p and sysIdx and p.SetSpecData and p.IsSpecScopedIndex and p:IsSpecScopedIndex(sysIdx) then
                if point == "ANCHORED" then
                    local anch = Engine.PositionManager:GetAnchor(f)
                    if anch then
                        p:SetSpecData(sysIdx, "Anchor", anch)
                        p:SetSpecData(sysIdx, "Position", nil)
                    end
                else
                    p:SetSpecData(sysIdx, "Position", { point = point, x = x, y = y })
                    p:SetSpecData(sysIdx, "Anchor", false)
                end
            end
        end

        -- Refresh settings (e.g. show/hide width/height sliders based on anchor)
        RefreshDialog()
    end, function(f, isGroupAdd)
        -- Selection callback: Open Orbit's settings dialog
        if Orbit.SettingsDialog then
            if isGroupAdd then
                Orbit.SettingsDialog:UpdateGroupDialog(plugin, Engine.FrameSelection:GetSelectedFrames())
                Orbit.SettingsDialog:Show()
                return
            end

            -- Deselect any native Blizzard frames first
            if EditModeManagerFrame then
                EditModeManagerFrame:ClearSelectedSystem()
            end

            local context = {
                system = plugin.name or frame.orbitName,
                systemIndex = systemIndex,
                systemFrame = f,
            }
            if plugin.system then
                context.system = plugin.system
            end

            -- Update and show Orbit's dialog
            Orbit.SettingsDialog:UpdateDialog(context)
            Orbit.SettingsDialog:Show()
            Orbit.SettingsDialog:PositionNearButton()
        end
    end)
end
