-- [ ORBIT POSITION UTILITIES ]-----------------------------------------------------------------------

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

local PositionUtils = {}
Engine.PositionUtils = PositionUtils

-- [ CONSTANTS ] -------------------------------------------------------------------------------------
local CENTER_THRESHOLD = 5

-- [ ANCHOR CALCULATION ] ----------------------------------------------------------------------------
function PositionUtils.CalculateAnchor(posX, posY, halfW, halfH)
    local anchorX, offsetX, justifyH
    local anchorY, offsetY
    local isOutsideRight = posX > halfW
    local isOutsideLeft = posX < -halfW

    -- X axis: snap to CENTER if within threshold, otherwise anchor to nearest edge
    if math.abs(posX) <= CENTER_THRESHOLD then
        anchorX = "CENTER"
        offsetX = 0
        justifyH = "CENTER"
    elseif posX > 0 then
        anchorX = "RIGHT"
        offsetX = halfW - posX -- distance from right edge (negative if outside)
        -- Inside: text grows LEFT (toward center), Outside: text grows RIGHT (away)
        justifyH = isOutsideRight and "LEFT" or "RIGHT"
    else
        anchorX = "LEFT"
        offsetX = halfW + posX -- distance from left edge (negative if outside)
        -- Inside: text grows RIGHT (toward center), Outside: text grows LEFT (away)
        justifyH = isOutsideLeft and "RIGHT" or "LEFT"
    end

    -- Y axis: snap to CENTER if within threshold, otherwise anchor to nearest edge
    if math.abs(posY) <= CENTER_THRESHOLD then
        anchorY = "CENTER"
        offsetY = 0
    elseif posY > 0 then
        anchorY = "TOP"
        offsetY = halfH - posY -- distance from top edge
    else
        anchorY = "BOTTOM"
        offsetY = halfH + posY -- distance from bottom edge
    end

    return anchorX, anchorY, offsetX, offsetY, justifyH
end

-- inverse of CalculateAnchor.
function PositionUtils.AnchorToCenter(anchorX, anchorY, offsetX, offsetY, halfW, halfH)
    local centerX, centerY = 0, 0
    if anchorX == "LEFT" then centerX = (offsetX or 0) - halfW
    elseif anchorX == "RIGHT" then centerX = halfW - (offsetX or 0) end
    if anchorY == "BOTTOM" then centerY = (offsetY or 0) - halfH
    elseif anchorY == "TOP" then centerY = halfH - (offsetY or 0) end
    return centerX, centerY
end

-- [ ANCHOR POINT BUILDER ] --------------------------------------------------------------------------
function PositionUtils.BuildAnchorPoint(anchorX, anchorY)
    if anchorY == "CENTER" and anchorX == "CENTER" then
        return "CENTER"
    elseif anchorY == "CENTER" then
        return anchorX
    elseif anchorX == "CENTER" then
        return anchorY
    else
        return anchorY .. anchorX
    end
end

-- Text → justifyH only; aura container → justifyH+anchorY; other → CENTER.
function PositionUtils.BuildComponentSelfAnchor(isFontString, isAuraContainer, anchorY, justifyH)
    if not justifyH or justifyH == "CENTER" then
        return "CENTER"
    end
    if isFontString then
        return justifyH
    end
    if isAuraContainer then
        return PositionUtils.BuildAnchorPoint(justifyH, anchorY)
    end
    return "CENTER"
end

-- Whether a component needs edge-relative offset compensation (width and optionally height)
function PositionUtils.NeedsEdgeCompensation(isFontString, isAuraContainer) return isFontString or isAuraContainer end

