--[[
    Castbar.lua - TGXG Interrupt
    -----------------------------------------------------------------------
    Blizzard cast-bar manager. No custom bars: the ORIGINAL
    TargetFrameSpellBar / FocusFrameSpellBar stay in charge of all cast
    rendering — this module only makes them freely placeable and scalable.

    How repositioning sticks: Blizzard layout code re-SetPoints the spell
    bars constantly (e.g. when auras shift the target frame). We hook
    SetPoint (hooksecurefunc, guarded against recursion) and immediately
    re-apply the user's anchor, so the bar can never jump back.

    Moving UX: unlock ("Castbars sperren" off) → a green MOVER overlay
    appears at each bar's current position; drag it, the real Blizzard
    bar follows, position is saved. "Position zurücksetzen" restores the
    original Blizzard anchor (captured before the first custom apply).

    Scaling: plain SetScale on the Blizzard frame (re-applied OnShow).
    The cast bars are not protected frames, so none of this taints.
    -----------------------------------------------------------------------
]]

TGX = TGX or {}
TGX.Castbar = TGX.Castbar or {}
local CB = TGX.Castbar

local BAR_NAMES = { target = "TargetFrameSpellBar", focus = "FocusFrameSpellBar" }
local _movers   = {}     -- unit → mover frame
local _hooked   = {}     -- unit → true once SetPoint/OnShow hooks are set
local _applying = {}     -- unit → true while WE are positioning (recursion guard)
local _orig     = {}     -- unit → captured original anchor points

local function db() return TGX.db end

local function GetBar(unit) return _G[BAR_NAMES[unit]] end

local function unitCfg(unit)
    local d = db()
    if unit == "focus" then
        return d.castbarFocus, d.castbarFocusScale, d.castbarFocusHasPos,
               d.castbarFocusPosX, d.castbarFocusPosY
    end
    return true, d.castbarScale, d.castbarHasPos, d.castbarPosX, d.castbarPosY
end

local function setPos(unit, hasPos, x, y)
    local d = db()
    if unit == "focus" then
        d.castbarFocusHasPos = hasPos
        if x then d.castbarFocusPosX, d.castbarFocusPosY = x, y end
    else
        d.castbarHasPos = hasPos
        if x then d.castbarPosX, d.castbarPosY = x, y end
    end
end

------------------------------------------------------------
-- Original-anchor capture & restore
------------------------------------------------------------
local _origSize = {}   -- unit → { w, h } captured before any resize

local function CaptureOriginal(unit)
    local bar = GetBar(unit)
    if not bar then return end
    if not _orig[unit] then
        local points = {}
        local ok = pcall(function()
            for i = 1, bar:GetNumPoints() do
                points[i] = { bar:GetPoint(i) }
            end
        end)
        if ok and #points > 0 then _orig[unit] = points end
    end
    if not _origSize[unit] then
        local ok, w, h = pcall(bar.GetSize, bar)
        if ok and w and h and w > 0 and h > 0 then
            _origSize[unit] = { w = w, h = h }
        end
    end
end

local function RestoreOriginal(unit)
    local bar = GetBar(unit)
    if not bar then return end
    local points = _orig[unit]
    if points then
        _applying[unit] = true
        pcall(function()
            bar:ClearAllPoints()
            for _, p in ipairs(points) do
                bar:SetPoint(unpack(p))
            end
        end)
        _applying[unit] = false
    end
    local os = _origSize[unit]
    if os then pcall(bar.SetSize, bar, os.w, os.h) end
end

------------------------------------------------------------
-- Apply position / scale to the Blizzard bar
------------------------------------------------------------
local function ApplyPosition(unit)
    local enabledMain = db().castbarEnabled
    local manage, _, hasPos, x, y = unitCfg(unit)
    if not (enabledMain and manage and hasPos) then return end
    local bar = GetBar(unit)
    if not bar then return end
    CaptureOriginal(unit)
    _applying[unit] = true
    pcall(function()
        bar:ClearAllPoints()
        -- Offsets are stored in SCREEN coordinates; convert into the
        -- bar's own (scaled) coordinate space so scale changes don't
        -- shift the visual position.
        local s = bar:GetScale()
        if not s or s <= 0 then s = 1 end
        bar:SetPoint("CENTER", UIParent, "CENTER", x / s, y / s)
    end)
    _applying[unit] = false
end

local function ApplyScale(unit)
    local enabledMain = db().castbarEnabled
    local manage, scale = unitCfg(unit)
    local bar = GetBar(unit)
    if not bar then return end
    local target = (enabledMain and manage) and (scale or 100) / 100 or 1
    pcall(bar.SetScale, bar, target)
end

-- Width/height override: 0 = keep Blizzard's original size (captured
-- before the first resize). The bar's atlas art is edge-anchored, so it
-- stretches cleanly with the frame.
local function sizeCfg(unit)
    local d = db()
    if unit == "focus" then
        return d.castbarFocusBarWidth or 0, d.castbarFocusBarHeight or 0
    end
    return d.castbarBarWidth or 0, d.castbarBarHeight or 0
end

local function ApplySize(unit)
    local bar = GetBar(unit)
    if not bar then return end
    CaptureOriginal(unit)
    local os = _origSize[unit]
    if not os then return end
    local manage = (unit == "target") or db().castbarFocus
    local w, h = sizeCfg(unit)
    if not (db().castbarEnabled and manage) then w, h = 0, 0 end
    pcall(bar.SetSize, bar,
        (w and w > 0) and w or os.w,
        (h and h > 0) and h or os.h)
