local addonName, SinsToolkit = ...

local FrameMover = {}
SinsToolkit.FrameMover = FrameMover

local sessionPositions = {}
local sessionScales = {}
local registered = {}

local DEFAULT_FRAMES = {
    "CharacterFrame",
    "PlayerSpellsFrame",
    "SpellBookFrame",
    "ProfessionsFrame",
    "ProfessionsBookFrame",
    "TradeSkillFrame",
    "CraftFrame",
    "ProfessionsCustomerOrdersFrame",
    "EncounterJournal",
    "CollectionsJournal",
    "WorldMapFrame",
    "QuestLogPopupDetailFrame",
    "PVEFrame",
    "FriendsFrame",
    "MailFrame",
    "MerchantFrame",
    "GossipFrame",
    "QuestFrame",
    "BankFrame",
    "AuctionHouseFrame",
    "ItemTextFrame",
    "DressUpFrame",
    "InspectFrame",
    "TradeFrame",
    "GuildBankFrame",
    "SettingsPanel",
    "DelvesCompanionConfigurationFrame",
    "DelvesCompanionFrame",
    "DelvesDashboardFrame",
    "DelvesDifficultyPickerFrame",
    "DelvesScenarioOverviewFrame",
}

local DEFAULT_FRAME_LOOKUP = {}
for _, name in ipairs(DEFAULT_FRAMES) do
    DEFAULT_FRAME_LOOKUP[name] = true
end

local function Print(message)
    print("|cff9b59b6[Sin's Toolkit: QOL]|r " .. message)
end

local function IsSafe(frame)
    return frame
        and not (frame.IsForbidden and frame:IsForbidden())
        and not ((frame.IsProtected and frame:IsProtected()) and InCombatLockdown())
end

local function IsEnabled()
    return SinsToolkit.IsFeatureActive and SinsToolkit.IsFeatureActive("frameMoverEnabled")
end

local function IsFrameDisabled(name)
    return SinsToolkitDB
        and SinsToolkitDB.frameMoverDisabledFrames
        and SinsToolkitDB.frameMoverDisabledFrames[name] == true
end

local function ModifierSatisfied()
    if not SinsToolkitDB or not SinsToolkitDB.frameMoverRequireModifier then return true end
    return IsShiftKeyDown and IsShiftKeyDown()
end

local function SavePosition(frame, name)
    if not frame or not name or not SinsToolkitDB then return end
    local mode = SinsToolkitDB.frameMoverPositionMode or "session"
    if mode == "off" then return end

    local point, relativeTo, relativePoint, x, y = frame:GetPoint()
    local data = {
        point = point or "CENTER",
        relativePoint = relativePoint or point or "CENTER",
        x = x or 0,
        y = y or 0,
    }
    if mode == "permanent" then
        SinsToolkitDB.frameMoverPositions[name] = data
    else
        sessionPositions[name] = data
    end
end

local function RestorePosition(frame, name)
    if not frame or not name or not SinsToolkitDB then return end
    local mode = SinsToolkitDB.frameMoverPositionMode or "session"
    local data = mode == "permanent" and SinsToolkitDB.frameMoverPositions[name] or sessionPositions[name]
    if not data then return end
    if not IsSafe(frame) then return end
    frame:ClearAllPoints()
    frame:SetPoint(data.point or "CENTER", UIParent, data.relativePoint or data.point or "CENTER", data.x or 0, data.y or 0)
end

local function SaveScale(frame, name)
    if not frame or not name or not SinsToolkitDB then return end
    local mode = SinsToolkitDB.frameMoverScaleMode or "session"
    if mode == "off" then return end
    if mode == "permanent" then
        SinsToolkitDB.frameMoverScales[name] = frame:GetScale()
    else
        sessionScales[name] = frame:GetScale()
    end
end

local function RestoreScale(frame, name)
    if not frame or not name or not SinsToolkitDB then return end
    local mode = SinsToolkitDB.frameMoverScaleMode or "session"
    local scale = mode == "permanent" and SinsToolkitDB.frameMoverScales[name] or sessionScales[name]
    if not scale then return end
    if not IsSafe(frame) then return end
    frame:SetScale(math.max(0.5, math.min(2.0, tonumber(scale) or 1)))
end

local function ResetFrame(frame, name, resetScale)
    if not frame or not name or not SinsToolkitDB then return end
    SinsToolkitDB.frameMoverPositions[name] = nil
    SinsToolkitDB.frameMoverScales[name] = nil
    sessionPositions[name] = nil
    sessionScales[name] = nil
    if IsSafe(frame) then
        if resetScale then frame:SetScale(1) end
        frame:SetUserPlaced(false)
    end
end

local function StartMovingFrame(frame, name, button)
    if not IsEnabled() or IsFrameDisabled(name) or not IsSafe(frame) then return end
    if button == "LeftButton" and ModifierSatisfied() and not (IsControlKeyDown and IsControlKeyDown()) then
        frame.__SinsToolkitMoving = true
        frame:StartMoving()
    elseif button == "RightButton"
        and SinsToolkitDB
        and SinsToolkitDB.frameMoverRightClickReset
        and IsShiftKeyDown and IsShiftKeyDown()
    then
        ResetFrame(frame, name, IsControlKeyDown and IsControlKeyDown())
        Print(name .. " position reset.")
    end
end