-- [ EDGE COMPENSATION ]------------------------------------------------------------------------------
function PositionUtils.CalculateAnchorWithWidthCompensation(posX, posY, halfW, halfH, needsWidthComp, compWidth, compHeight, isAuraContainer)
    local anchorX, anchorY, offsetX, offsetY, justifyH = PositionUtils.CalculateAnchor(posX, posY, halfW, halfH)
    local selfAnchorY = anchorY

    if not needsWidthComp then
        return anchorX, anchorY, offsetX, offsetY, justifyH, selfAnchorY
    end

    local compHalfW = (compWidth or 0) / 2
    local isOutsideLeft = posX < -halfW
    local isOutsideRight = posX > halfW

    if anchorX == "LEFT" then
        justifyH = isOutsideLeft and "RIGHT" or "LEFT"
        local widthComp = isOutsideLeft and compHalfW or -compHalfW
        offsetX = posX + halfW + widthComp
    elseif anchorX == "RIGHT" then
        justifyH = isOutsideRight and "LEFT" or "RIGHT"
        local widthComp = isOutsideRight and compHalfW or -compHalfW
        offsetX = halfW - posX + widthComp
    end

    if isAuraContainer and anchorY ~= "CENTER" then
        local isOutsideTop = posY > halfH
        local isOutsideBottom = posY < -halfH
        if anchorY == "TOP" then selfAnchorY = isOutsideTop and "BOTTOM" or "TOP"
        elseif anchorY == "BOTTOM" then selfAnchorY = isOutsideBottom and "TOP" or "BOTTOM" end
        if justifyH ~= "CENTER" then
            local compHalfH = (compHeight or 0) / 2
            if anchorY == "TOP" then
                offsetY = halfH - posY + (isOutsideTop and compHalfH or -compHalfH)
            elseif anchorY == "BOTTOM" then
                offsetY = posY + halfH + (isOutsideBottom and compHalfH or -compHalfH)
            end
        end
    end

    return anchorX, anchorY, offsetX, offsetY, justifyH, selfAnchorY
end

-- [ APPLY TEXT POSITION ] ---------------------------------------------------------------------------
-- Canvas layer, sparse: only components the user reordered carry a layer; pristine components keep their plugin-authored stacking. Frame components offset from the parent's Overlay slot but NEVER sink below the border/highlight band — layers reorder components against each other, not against the frame's furniture (bars 1, border 5, highlight 6); the single level between highlight and Overlay means all negative notches share the one "behind siblings" slot. Regions map onto draw sublevels (-8..7) within their host, which cannot cross frames by construction.
function PositionUtils.ApplyComponentLayer(element, parent, layer)
    if not layer then return end
    if element.SetFrameLevel then
        local Levels = Orbit.Constants.Levels
        local base = (parent and parent.GetFrameLevel and parent:GetFrameLevel() or 0)
        element:SetFrameLevel(base + math.max(Levels.Border + 1, Levels.Overlay + layer))
    elseif element.SetDrawLayer and element.GetDrawLayer then
        local drawLayer = element:GetDrawLayer()
        element:SetDrawLayer(drawLayer or "OVERLAY", math.max(-8, math.min(7, layer)))
    end
end

