local _, namespace = ...

local WTUI = namespace.WildThingsUI

--[[
CREATE-ONCE CONTRACT (ROADMAP SC5):
WTUI:MakeDraggable and WTUI:RegisterDropTarget must each run exactly once per frame,
at that frame's construction time (inside its Create*/Make* function). NEVER call
either from a per-refresh/per-render/rebind path (Bind/SetLayout/repaint-style
methods) — doing so would re-wire duplicate OnDragStart/OnDragStop/OnHide scripts
and re-register the same frame as a drop target multiple times.
]]

-- Guard helper: ensure a frame has a given script registered before HookScript-ing it
-- (mirrors UI/Motion.lua's EnsureScript / WTUI:AddTooltip in UI/Functions.lua — some
-- frame types don't have every script slot available until something has been
-- assigned to it at least once).
local function EnsureScript(frame, scriptName)
    if not frame:HasScript(scriptName) then
        frame:SetScript(scriptName, function() end)
    end
end

-- Module-scoped drag state: nil when idle, or { source = frame, payload = any }
-- while a drag gesture is in flight.
local activeDrag = nil

-- Ordered array of registered drop-target frames, in registration order.
local dropTargets = {}

-- WTUI:GetActiveDrag()
-- Read accessor for the module-scoped drag state — the single query surface other
-- widgets (e.g. drag-hover affordances) use to know a drag is in flight.
function WTUI:GetActiveDrag()
    return activeDrag
end

-- WTUI:MakeDraggable(frame, getPayload, opts)
-- Wires the native, event-driven drag gesture pair onto frame: EnableMouse(true) +
-- RegisterForDrag("LeftButton") + OnDragStart/OnDragStop. No per-frame OnUpdate, no
-- cursor-follow ghost frame — the engine pairs the gesture natively.
--
-- getPayload(frame) -> any | nil. Returning nil (e.g. an empty slot) leaves
-- activeDrag unset, making OnDragStart a silent no-op — empty frames are never drag
-- sources.
function WTUI:MakeDraggable(frame, getPayload, opts)
    opts = opts or {}
    local dimAlpha = opts.dimAlpha or 0.35

    frame:EnableMouse(true)
    frame:RegisterForDrag("LeftButton")

    frame:SetScript("OnDragStart", function()
        local payload = getPayload(frame)
        if payload == nil then return end
        activeDrag = { source = frame, payload = payload }
        frame:SetAlpha(dimAlpha)
    end)

    frame:SetScript("OnDragStop", function()
        if not activeDrag or activeDrag.source ~= frame then return end
        frame:SetAlpha(1) -- restore origin before any hit-test runs
        local hit = WTUI:ResolveDropTarget(frame)
        if hit and hit ~= frame and hit.wtOnDrop then
            hit.wtOnDrop(activeDrag.payload, hit)
        end
        activeDrag = nil
    end)

    -- Source-hidden backstop: recover a stuck-dim origin + stale activeDrag if the
    -- frame hides mid-gesture (window close, encounter switch, view toggle away).
    EnsureScript(frame, "OnHide")
    frame:HookScript("OnHide", function()
        if activeDrag and activeDrag.source == frame then
            frame:SetAlpha(1)
            activeDrag = nil
        end
    end)
end

-- WTUI:RegisterDropTarget(frame, onDrop, opts)
-- Registers frame as a drop target: stores frame.wtOnDrop = onDrop and appends frame
-- to the ordered dropTargets array (opts reserved for future use).
function WTUI:RegisterDropTarget(frame, onDrop, opts) -- luacheck: no unused args
    frame.wtOnDrop = onDrop
    dropTargets[#dropTargets + 1] = frame
end

-- NormalizeMouseFoci()
-- GetMouseFoci()'s exact return shape (singular region vs. frontmost-to-backmost
-- table) is unverified in this client (research A1) — normalize to an array so the
-- optional fast path below is shape-independent regardless of which shape it is.
local function NormalizeMouseFoci()
    local foci = GetMouseFoci and GetMouseFoci() or nil
    if type(foci) == "table" then return foci end
    if foci then return { foci } end
    return {}
end

-- WTUI:ResolveDropTarget(sourceFrame)
-- Resolves the drop target for the gesture that just ended on sourceFrame. Optional
-- GetMouseFoci fast path first, then the primary, self-sufficient ordered
-- IsVisible()/IsMouseOver() loop. Excludes sourceFrame. Returns nil on no hit.
function WTUI:ResolveDropTarget(sourceFrame)
    local foci = NormalizeMouseFoci()
    for _, region in ipairs(foci) do
        for _, target in ipairs(dropTargets) do
            if region == target and target ~= sourceFrame then
                return target
            end
        end
    end

    for _, target in ipairs(dropTargets) do
        if target ~= sourceFrame and target:IsVisible() and target:IsMouseOver() then
            return target
        end
    end

    return nil
end

-- WTUI:CancelActiveDrag()
-- Force-restores the origin alpha and clears activeDrag. A screen may call this from
-- its own OnHide to force-cancel an in-flight drag on window close.
function WTUI:CancelActiveDrag()
    if activeDrag then
        activeDrag.source:SetAlpha(1)
        activeDrag = nil
    end
end