local function StopMovingFrame(frame, name)
    if frame.__SinsToolkitMoving then
        frame.__SinsToolkitMoving = nil
        frame:StopMovingOrSizing()
        frame:SetUserPlaced(false)
        SavePosition(frame, name)
    end
end

local function EnsureDragStrip(frame, name)
    if frame.__SinsToolkitFrameMoverDragStrip then return end
    if not frame.CreateTexture then return end

    local dragStrip = CreateFrame("Frame", nil, frame)
    dragStrip:SetPoint("TOPLEFT", frame, "TOPLEFT", 8, -6)
    dragStrip:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -44, -6)
    dragStrip:SetHeight(28)
    dragStrip:EnableMouse(true)
    dragStrip:SetFrameLevel(math.min((frame:GetFrameLevel() or 0) + 20, 10000))
    dragStrip:SetScript("OnMouseDown", function(_, button)
        StartMovingFrame(frame, name, button)
    end)
    dragStrip:SetScript("OnMouseUp", function()
        StopMovingFrame(frame, name)
    end)
    dragStrip:SetScript("OnLeave", function()
        StopMovingFrame(frame, name)
    end)

    frame.__SinsToolkitFrameMoverDragStrip = dragStrip
end

function FrameMover:RegisterFrame(name)
    if registered[name] then return end
    registered[name] = true
end

function FrameMover:GetFrameNames()
    local names = {}
    for name in pairs(registered) do
        names[#names + 1] = name
    end
    table.sort(names)
    return names
end

function FrameMover:ApplyFrame(name)
    local frame = _G[name]
    if not IsSafe(frame) then return end
    if IsFrameDisabled(name) then return end

    RestorePosition(frame, name)
    RestoreScale(frame, name)

    if frame.__SinsToolkitFrameMover then return end
    frame.__SinsToolkitFrameMover = true

    frame:SetMovable(true)
    frame:EnableMouse(true)
    if frame.SetResizable then frame:SetResizable(true) end
    if frame.EnableMouseWheel then frame:EnableMouseWheel(true) end
    EnsureDragStrip(frame, name)

    frame:HookScript("OnMouseDown", function(self, button)
        StartMovingFrame(self, name, button)
    end)

    frame:HookScript("OnMouseUp", function(self)
        StopMovingFrame(self, name)
    end)

    frame:HookScript("OnMouseWheel", function(self, delta)
        if not IsEnabled() or IsFrameDisabled(name) or not IsSafe(self) then return end
        if not (SinsToolkitDB and SinsToolkitDB.frameMoverCtrlScrollScale) then return end
        if not (IsControlKeyDown and IsControlKeyDown()) then return end
        local current = self:GetScale() or 1
        local nextScale = math.max(0.5, math.min(2.0, current + (delta > 0 and 0.05 or -0.05)))
        self:SetScale(nextScale)
        SaveScale(self, name)
    end)
end

function FrameMover:Apply()
    if not SinsToolkit.EnsureDB then return end
    SinsToolkit.EnsureDB()
    if not IsEnabled() then return end

    for _, name in ipairs(DEFAULT_FRAMES) do
        self:RegisterFrame(name)
    end
    for name in pairs(registered) do
        self:ApplyFrame(name)
    end
end

function FrameMover:ApplyVisibleFrame(frame)
    if not frame or not frame.GetName then return end
    local name = frame:GetName()
    if not name or not DEFAULT_FRAME_LOOKUP[name] then return end
    self:RegisterFrame(name)
    self:ApplyFrame(name)
end

function FrameMover:ResetAll()
    if not SinsToolkitDB then return end
    wipe(SinsToolkitDB.frameMoverPositions)
    wipe(SinsToolkitDB.frameMoverScales)
    wipe(sessionPositions)
    wipe(sessionScales)
    Print("Frame Mover saved positions and scales cleared. Reload UI if a frame is already open.")
end

function FrameMover:GetStatusText()
    local conflict = SinsToolkit.GetFeatureConflictText and SinsToolkit.GetFeatureConflictText("frameMoverEnabled") or ""
    return ("Frame Mover: %s\nPosition saving: %s\nScale saving: %s\nMove modifier: %s\n%s"):format(
        IsEnabled() and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r",
        SinsToolkitDB and SinsToolkitDB.frameMoverPositionMode or "session",
        SinsToolkitDB and SinsToolkitDB.frameMoverScaleMode or "session",
        SinsToolkitDB and SinsToolkitDB.frameMoverRequireModifier and "Shift required" or "None",
        conflict
    )
end

function SinsToolkit.ApplyFrameMover()
    if FrameMover.Apply then FrameMover:Apply() end
end

local eventFrame = CreateFrame("Frame")
local applyQueued = false

local function QueueApply()
    if applyQueued then return end
    applyQueued = true
    C_Timer.After(0, function()
        applyQueued = false
        FrameMover:Apply()
    end)
end

eventFrame:RegisterEvent("PLAYER_LOGIN")
eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
eventFrame:RegisterEvent("PLAYER_INTERACTION_MANAGER_FRAME_SHOW")
eventFrame:RegisterEvent("TRADE_SKILL_SHOW")
eventFrame:RegisterEvent("CRAFT_SHOW")
eventFrame:SetScript("OnEvent", function(_, event)
    QueueApply()
end)

hooksecurefunc("ShowUIPanel", function(frame)
    FrameMover:ApplyVisibleFrame(frame)
    QueueApply()
end)