function PositionUtils.ApplyTextPosition(element, parent, pos, defaultAnchor, defaultOffsetX, defaultOffsetY, isAuraContainer)
    if not element or not parent then
        return false
    end

    pos = pos or {}

    PositionUtils.ApplyComponentLayer(element, parent, pos.layer)

    -- Apply justifyH if element supports it
    if pos.justifyH and element.SetJustifyH then
        element:SetJustifyH(pos.justifyH)
    end

    element:ClearAllPoints()

    -- Anchor-based positioning (preferred)
    if pos.anchorX then
        local anchorPoint = PositionUtils.BuildAnchorPoint(pos.anchorX, pos.anchorY or "CENTER")

        -- Calculate offset signs (positive = inward)
        local offsetX = pos.offsetX or 0
        local offsetY = pos.offsetY or 0
        -- Scale the inset by current/authored icon size so text keeps its relative spot when the icon is resized.
        if pos.baseSize and pos.baseSize > 0 and parent.GetWidth then
            local cur = parent:GetWidth()
            if cur and cur > 0 then
                local ratio = cur / pos.baseSize
                offsetX = offsetX * ratio
                offsetY = offsetY * ratio
            end
        end
        if pos.anchorX == "RIGHT" then
            offsetX = -offsetX
        end
        if pos.anchorY == "TOP" then
            offsetY = -offsetY
        end

        local selfAnchor
        if isAuraContainer then
            selfAnchor = PositionUtils.BuildComponentSelfAnchor(false, true, pos.selfAnchorY or pos.anchorY, pos.justifyH)
        elseif pos.justifyH and pos.justifyH ~= "CENTER" and element.SetJustifyH then
            selfAnchor = pos.justifyH
        else
            selfAnchor = "CENTER"
        end
        local scale = parent:GetEffectiveScale()
        local ew, eh = element.GetWidth and element:GetWidth() or 0, element.GetHeight and element:GetHeight() or 0
        offsetX, offsetY = Engine.Pixel:SnapPosition(offsetX, offsetY, selfAnchor, ew, eh, scale)
        element:SetPoint(selfAnchor, parent, anchorPoint, offsetX, offsetY)
        return true
    end

    -- Center-relative fallback (posX/posY)
    if pos.posX ~= nil and pos.posY ~= nil then
        local scale = parent:GetEffectiveScale()
        element:SetPoint("CENTER", parent, "CENTER", Orbit.Engine.Pixel:Snap(pos.posX, scale), Orbit.Engine.Pixel:Snap(pos.posY, scale))
        return true
    end

    if defaultAnchor then
        local scale = parent:GetEffectiveScale()
        local ew, eh = element.GetWidth and element:GetWidth() or 0, element.GetHeight and element:GetHeight() or 0
        local dx, dy = Engine.Pixel:SnapPosition(defaultOffsetX or 0, defaultOffsetY or 0, defaultAnchor, ew, eh, scale)
        element:SetPoint(defaultAnchor, parent, defaultAnchor, dx, dy)
        return true
    end

    return false
end

-- [ ANCHOR TO POSITION ]-----------------------------------------------------------------------------
function PositionUtils.AnchorToPosition(posX, posY, halfW, halfH, defaultPosition)
    if posX and posY and halfW and halfH then
        local beyondX = math.max(0, math.abs(posX) - halfW)
        local beyondY = math.max(0, math.abs(posY) - halfH)
        if beyondY > beyondX then return posY > 0 and "Above" or "Below"
        elseif beyondX > beyondY then return posX > 0 and "Right" or "Left" end
        if math.abs(posX) / math.max(halfW, 1) > math.abs(posY) / math.max(halfH, 1) then return posX > 0 and "Right" or "Left"
        else return posY > 0 and "Above" or "Below" end
    end
    return defaultPosition or "Right"
end

-- [ FINAL OFFSET BUILDER ] --------------------------------------------------------------------------
function PositionUtils.AnchorOffsetsToFinal(anchorX, anchorY, offsetX, offsetY, posX, posY)
    local fx
    if anchorX == "CENTER" then
        fx = posX or 0
    else
        fx = offsetX or 0
        if anchorX == "RIGHT" then fx = -fx end
    end
    local fy
    if anchorY == "CENTER" then
        fy = posY or 0
    else
        fy = offsetY or 0
        if anchorY == "TOP" then fy = -fy end
    end
    return fx, fy
end

-- [ APPLY ICON POSITION ]----------------------------------------------------------------------------
function PositionUtils.ApplyIconPosition(icon, parentFrame, pos)
    if not pos or not pos.anchorX then return end
    local anchorPoint = PositionUtils.BuildAnchorPoint(pos.anchorX, pos.anchorY or "CENTER")
    local finalX = pos.offsetX or 0
    local finalY = pos.offsetY or 0
    if pos.anchorX == "RIGHT" then finalX = -finalX end
    if pos.anchorY == "TOP" then finalY = -finalY end
    local scale = parentFrame:GetEffectiveScale()
    local iw, ih = icon.GetWidth and icon:GetWidth() or 0, icon.GetHeight and icon:GetHeight() or 0
    finalX, finalY = Engine.Pixel:SnapPosition(finalX, finalY, "CENTER", iw, ih, scale)
    icon:ClearAllPoints()
    icon:SetPoint("CENTER", parentFrame, anchorPoint, finalX, finalY)
end