end

local function EnsureHooks(unit)
    if _hooked[unit] then return end
    local bar = GetBar(unit)
    if not bar then return end
    _hooked[unit] = true
    -- Blizzard re-anchors the spell bar all the time; force ours back.
    hooksecurefunc(bar, "SetPoint", function()
        if _applying[unit] then return end
        local _, _, hasPos = unitCfg(unit)
        if db().castbarEnabled and hasPos then
            ApplyPosition(unit)
        end
    end)
    bar:HookScript("OnShow", function()
        ApplyScale(unit)
        ApplySize(unit)
        ApplyPosition(unit)
    end)
end

------------------------------------------------------------
-- Movers (drag proxies shown while unlocked)
------------------------------------------------------------
local function MoverLabel(unit)
    return "TGXG " .. (unit == "focus" and (FOCUS or "Focus") or (TARGET or "Target"))
end

local function CreateMover(unit)
    local m = CreateFrame("Frame", "TGXGCastbarMover_" .. unit, UIParent)
    m.unit = unit
    m:SetFrameStrata("DIALOG")
    m:SetClampedToScreen(true)
    m:SetMovable(true)
    m:EnableMouse(true)
    m:Hide()

    m.bg = m:CreateTexture(nil, "BACKGROUND")
    m.bg:SetAllPoints()
    m.bg:SetColorTexture(0.2, 1.0, 0.6, 0.35)

    m.text = m:CreateFontString(nil, "OVERLAY")
    m.text:SetFont(STANDARD_TEXT_FONT, 11, "OUTLINE")
    m.text:SetPoint("CENTER")
    m.text:SetText(MoverLabel(unit))

    m:SetScript("OnMouseDown", function(f, button)
        if button == "LeftButton" then f:StartMoving() end
    end)
    m:SetScript("OnMouseUp", function(f)
        f:StopMovingOrSizing()
        local cx, cy = f:GetCenter()
        local ux, uy = UIParent:GetCenter()
        setPos(unit, true, cx - ux, cy - uy)
        ApplyPosition(unit)
        CB:UpdateMover(unit)
    end)
    return m
end

-- Place the mover on the bar's current on-screen rect.
function CB:UpdateMover(unit)
    local m = _movers[unit]
    if not m then return end
    local d = db()
    local manage = (unit == "target") or d.castbarFocus
    -- Visible while unlocked OR in test mode — the test preview must let
    -- the user place and scale the bars without hunting for the lock.
    local shouldShow = d.castbarEnabled and manage
        and ((not d.castbarLocked) or TGX.testMode)
    if not shouldShow then m:Hide() return end

    local bar = GetBar(unit)
    if not bar then m:Hide() return end

    -- Size: the bar's size in screen units (bar scale factored in).
    local s = bar:GetScale() or 1
    local w, h = bar:GetSize()
    m:SetSize(math.max(60, (w or 150) * s), math.max(14, (h or 12) * s) + 8)

    m:ClearAllPoints()
    local _, _, hasPos, x, y = unitCfg(unit)
    if hasPos then
        m:SetPoint("CENTER", UIParent, "CENTER", x, y)
    else
        -- No custom position yet: sit on the bar's current location.
        local bx, by = bar:GetCenter()
        if bx and by then
            local es  = bar:GetEffectiveScale()
            local ues = UIParent:GetEffectiveScale()
            m:SetPoint("CENTER", UIParent, "BOTTOMLEFT",
                bx * es / ues, by * es / ues)
        else
            m:SetPoint("CENTER", UIParent, "CENTER", 0, unit == "focus" and -240 or -180)
        end
    end
    m:Show()
end

------------------------------------------------------------
-- Public API (called from Core / Options)
------------------------------------------------------------
function CB:Create()
    if _movers.target then return end
    _movers.target = CreateMover("target")
    _movers.focus  = CreateMover("focus")
    -- Capture originals early, before any custom apply.
    CaptureOriginal("target")
    CaptureOriginal("focus")
    EnsureHooks("target")
    EnsureHooks("focus")
    self:ApplySettings()
end

function CB:ApplySettings()
    for unit in pairs(BAR_NAMES) do
        EnsureHooks(unit)
        ApplyScale(unit)
        ApplySize(unit)
        local _, _, hasPos = unitCfg(unit)
        local manage = (unit == "target") or db().castbarFocus
        if db().castbarEnabled and manage and hasPos then
            ApplyPosition(unit)
        else
            RestoreOriginal(unit)
            ApplySize(unit)   -- size override stays valid without custom pos
        end
        self:UpdateMover(unit)
    end
end

function CB:ApplyLock()
    self:UpdateMover("target")
    self:UpdateMover("focus")
end

-- Reset one bar back to Blizzard's own anchor AND original size.
function CB:ResetPosition(unit)
    setPos(unit, false)
    local d = db()
    if unit == "focus" then
        d.castbarFocusBarWidth, d.castbarFocusBarHeight = 0, 0
    else
        d.castbarBarWidth, d.castbarBarHeight = 0, 0
    end
    RestoreOriginal(unit)
    self:UpdateMover(unit)
end

-- Test mode: nothing to fake on the real Blizzard bars; just surface the
-- movers so the user sees where the bars live.
function CB:StartTest()
    self:UpdateMover("target")
    self:UpdateMover("focus")
end
function CB:StopTest()
    self:UpdateMover("target")
    self:UpdateMover("focus")
end
