if not WeakAuras.IsLibsOK() then return end
---@type string
local AddonName = ...
---@class OptionsPrivate
local OptionsPrivate = select(2, ...)

local createCenterLines = true --- Creates only the middle lines
local showNormalLines = false -- Show all alignment lines all the time
local highlightColor = { 1, 1, 0 } -- The color of lines that are we are currently aligned too
local gridHighlightColor = { 0.3, 1, 0.3}
local normalColor = { 0.3, 0.3, 0.6, } -- The color of lines if they aren't matched if showNormalLines is enabled
local gridColor = { 0.3, 0.6, 0.3 } -- The color of grid lines, if they aren't matched and enabled

-- Lua APIs
local pairs = pairs

-- WoW APIs
local IsShiftKeyDown, CreateFrame =  IsShiftKeyDown, CreateFrame

---@class WeakAuras
local WeakAuras = WeakAuras
local L = WeakAuras.L

local moversizer
local mover

local MAGNETIC_ALIGNMENT = 10
local function distance(num1, num2)
  return abs(num2 - num1)
end

local function EnsureTexture(self, texture)
  if texture then
    return texture
  else
    local ret = self:CreateTexture()
    ret:SetTexture("Interface\\GLUES\\CharacterSelect\\Glues-AddOn-Icons.blp")
    ret:SetWidth(16)
    ret:SetHeight(16)
    ret:SetTexCoord(0, 0.25, 0, 1)
    ret:SetVertexColor(1, 1, 1, 0.25)
    return ret
  end
end

local function moveOnePxl(direction)
  if mover and mover.moving then
    local data = mover.moving.data
    local physicalWidth, physicalHeight = GetPhysicalScreenSize();
    local oneX = GetScreenWidth() / physicalWidth
    local oneY = GetScreenHeight() / physicalHeight
    if data then
      if direction == "top" then
        data.yOffset = data.yOffset + oneY
      elseif direction == "bottom" then
        data.yOffset = data.yOffset - oneY
      elseif direction == "left" then
        data.xOffset = data.xOffset - oneX
      elseif direction == "right" then
        data.xOffset = data.xOffset + oneX
      end
      WeakAuras.Add(data, true)
      WeakAuras.UpdateThumbnail(data)
      OptionsPrivate.ResetMoverSizer()
      OptionsPrivate.Private.AddParents(data)
      WeakAuras.FillOptions()
    end
  end
end

local function ConstructMover(frame)
  local topAndBottom = CreateFrame("Frame", nil, frame)
  topAndBottom:SetClampedToScreen(true)
  topAndBottom:SetSize(25, 45)
  topAndBottom:SetPoint("LEFT", frame, "RIGHT", 1, 0)
  local top = CreateFrame("Button", nil, topAndBottom)
  top:SetSize(25, 25)
  top:SetPoint("TOP", topAndBottom)
  top:SetFrameStrata("BACKGROUND")
  local bottom = CreateFrame("Button", nil, topAndBottom)
  bottom:SetSize(25, 25)
  bottom:SetPoint("BOTTOM", topAndBottom)
  bottom:SetFrameStrata("BACKGROUND")

  local leftAndRight = CreateFrame("Frame", nil, frame)
  leftAndRight:SetClampedToScreen(true)
  leftAndRight:SetSize(45, 25)
  leftAndRight:SetPoint("TOP", frame, "BOTTOM", 0, 1)
  local left = CreateFrame("Button", nil, leftAndRight)
  left:SetSize(25, 25)
  left:SetPoint("LEFT", leftAndRight)
  left:SetFrameStrata("BACKGROUND")
  local right = CreateFrame("Button", nil, leftAndRight)
  right:SetSize(25, 25)
  right:SetPoint("RIGHT", leftAndRight)
  right:SetFrameStrata("BACKGROUND")

  top:SetNormalTexture("interface\\buttons\\ui-scrollbar-scrollupbutton-up.blp")
  top:SetHighlightTexture("interface\\buttons\\ui-scrollbar-scrollupbutton-highlight.blp")
  top:SetPushedTexture("interface\\buttons\\ui-scrollbar-scrollupbutton-down.blp")
  top:SetScript("OnClick", function() moveOnePxl("top") end)

  bottom:SetNormalTexture("interface\\buttons\\ui-scrollbar-scrollupbutton-up.blp")
  bottom:GetNormalTexture():SetTexCoord(0, 1, 1, 0)
  bottom:SetHighlightTexture("interface\\buttons\\ui-scrollbar-scrollupbutton-highlight.blp")
  bottom:GetHighlightTexture():SetTexCoord(0, 1, 1, 0)
  bottom:SetPushedTexture("interface\\buttons\\ui-scrollbar-scrollupbutton-down.blp")
  bottom:GetPushedTexture():SetTexCoord(0, 1, 1, 0)
  bottom:SetScript("OnClick", function() moveOnePxl("bottom") end)

  left:SetNormalTexture("interface\\buttons\\ui-scrollbar-scrollupbutton-up.blp")
  left:GetNormalTexture():SetRotation(math.pi/2)
  left:SetHighlightTexture("interface\\buttons\\ui-scrollbar-scrollupbutton-highlight.blp")
  left:GetHighlightTexture():SetRotation(math.pi/2)
  left:SetPushedTexture("interface\\buttons\\ui-scrollbar-scrollupbutton-down.blp")
  left:GetPushedTexture():SetRotation(math.pi/2)
  left:SetScript("OnClick", function() moveOnePxl("left") end)

  right:SetNormalTexture("interface\\buttons\\ui-scrollbar-scrollupbutton-up.blp")
  right:GetNormalTexture():SetRotation(-math.pi/2)
  right:SetHighlightTexture("interface\\buttons\\ui-scrollbar-scrollupbutton-highlight.blp")
  right:GetHighlightTexture():SetRotation(-math.pi/2)
  right:SetPushedTexture("interface\\buttons\\ui-scrollbar-scrollupbutton-down.blp")
  right:GetPushedTexture():SetRotation(-math.pi/2)
  right:SetScript("OnClick", function() moveOnePxl("right") end)

  local arrow = CreateFrame("Frame", nil, frame)
  arrow:SetClampedToScreen(true)
  arrow:SetSize(196, 196)
  arrow:SetPoint("CENTER", frame, "CENTER")
  arrow:SetFrameStrata("HIGH")
  local arrowTexture = arrow:CreateTexture()
  arrowTexture:SetTexture("Interface\\Addons\\WeakAuras\\Media\\Textures\\offscreen.tga")
  arrowTexture:SetSize(128, 128)
  arrowTexture:SetPoint("CENTER", arrow, "CENTER")
  arrowTexture:SetVertexColor(0.8, 0.8, 0.2)
  arrowTexture:Hide()
  local offscreenText = arrow:CreateFontString(nil, "OVERLAY")
  offscreenText:SetFont(STANDARD_TEXT_FONT, 14, "THICKOUTLINE");
  offscreenText:SetText(L["Aura is\nOff Screen"])
  offscreenText:Hide()
  offscreenText:SetPoint("CENTER", arrow, "CENTER")

  return arrowTexture, offscreenText
end

local function ConstructSizer(frame)
  -- topright, bottomright, bottomleft, topleft

  local topright = CreateFrame("Frame", nil, frame)
  topright:EnableMouse()
  topright:SetWidth(16)
  topright:SetHeight(16)
  topright:SetPoint("TOPRIGHT", frame, "TOPRIGHT")

  local texTR1 = topright:CreateTexture(nil, "OVERLAY")
  texTR1:SetTexture("Interface\\BUTTONS\\UI-Listbox-Highlight.blp")
  texTR1:SetBlendMode("ADD")
  texTR1:SetTexCoord(0.5, 0, 0, 0, 0.5, 1, 0, 1)
  texTR1:SetPoint("TOPRIGHT", topright, "TOPRIGHT", -3, -3)
  texTR1:SetPoint("BOTTOMLEFT", topright, "BOTTOM")

  local texTR2 = topright:CreateTexture(nil, "OVERLAY")
  texTR2:SetTexture("Interface\\BUTTONS\\UI-Listbox-Highlight.blp")
  texTR2:SetBlendMode("ADD")
  texTR2:SetTexCoord(0, 0, 0, 1, 0.5, 0, 0.5, 1)
  texTR2:SetPoint("TOPRIGHT", texTR1, "TOPLEFT")
  texTR2:SetPoint("BOTTOMLEFT", topright, "LEFT")

  topright.Highlight = function()
    if WeakAurasOptionsSaved.lockPositions then
      return
    end
    texTR1:Show()
    texTR2:Show()
  end
  topright.Clear = function()
    texTR1:Hide()
    texTR2:Hide()
  end

  local bottomright = CreateFrame("Frame", nil, frame)
  bottomright:EnableMouse()
  bottomright:SetWidth(16)
  bottomright:SetHeight(16)
  bottomright:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT")

  local texBR1 = bottomright:CreateTexture(nil, "OVERLAY")
  texBR1:SetTexture("Interface\\BUTTONS\\UI-Listbox-Highlight.blp")
  texBR1:SetBlendMode("ADD")
  texBR1:SetTexCoord(1, 0, 0.5, 0, 1, 1, 0.5, 1)
  texBR1:SetPoint("BOTTOMRIGHT", bottomright, "BOTTOMRIGHT", -3, 3)
  texBR1:SetPoint("TOPLEFT", bottomright, "TOP")

  local texBR2 = bottomright:CreateTexture(nil, "OVERLAY")
  texBR2:SetTexture("Interface\\BUTTONS\\UI-Listbox-Highlight.blp")
  texBR2:SetBlendMode("ADD")
  texBR2:SetTexCoord(0, 0, 0, 1, 0.5, 0, 0.5, 1)
  texBR2:SetPoint("BOTTOMRIGHT", texBR1, "BOTTOMLEFT")
  texBR2:SetPoint("TOPLEFT", bottomright, "LEFT")

  bottomright.Highlight = function()
    if WeakAurasOptionsSaved.lockPositions then
      return
    end
    texBR1:Show()
    texBR2:Show()
  end
  bottomright.Clear = function()
    texBR1:Hide()
    texBR2:Hide()
  end

  local bottomleft = CreateFrame("Frame", nil, frame)
  bottomleft:EnableMouse()
  bottomleft:SetSize(16, 16)
  bottomleft:SetHeight(16)
  bottomleft:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT")

  local texBL1 = bottomleft:CreateTexture(nil, "OVERLAY")
  texBL1:SetTexture("Interface\\BUTTONS\\UI-Listbox-Highlight.blp")
  texBL1:SetBlendMode("ADD")
  texBL1:SetTexCoord(1, 0, 0.5, 0, 1, 1, 0.5, 1)
  texBL1:SetPoint("BOTTOMLEFT", bottomleft, "BOTTOMLEFT", 3, 3)
  texBL1:SetPoint("TOPRIGHT", bottomleft, "TOP")

  local texBL2 = bottomleft:CreateTexture(nil, "OVERLAY")
  texBL2:SetTexture("Interface\\BUTTONS\\UI-Listbox-Highlight.blp")
  texBL2:SetBlendMode("ADD")
  texBL2:SetTexCoord(0.5, 0, 0.5, 1, 1, 0, 1, 1)
  texBL2:SetPoint("BOTTOMLEFT", texBL1, "BOTTOMRIGHT")
  texBL2:SetPoint("TOPRIGHT", bottomleft, "RIGHT")

  bottomleft.Highlight = function()
    if WeakAurasOptionsSaved.lockPositions then
      return
    end
    texBL1:Show()
    texBL2:Show()
  end
  bottomleft.Clear = function()
    texBL1:Hide()
    texBL2:Hide()
  end

  local topleft = CreateFrame("Frame", nil, frame)
  topleft:EnableMouse()
  topleft:SetWidth(16)
  topleft:SetHeight(16)
  topleft:SetPoint("TOPLEFT", frame, "TOPLEFT")

  local texTL1 = topleft:CreateTexture(nil, "OVERLAY")
  texTL1:SetTexture("Interface\\BUTTONS\\UI-Listbox-Highlight.blp")
  texTL1:SetBlendMode("ADD")
  texTL1:SetTexCoord(0.5, 0, 0, 0, 0.5, 1, 0, 1)
  texTL1:SetPoint("TOPLEFT", topleft, "TOPLEFT", 3, -3)
  texTL1:SetPoint("BOTTOMRIGHT", topleft, "BOTTOM")

  local texTL2 = topleft:CreateTexture(nil, "OVERLAY")
  texTL2:SetTexture("Interface\\BUTTONS\\UI-Listbox-Highlight.blp")
  texTL2:SetBlendMode("ADD")
  texTL2:SetTexCoord(0.5, 0, 0.5, 1, 1, 0, 1, 1)
  texTL2:SetPoint("TOPLEFT", texTL1, "TOPRIGHT")
  texTL2:SetPoint("BOTTOMRIGHT", topleft, "RIGHT")

  topleft.Highlight = function()
    if WeakAurasOptionsSaved.lockPositions then
      return
    end
    texTL1:Show()
    texTL2:Show()
  end
  topleft.Clear = function()
    texTL1:Hide()
    texTL2:Hide()
  end

  -- top, right, bottom, left

  local top = CreateFrame("Frame", nil, frame)
  top:EnableMouse()
  top:SetHeight(8)
  top:SetPoint("TOPRIGHT", topright, "TOPLEFT")
  top:SetPoint("TOPLEFT", topleft, "TOPRIGHT")

  local texT = top:CreateTexture(nil, "OVERLAY")
  texT:SetTexture("Interface\\BUTTONS\\UI-Listbox-Highlight.blp")
  texT:SetBlendMode("ADD")
  texT:SetPoint("TOPRIGHT", topright, "TOPRIGHT", -3, -3)
  texT:SetPoint("BOTTOMLEFT", topleft, "LEFT", 3, 0)

  top.Highlight = function()
    if WeakAurasOptionsSaved.lockPositions then
      return
    end
    texT:Show()
  end
  top.Clear = function()
    texT:Hide()
  end

  local right = CreateFrame("Frame", nil, frame)
  right:EnableMouse()
  right:SetWidth(8)
  right:SetPoint("BOTTOMRIGHT", bottomright, "TOPRIGHT")
  right:SetPoint("TOPRIGHT", topright, "BOTTOMRIGHT")

  local texR = right:CreateTexture(nil, "OVERLAY")
  texR:SetTexture("Interface\\BUTTONS\\UI-Listbox-Highlight.blp")
  texR:SetBlendMode("ADD")
  texR:SetPoint("BOTTOMRIGHT", bottomright, "BOTTOMRIGHT", -3, 3)
  texR:SetPoint("TOPLEFT", topright, "TOP", 0, -3)

  right.Highlight = function()
    if WeakAurasOptionsSaved.lockPositions then
      return
    end
    texR:Show()
  end
  right.Clear = function()
    texR:Hide()
  end

  local bottom = CreateFrame("Frame", nil, frame)
  bottom:EnableMouse()
  bottom:SetHeight(8)
  bottom:SetPoint("BOTTOMLEFT", bottomleft, "BOTTOMRIGHT")
  bottom:SetPoint("BOTTOMRIGHT", bottomright, "BOTTOMLEFT")

  local texB = bottom:CreateTexture(nil, "OVERLAY")
  texB:SetTexture("Interface\\BUTTONS\\UI-Listbox-Highlight.blp")
  texB:SetBlendMode("ADD")
  texB:SetTexCoord(1, 0, 0, 0, 1, 1, 0, 1)
  texB:SetPoint("BOTTOMLEFT", bottomleft, "BOTTOMLEFT", 3, 3)
  texB:SetPoint("TOPRIGHT", bottomright, "RIGHT", -3, 0)

  bottom.Highlight = function()
    if WeakAurasOptionsSaved.lockPositions then
      return
    end
    texB:Show()
  end
  bottom.Clear = function()
    texB:Hide()
  end

  local left = CreateFrame("Frame", nil, frame)
  left:EnableMouse()
  left:SetWidth(8)
  left:SetPoint("TOPLEFT", topleft, "BOTTOMLEFT")
  left:SetPoint("BOTTOMLEFT", bottomleft, "TOPLEFT")

  local texL = left:CreateTexture(nil, "OVERLAY")
  texL:SetTexture("Interface\\BUTTONS\\UI-Listbox-Highlight.blp")
  texL:SetBlendMode("ADD")
  texL:SetTexCoord(1, 0, 0, 0, 1, 1, 0, 1)
  texL:SetPoint("BOTTOMLEFT", bottomleft, "BOTTOMLEFT", 3, 3)
  texL:SetPoint("TOPRIGHT", topleft, "TOP", 0, -3)

  left.Highlight = function()
    if WeakAurasOptionsSaved.lockPositions then
      return
    end
    texL:Show()
  end
  left.Clear = function()
    texL:Hide()
  end

  -- return in cw order
  return top, topright, right, bottomright, bottom, bottomleft, left, topleft
end

--- @class AlignmentLineReference
--- @field id auraId
--- @field side "LEFT"|"RIGHT"|"TOP"|"BOTTOM"|"CENTERX"|"CENTERY" The side the reference
--- @field pos1 number The bottom position for vertical or the left position for horizontal lines
--- @field pos2 number The top position for vertical or the right position for horizontal lines

--- @class AlignmentLine
--- @field SetStartPoint fun(self: AlignmentLine, relativePoint: AnchorPoint, relativeTo: Region, offsetX: number?, offsetY: number?)
--- @field SetEndPoint fun(self: AlignmentLine, relativePoint: AnchorPoint, relativeTo: Region, offsetX: number?, offsetY: number?)
--- @field SetThickness fun(self: AlignmentLine, thickness: number)
--- @field SetHighlighted fun(self: AlignmentLine, highlight: boolean)

--- @class LineObjectPool
--- @field Acquire fun(self: LineObjectPool): AlignmentLine
--- @field Release fun(self: LineObjectPool, line: AlignmentLine)

--- @class LineInformation
--- @field position number
--- @field delta number
--- @field gridLine boolean
--- @field references AlignmentLineReference[]
--- @field highlightTextures Texture[]
--- @field line AlignmentLine?
--- @field SetStartPoint fun(self: LineInformation, relativePoint: AnchorPoint, relativeTo: Region, offsetX: number?, offsetY: number?)
--- @field SetEndPoint fun(self: LineInformation, relativePoint: AnchorPoint, relativeTo: Region, offsetX: number?, offsetY: number?)
--- @field SetThickness fun(self: LineInformation, thickness: number)
--- @field SetHighlighted fun(self: LineInformation, highlight: boolean)
--- @field Hide fun(self: LineInformation)
--- @field Show fun(self: LineInformation)
--- @field UpdateColor fun(self: LineInformation)
--- @field UpdateHighlight fun(self: LineInformation)
--- @field AcquireLine fun(self: LineInformation)
--- @field ReleaseLine fun(self: LineInformation)
--- @field Release fun(self: LineInformation)
--- @field Score fun(self: LineInformation, positions: table<"LEFT"|"RIGHT"|"TOP"|"BOTTOM"|"CENTERX"|"CENTERY", number>): number

--- @class AlignmentLines
local AlignmentLines = CreateFrame("Frame", nil, UIParent) --[[@as AlignmentLines]]
AlignmentLines:SetAllPoints(UIParent)
AlignmentLines:SetFrameStrata("BACKGROUND")

local HighlightFrame = CreateFrame("Frame", nil, UIParent)
HighlightFrame:SetAllPoints(UIParent)
HighlightFrame:SetFrameStrata("TOOLTIP")

--- @type LineObjectPool
AlignmentLines.linePool = CreateObjectPool(
  function(self)
    return AlignmentLines:CreateLine()
  end,
  function(self, line)
    line:Hide()
  end)

HighlightFrame.texturePool = CreateObjectPool(
  function(self)
    local tex = HighlightFrame:CreateTexture()
    tex:SetTexture("Interface\\BUTTONS\\UI-Listbox-Highlight.blp")
    tex:SetVertexColor(1, 1, 1, 0.3)
    return tex
  end,
  function(self, texture)
    texture:Hide()
    texture:ClearAllPoints()
  end)

--- @type fun(side: "LEFT"|"RIGHT"|"BOTTOM"|"TOP"|"CENTERX"|"CENTERY"): "LEFT"|"RIGHT"|"BOTTOM"|"TOP"|"CENTERX"|"CENTERY"
local function MirrorSide(side)
  if side == "LEFT" then
    return "RIGHT"
  elseif side == "RIGHT" then
    return "LEFT"
  elseif side == "TOP" then
    return "BOTTOM"
  elseif side == "BOTTOM" then
    return "TOP"
  else -- "CENTERX" or "CENTERY"
    return side
  end
end

--- @type fun(side: "LEFT"|"RIGHT"|"BOTTOM"|"TOP"|"CENTERX"|"CENTERY"): "LEFT"|"RIGHT"|"BOTTOM"|"TOP"|"CENTERX"|"CENTERY"|nil
local function Pos1Side(side)
  if side == "LEFT" or side == "RIGHT" or side == "CENTERX" then
    return "BOTTOM"
  elseif side == "TOP" or side == "BOTTOM" or side == "CENTERY" then
    return "LEFT"
  end
end

--- @type fun(side: "LEFT"|"RIGHT"|"BOTTOM"|"TOP"|"CENTERX"|"CENTERY"): "LEFT"|"RIGHT"|"BOTTOM"|"TOP"|"CENTERX"|"CENTERY"|nil
local function Pos2Side(side)
  if side == "LEFT" or side == "RIGHT" or side == "CENTERX" then
    return "TOP"
  elseif side == "TOP" or side == "BOTTOM" or side == "CENTERY" then
    return "RIGHT"
  end
end

--- @class LineInformation
local LineInformationFuncs = {
  SetStartPoint = function(self, relativePoint, relativeTo, offsetX, offsetY)
    self.startPoint = {relativePoint, relativeTo, offsetX, offsetY}
    if self.line then
      self.line:SetStartPoint(relativePoint, relativeTo, offsetX, offsetY)
    end
  end,
  SetEndPoint = function(self, relativePoint, relativeTo, offsetX, offsetY)
    self.endPoint = {relativePoint, relativeTo, offsetX, offsetY}
    if self.line then
      self.line:SetEndPoint(relativePoint, relativeTo, offsetX, offsetY)
    end
  end,
  SetThickness = function(self, thickness)
    self.thickness = thickness
    if self.line then
      self.line:SetThickness(thickness)
    end
  end,
  Show = function(self)
    if not self.line then
      self:AcquireLine()
    end
    self:UpdateColor()
    self.line:SetStartPoint(unpack(self.startPoint))
    self.line:SetEndPoint(unpack(self.endPoint))
    self.line:SetThickness(self.thickness)
    self.line:Show()
  end,
  SetHighlighted = function(self, highlight)
    if self.highlight == highlight then
      return
    end

    local needLine = highlight or showNormalLines
    self.highlight = highlight

    if needLine and not self.line then
      self:Show()
    elseif not needLine and self.line then
      self:ReleaseLine()
    end
    self:UpdateColor()
    self:UpdateHighlight()
  end,
  UpdateColor = function(self)
    if not self.line then
      return
    end

    if self.highlight then
      if self.gridLine then
        self.line:SetColorTexture(unpack(gridHighlightColor))
      else
        self.line:SetColorTexture(unpack(highlightColor))
      end
    else
      if self.gridLine then
        self.line:SetColorTexture(unpack(gridColor))
      else
        self.line:SetColorTexture(unpack(normalColor))
      end
    end
  end,
  UpdateHighlight = function(self)
    if self.highlight then
      for _, data in ipairs(self.references) do
        local region = WeakAuras.GetRegion(data.id)
        local texture = HighlightFrame.texturePool:Acquire()
        texture:SetAllPoints(region)
        texture:SetDrawLayer("ARTWORK", 7)
        texture:Show()
        tinsert(self.highlightTextures, texture)
      end
    else
      for _, texture in ipairs(self.highlightTextures) do
        HighlightFrame.texturePool:Release(texture)
      end
      wipe(self.highlightTextures)
    end
  end,
  AcquireLine = function(self)
    if not self.line then
      self.line = AlignmentLines.linePool:Acquire()
    end
  end,
  ReleaseLine = function(self)
    if self.line then
      AlignmentLines.linePool:Release(self.line)
      self.line = nil
    end
  end,
  Release = function(self)
    -- Clears any aura highlights
    self:SetHighlighted(false)
    self:ReleaseLine()
  end,
  AddReference = function(self, id, side, pos1, pos2)
    tinsert(self.references, {id = id, side = side, pos1 = pos1, pos2 = pos2})
  end,
  --- @type fun(self: LineInformation, positions: table<"LEFT"|"RIGHT"|"BOTTOM"|"TOP"|"CENTERX"|"CENTERY", number>) : number
  Score = function(self, positions)
    if self.gridLine then
      return 0 -- Prefer aura lines
    else
      local score = -1

      for _, ref in ipairs(self.references) do
        -- The line is within MAGNETIC_ALIGNMENT, otherwise we wouldn't be asked to score it
        -- This compares whether the line is for the same "side",
        -- by checking the distance to that side
        local auraPos = positions[ref.side]
        local mirrorPos = positions[MirrorSide(ref.side)]
        if auraPos then
          local dist = distance(auraPos, self.position)
          if dist < MAGNETIC_ALIGNMENT then
            -- Same side: 100 as a base, meaning these lines are heavily preferred to lines
            -- for other sides
            score = max(score, 100 + (MAGNETIC_ALIGNMENT - dist))
          elseif mirrorPos then
            dist = distance(mirrorPos, self.position)
            score = max(score, 10 + (MAGNETIC_ALIGNMENT - dist))
          end

          -- Now check how far away the reference is on the orthogonal direction
          -- Add up to 80 points for being near a reference
          local auraPos1 = positions[Pos1Side(ref.side)]
          local auraPos2 = positions[Pos2Side(ref.side)]
          if auraPos1 and auraPos2 then
            local minDistanceOrth = min(distance(auraPos1, ref.pos1), distance(auraPos2, ref.pos2))
            if minDistanceOrth < 10 * MAGNETIC_ALIGNMENT then
              score = score + 8 * (10 * MAGNETIC_ALIGNMENT - minDistanceOrth)
            end
          end
        end
      end
      return score
    end
  end,

}

--- @type fun(position: number, gridLine: boolean): LineInformation
local function CreateLineInformation(position, gridLine)
  local line = {}
  for k, f in pairs(LineInformationFuncs) do
    line[k] = f
  end
  line.position = position
  line.gridLine = gridLine
  line.references = {}
  line.highlightTextures = {}
  line.highlight = nil
  return line
end

--- @type table<number, LineInformation>
AlignmentLines.horizontalLines = {}
--- @type table<number, LineInformation>
AlignmentLines.verticalLines = {}

--- @type fun(input: number): number
local function RoundSmallDifference(input)
  local r = Round(input)
  if (abs(r - input) < 0.1) then
    return r
  end
  return input
end

--- @type fun(input: number): number
local function AlignToPixelX(virX)
  local physicalWidth, physicalHeight = GetPhysicalScreenSize();
  local virtualWidth = GetScreenWidth()
  local phyX = virX * physicalWidth / virtualWidth
  return Round(10 * Round(phyX) * virtualWidth / physicalWidth) / 10
end

--- @type fun(input: number): number
local function AlignToPixelY(virY)
  local physicalWidth, physicalHeight = GetPhysicalScreenSize();
  local virtualHeight = GetScreenHeight()
  local phyY = virY * physicalHeight / virtualHeight
  return Round(10 * Round(phyY) * virtualHeight / physicalHeight) / 10
end

---@param self AlignmentLines
---@param sizerPoint AnchorPoint?
AlignmentLines.CreateMiddleLines = function(self, sizerPoint)
  if not createCenterLines then
    return
  end

  local midX, midY = UIParent:GetCenter()
  midX = RoundSmallDifference(midX)
  midY = RoundSmallDifference(midY)
  if not sizerPoint or sizerPoint:find("LEFT", 1) or sizerPoint:find("RIGHT", 1) then
    local line = CreateLineInformation(midX, true)
    line:SetStartPoint("TOPLEFT", UIParent, midX, 0)
    line:SetEndPoint("BOTTOMLEFT", UIParent, midX, 0)
    line:SetThickness(2)
    self.verticalLines[midX] = line
  end

  if not sizerPoint or sizerPoint:find("BOTTOM") or sizerPoint:find("TOP") then
    local line = CreateLineInformation(midY, true)
    line:SetStartPoint("BOTTOMLEFT", UIParent, 0, midY)
    line:SetEndPoint("BOTTOMRIGHT", UIParent, 0, midY)
    line:SetThickness(2)
    self.horizontalLines[midY] = line
  end
end

--- @type fun(self: AlignmentLines, data: auraData, sizerPoint: AnchorPoint?): LineInformation, LineInformation
AlignmentLines.CreateLineInformation = function(self, data, sizerPoint)
  local addVertical = not sizerPoint or sizerPoint:find("LEFT", 1) or sizerPoint:find("RIGHT", 1)
  local addHorizontal = not sizerPoint or sizerPoint:find("BOTTOM", 1) or sizerPoint:find("TOP", 1)

  --- @type LineInformation, LineInformation
  local horizontalLines, verticalLines = {}, {}
  --- @type table<auraId, boolean>
  local skipIds = {}
  for child in OptionsPrivate.Private.TraverseAll(data) do
    skipIds[child.id] = true
  end

  for id, v in pairs(OptionsPrivate.displayButtons) do
    local region = WeakAuras.GetRegion(v.data.id)
    if not skipIds[id]
       and v.view.visibility >= 1
       and region and not region:IsAnchoringRestricted()
       and v.data.regionType ~= "group"
       and v.data.regionType ~= "dynamicgroup"
    then
      local scale = region:GetEffectiveScale() / UIParent:GetEffectiveScale()
      local left = region:GetLeft()
      left = left and AlignToPixelX(left * scale) or nil
      local right = region:GetRight()
      right = right and AlignToPixelX(right * scale) or nil
      local top = region:GetTop()
      top = top and AlignToPixelY(top * scale) or nil
      local bottom = region:GetBottom()
      bottom = bottom and AlignToPixelY(bottom * scale) or nil
      local centerX, centerY = region:GetCenter()
      centerX = centerX and AlignToPixelX(centerX * scale) or nil
      centerY = centerY and AlignToPixelY(centerY * scale) or nil

      if not IsControlKeyDown() then
        if addVertical then
          if left and bottom and top then
            local leftLine = CreateLineInformation(left, false)
            leftLine:AddReference(id, "LEFT", bottom, top)
            tinsert(verticalLines, leftLine)
          end

          if right and bottom and top then
            local rightLine = CreateLineInformation(right, false)
            rightLine:AddReference(id, "RIGHT", bottom, top)
            tinsert(verticalLines, rightLine)
          end
        end
        if addHorizontal then
          if top and left and right then
            local topLine = CreateLineInformation(top, false)
            topLine:AddReference(id, "TOP", left, right)
            tinsert(horizontalLines, topLine)
          end

          if bottom and left and right then
            local bottomLine = CreateLineInformation(bottom, false)
            bottomLine:AddReference(id, "BOTTOM", left, right)
            tinsert(horizontalLines, bottomLine)
          end
        end
      else
        if addVertical then
          if centerX and bottom and top then
            local xLine = CreateLineInformation(centerX, false)
            xLine:AddReference(id, "CENTERX", bottom, top)
            tinsert(verticalLines, xLine)
          end
        end
        if addHorizontal then
          if centerY and left and right then
            local yLine = CreateLineInformation(centerY, false)
            yLine:AddReference(id, "CENTERY", left, right)
            tinsert(horizontalLines, yLine)
          end
        end
      end
    end
  end

  table.sort(verticalLines, function(a, b) return a.position < b.position end)
  table.sort(horizontalLines, function(a, b) return a.position < b.position end)

  return self:MergeLineInformation(verticalLines), self:MergeLineInformation(horizontalLines)
end

--- @type fun(self: AlignmentLines, lines: LineInformation): LineInformation
AlignmentLines.MergeLineInformation = function(self, lines)
  local startIndex
  local startPos
  -- Add a line at infinity at the end, this makes the loop easier
  tinsert(lines, {position = math.huge})

  --- @type LineInformation
  local result = {}
  for index, line in ipairs(lines) do
    if not startPos then
      startPos = line.position
      startIndex = index
    else
      if (line.position - startPos) >= 1 then
        if startIndex then
          -- This line is too far away from the last lines to merge,
          -- So merge from startIndex to index - 1
          local lineToInsert = lines[startIndex]
          local positionSum = lineToInsert.position
          for i = startIndex + 1, index - 1 do
            local lineToMerge = lines[i]
            positionSum = positionSum + lineToMerge.position
            tinsert(lineToInsert.references, lineToMerge.references[1])
          end
          lineToInsert.position = positionSum / (index - startIndex)
          tinsert(result, lineToInsert)
          -- Now start a potential new merge from this line
          startPos = line.position
          startIndex = index
        end
      else
        -- Will be merged later
      end
    end
  end
  -- And remove the infinity line at the end
  lines[#lines] = nil

  return result
end

---@param self AlignmentLines
AlignmentLines.CleanUpLines = function(self)
  for _, line in pairs(self.horizontalLines) do
    line:Release()
  end

  for _, line in pairs(self.verticalLines) do
    line:Release()
  end

  wipe(self.horizontalLines)
  wipe(self.verticalLines)
end

---@param self AlignmentLines
---@param data auraData
---@param sizerPoint AnchorPoint?
AlignmentLines.CreateLines = function(self, data, sizerPoint)
  self:CleanUpLines()

  local align = (WeakAurasOptionsSaved.magnetAlign and not IsShiftKeyDown())
                    or (not WeakAurasOptionsSaved.magnetAlign and IsShiftKeyDown())
  if align then
    self:CreateMiddleLines()

    local auraVerticalLinesInfo, auraHorizontalLinesInfo = self:CreateLineInformation(data, sizerPoint)

    for _, lineInfo in ipairs(auraVerticalLinesInfo) do
      local x = lineInfo.position
      if self.verticalLines[floor(x)] or self.verticalLines[ceil(x)] then
        -- Grid lines are always on integer values
        -- Ignore a grid line that is close enough is already there
      else
        lineInfo:SetStartPoint("TOPLEFT", UIParent, x, 0)
        lineInfo:SetEndPoint("BOTTOMLEFT", UIParent, x, 0)
        lineInfo:SetThickness(2)
        self.verticalLines[x] = lineInfo
      end
    end

    for _, lineInfo in ipairs(auraHorizontalLinesInfo) do
      local y = lineInfo.position
      if self.horizontalLines[floor(y)] or self.horizontalLines[ceil(y)] then
        -- Grid lines are always on integer values
        -- Ignore a grid line that is close enough is already there
      else
        lineInfo:SetStartPoint("BOTTOMLEFT", UIParent, 0, y)
        lineInfo:SetEndPoint("BOTTOMRIGHT", UIParent, 0, y)
        lineInfo:SetThickness(2)
        self.horizontalLines[y] = lineInfo
      end
    end
  end
end

---@param lines AlignmentLine[]
---@param positions table<"LEFT"|"RIGHT"|"TOP"|"BOTTOM"|"CENTERX"|"CENTERY", number>
---       The positions of the aura, that are used to score lines
---@param auraSize number?
---       The size of the aura, this is used to highlight additional lines that exactly
---       auraSize away
---@return number? -- The delta
local function SelectLines(lines, positions, auraSize)
  if #lines == 0 then
    -- Nothing to do
  elseif #lines == 1 then
    lines[1]:SetHighlighted(true)
    return lines[1].delta
  else
    --- @type number
    local bestScore = -1
    --- @type AlignmentLine?
    local bestLine = nil
    for _, line in ipairs(lines) do
      local lineScore = line:Score(positions)
      if lineScore > bestScore then
        bestScore = lineScore
        bestLine = line
      end
    end

    for _, line in ipairs(lines) do
      if line == bestLine then
        line:SetHighlighted(true)
      elseif bestLine then
        local diffBetweenLines = distance(line.position, bestLine.position)
        if auraSize and distance(diffBetweenLines, auraSize) < 1 then
          -- Other line is the as far away as the aura is wide
          line:SetHighlighted(true)
        else
          line:SetHighlighted(false)
        end
      else
        line:SetHighlighted(false)
      end
    end
    return bestLine and bestLine.delta
  end
end

AlignmentLines.ShowLinesFor = function(self, ctrlKey, region, sizePoint)
  local align = (WeakAurasOptionsSaved.magnetAlign and not IsShiftKeyDown())
                    or (not WeakAurasOptionsSaved.magnetAlign and IsShiftKeyDown())
  if not align then
    return
  end

  local scale = region:GetEffectiveScale() / UIParent:GetScale()

  local centerX, centerY = region:GetCenter()
  centerX, centerY = centerX * scale, centerY * scale
  local left, right = region:GetLeft() * scale, region:GetRight() * scale
  local top, bottom = region:GetTop() * scale, region:GetBottom() * scale

  if region.regionType == "group" then
    left = left + region.blx * scale
    right = right + region.trx * scale
    bottom = bottom + region.bly * scale
    top = top + region.try * scale
    centerX = (left + right) / 2
    centerY = (bottom + top) / 2
  end

  local positions = {
    LEFT = left,
    RIGHT = right,
    TOP = top,
    BOTTOM = bottom,
    CENTERX = centerX,
    CENTERY = centerY
  }

  local verticalPotentials = {}
  local horizontalPotentials = {}
  if sizePoint then
    local sizeX = sizePoint:find("LEFT", 1) and left or right
    local sizeY = sizePoint:find("TOP", 1) and top or bottom

    for pos, line in pairs(self.verticalLines) do
      if distance(sizeX, pos) < MAGNETIC_ALIGNMENT then
        line.delta = pos - sizeX
        tinsert(verticalPotentials, line)
      else
        line:SetHighlighted(false)
      end
    end

    for pos, line in pairs(self.horizontalLines) do
      if distance(sizeY, pos) < MAGNETIC_ALIGNMENT then
        line.delta = pos - sizeY
        tinsert(horizontalPotentials, line)
      else
        line:SetHighlighted(false)
      end
    end

    mover.verticalDelta = SelectLines(verticalPotentials, positions)
    mover.horizontalDelta = SelectLines(horizontalPotentials, positions)
  else
    if ctrlKey then
      for pos, line in pairs(self.verticalLines) do
        if distance(centerX, pos) < MAGNETIC_ALIGNMENT then
          line.delta = pos - centerX
          tinsert(verticalPotentials, line)
        else
          line:SetHighlighted(false)
        end
      end

      for pos, line in pairs(self.horizontalLines) do
        if distance(centerY, pos) < MAGNETIC_ALIGNMENT then
          line.delta = pos - centerY
          tinsert(horizontalPotentials, line)
        else
          line:SetHighlighted(false)
        end
      end
      mover.verticalDelta = SelectLines(verticalPotentials, positions)
      mover.horizontalDelta = SelectLines(horizontalPotentials, positions)
    else
      for pos, line in pairs(self.verticalLines) do
        if distance(left, pos) < MAGNETIC_ALIGNMENT then
          line.delta = pos - left
          tinsert(verticalPotentials, line)
        elseif distance(right, pos) < MAGNETIC_ALIGNMENT then
          line.delta = pos - right
          tinsert(verticalPotentials, line)
        else
          line:SetHighlighted(false)
        end
      end

      for pos, line in pairs(self.horizontalLines) do
        if distance(bottom, pos) < MAGNETIC_ALIGNMENT then
          line.delta = pos - bottom
          tinsert(horizontalPotentials, line)
        elseif distance(top, pos) < MAGNETIC_ALIGNMENT then
          line.delta = pos - top
          tinsert(horizontalPotentials, line)
        else
          line:SetHighlighted(false)
        end
      end

      local auraWidth = right - left
      local auraHeight = top - bottom
      mover.verticalDelta = SelectLines(verticalPotentials, positions, auraWidth)
      mover.horizontalDelta = SelectLines(horizontalPotentials, positions, auraHeight)
    end
  end
end

local function ConstructMoverSizer(parent)
  local frame = CreateFrame("Frame", nil, parent, "BackdropTemplate")
  frame:SetBackdrop({
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    edgeSize = 12,
    insets = {left = 0, right = 0, top = 0, bottom = 0}
  })
  frame:EnableMouse()

  frame.top, frame.topright, frame.right, frame.bottomright, frame.bottom, frame.bottomleft, frame.left, frame.topleft
  = ConstructSizer(frame)

  frame.arrowTexture, frame.offscreenText = ConstructMover(frame)

  frame.top.Clear()
  frame.topright.Clear()
  frame.right.Clear()
  frame.bottomright.Clear()
  frame.bottom.Clear()
  frame.bottomleft.Clear()
  frame.left.Clear()
  frame.topleft.Clear()

  local mover = CreateFrame("Frame", nil, frame)
  mover:EnableMouse()
  mover.moving = {}
  mover.interims = {}
  mover.selfPointIcon = mover:CreateTexture()
  mover.selfPointIcon:SetTexture("Interface\\GLUES\\CharacterSelect\\Glues-AddOn-Icons.blp")
  mover.selfPointIcon:SetWidth(16)
  mover.selfPointIcon:SetHeight(16)
  mover.selfPointIcon:SetTexCoord(0, 0.25, 0, 1)
  mover.anchorPointIcon = mover:CreateTexture()
  mover.anchorPointIcon:SetTexture("Interface\\GLUES\\CharacterSelect\\Glues-AddOn-Icons.blp")
  mover.anchorPointIcon:SetWidth(16)
  mover.anchorPointIcon:SetHeight(16)
  mover.anchorPointIcon:SetTexCoord(0, 0.25, 0, 1)

  local moverText = mover:CreateFontString(nil, "OVERLAY", "GameFontNormal")
  mover.text = moverText
  moverText:Hide()

  local sizerText = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
  frame.text = sizerText
  sizerText:Hide()

  frame.ScaleCorners = function(self, width, height)
    local limit = math.min(width, height) + 16
    local size = 16
    if limit <= 40 then
      size = limit * (2/5)
    end
    frame.bottomleft:SetWidth(size)
    frame.bottomleft:SetHeight(size)
    frame.bottomright:SetWidth(size)
    frame.bottomright:SetHeight(size)
    frame.topright:SetWidth(size)
    frame.topright:SetHeight(size)
    frame.topleft:SetWidth(size)
    frame.topleft:SetHeight(size)
  end

  frame.ReAnchor = function(self)
    if mover.moving.region then
      self:AnchorPoints(mover.moving.region, mover.moving.data)
    end
  end

  frame.AnchorPoints = function(self, region, data)
    local scale = region:GetEffectiveScale() / UIParent:GetEffectiveScale()
    if data.regionType == "group" then
      mover:SetWidth((region.trx - region.blx) * scale)
      mover:SetHeight((region.try - region.bly) * scale)
    else
      mover:SetWidth(region:GetWidth() * scale)
      mover:SetHeight(region:GetHeight() * scale)
    end
  end

  frame.GetCurrentId = function(self)
    return self.currentId
  end

  frame.SizingSetData = function(self, data, width, height, alignDeltaX, alignDeltaY, scale)
    alignDeltaX = alignDeltaX or 0
    alignDeltaY = alignDeltaY or 0

    local deltaWidth = width - data.width
    local deltaHeight = height - data.height

    local auraSelfPoint = data.selfPoint
    local moverSizePoint = mover.sizePoint

    local parent = data.parent
    if parent then
      local parentData = WeakAuras.GetData(parent)
      if parentData == "dynamicgroup" then
        -- If the aura is in a dynamic group then we don't want to set xOffset/yOffset at all.
        -- These settings ensure that
        auraSelfPoint = "TOPRIGHT"
        moverSizePoint = "BOTTOMLEFT"
      end
    end

    if auraSelfPoint:find("LEFT", 1) then
      if moverSizePoint:find("LEFT", 1) then
        data.xOffset = data.xOffset - deltaWidth + alignDeltaX / scale
        data.width = width - alignDeltaX / scale
      elseif moverSizePoint:find("RIGHT", 1) then
        data.width = width + alignDeltaX / scale
      end
    elseif auraSelfPoint:find("RIGHT", 1) then
      if moverSizePoint:find("LEFT", 1) then
        data.width = width - alignDeltaX / scale
      elseif moverSizePoint:find("RIGHT", 1) then
        data.xOffset = data.xOffset + deltaWidth + alignDeltaX / scale
        data.width = width + alignDeltaX / scale
      end
    else -- CENTER
      if moverSizePoint:find("LEFT", 1) then
        data.xOffset = data.xOffset - deltaWidth / 2 + alignDeltaX / 2 / scale
        data.width = width - alignDeltaX / scale
      else
        data.xOffset = data.xOffset + deltaWidth / 2 + alignDeltaX / 2 / scale
        data.width = width + alignDeltaX / scale
      end
    end

    if auraSelfPoint:find("BOTTOM", 1) then
      if moverSizePoint:find("BOTTOM", 1) then
        data.yOffset = data.yOffset - deltaHeight + alignDeltaY / scale
        data.height = height - alignDeltaY / scale
      elseif moverSizePoint:find("TOP", 1) then
        data.height = height + alignDeltaY / scale
      end
    elseif auraSelfPoint:find("TOP", 1) then
      if moverSizePoint:find("BOTTOM", 1) then
        data.height = height - alignDeltaY / scale
      elseif moverSizePoint:find("TOP", 1) then
        data.yOffset = data.yOffset + deltaHeight + alignDeltaY / scale
        data.height = height + alignDeltaY / scale
      end
    else -- CENTER
      if moverSizePoint:find("BOTTOM", 1) then
        data.yOffset = data.yOffset - deltaHeight / 2 + alignDeltaY / 2 / scale
        data.height = height - alignDeltaY / scale
      else
        data.yOffset = data.yOffset + deltaHeight / 2 + alignDeltaY / 2 / scale
        data.height = height + alignDeltaY / scale
      end
    end
  end

  frame.SetToRegion = function(self, region, data)
    frame.currentId = data.id
    local scale = region:GetEffectiveScale() / UIParent:GetEffectiveScale()
    mover.moving.region = region
    mover.moving.data = data
    mover.onUpdate(mover, 0)
    local ok, selfPoint, anchor, anchorPoint, xOff, yOff = pcall(region.GetPoint, region, 1)
    if not ok then
      return
    end
    self:Show()

    mover.selfPoint, mover.anchor, mover.anchorPoint = selfPoint, anchor, anchorPoint

    xOff = xOff or 0
    yOff = yOff or 0
    mover:ClearAllPoints()
    frame:ClearAllPoints()
    if data.regionType == "group" then
      local blx, bly, trx, try = region:GetBoundingRect()
      mover:SetWidth((trx - blx) * scale)
      mover:SetHeight((try - bly) * scale)

      mover:SetPoint("BOTTOMLEFT", mover.anchor or UIParent, mover.anchorPoint or "CENTER",
                     (xOff + region.blx) * scale, (yOff + region.bly) * scale)
    else
      mover:SetWidth(region:GetWidth() * scale)
      mover:SetHeight(region:GetHeight() * scale)
      mover:SetPoint(mover.selfPoint or "CENTER", mover.anchor or UIParent, mover.anchorPoint or "CENTER",
                     xOff * scale, yOff * scale)
    end
    frame:SetPoint("BOTTOMLEFT", mover, "BOTTOMLEFT", -8, -8)
    frame:SetPoint("TOPRIGHT", mover, "TOPRIGHT", 8, 8)
    frame:ScaleCorners(region:GetWidth(), region:GetHeight())
    local regionStrata = region:GetFrameStrata()
    if regionStrata then
      local strata = math.min(tIndexOf(OptionsPrivate.Private.frame_strata_types, regionStrata) + 1, 9)
      frame:SetFrameStrata(OptionsPrivate.Private.frame_strata_types[strata])
      mover:SetFrameStrata(OptionsPrivate.Private.frame_strata_types[strata])
      frame:SetFrameLevel(region:GetFrameLevel() + 1)
      mover:SetFrameLevel(region:GetFrameLevel() + 1)
    end

    local db = OptionsPrivate.savedVars.db
    mover.startMoving = function()
      if WeakAurasOptionsSaved.lockPositions then
        return
      end
      OptionsPrivate.Private.CancelAnimation(region, true, true, true, true, true)
      mover:ClearAllPoints()
      if data.regionType == "group" then
        mover:SetPoint("BOTTOMLEFT", region, mover.anchorPoint, region.blx * scale, region.bly * scale)
      else
        mover:SetPoint(mover.selfPoint, region, mover.selfPoint)
      end
      region:StartMoving()
      mover.isMoving = true
      mover.onUpdate(mover, 0)
      mover.text:Show()
      -- build list of alignment coordinates
      AlignmentLines:CreateLines(mover.moving.data)
      AlignmentLines:Show()
      HighlightFrame:Show()
    end

    mover.doneMoving = function(self, event, key)
      if event == "MODIFIER_STATE_CHANGED" then
        if key == "LCTRL" or key == "RCTRL" or key == "LSHIFT" or key == "RSHIFT" then
          AlignmentLines:CleanUpLines()
          AlignmentLines:CreateLines(mover.moving.data, mover.sizePoint)
          AlignmentLines:Show()
          HighlightFrame:Show()
        end
        return
      end

      if not mover.isMoving then
        return
      end
      region:StopMovingOrSizing()
      mover.isMoving = false
      mover.text:Hide()
      AlignmentLines:CleanUpLines()
      AlignmentLines:Hide()
      HighlightFrame:Hide()

      local align = (WeakAurasOptionsSaved.magnetAlign and not IsShiftKeyDown())
                    or (not WeakAurasOptionsSaved.magnetAlign and IsShiftKeyDown())

      local xDelta = 0
      local yDelta = 0
      if align then
        xDelta = mover.verticalDelta or 0
        yDelta = mover.horizontalDelta or 0
      end

      if data.xOffset and data.yOffset then
        local selfX, selfY = mover.selfPointIcon:GetCenter()
        local anchorX, anchorY = mover.anchorPointIcon:GetCenter()
        local dX = selfX - anchorX
        local dY = selfY - anchorY
        data.xOffset = dX / scale + xDelta / scale
        data.yOffset = dY / scale + yDelta / scale
      end
      region:ResetPosition()
      WeakAuras.Add(data)
      OptionsPrivate.Private.AddParents(data)
      WeakAuras.UpdateThumbnail(data)

      local xOff, yOff
      mover.selfPoint, mover.anchor, mover.anchorPoint, xOff, yOff = region:GetPoint(1)
      xOff = xOff or 0
      yOff = yOff or 0
      mover:ClearAllPoints()
      if data.regionType == "group" then
        mover:SetWidth((region.trx - region.blx) * scale)
        mover:SetHeight((region.try - region.bly) * scale)
        mover:SetPoint("BOTTOMLEFT", mover.anchor, mover.anchorPoint,
                       (xOff + region.blx) * scale, (yOff + region.bly) * scale)
      else
        mover:SetWidth(region:GetWidth() * scale)
        mover:SetHeight(region:GetHeight() * scale)
        mover:SetPoint(mover.selfPoint, mover.anchor, mover.anchorPoint, xOff * scale, yOff * scale)
      end
      frame.text:Hide()
      frame:SetScript("OnUpdate", nil)

      WeakAuras.FillOptions()
      OptionsPrivate.Private.Animate("display", data.uid, "main", data.animation.main,
                                     OptionsPrivate.Private.EnsureRegion(data.id), false, nil, true)
    end

    if data.parent and db.displays[data.parent] and db.displays[data.parent].regionType == "dynamicgroup" then
      mover:SetScript("OnMouseDown", nil)
      mover:SetScript("OnMouseUp", nil)
      mover:SetScript("OnEvent", nil)
      mover:SetScript("OnHide", nil)
    else
      mover:SetScript("OnMouseDown", mover.startMoving)
      mover:SetScript("OnMouseUp", mover.doneMoving)
      mover:SetScript("OnEvent", mover.doneMoving)
      mover:SetScript("OnHide", mover.doneMoving)
    end

    if region:IsResizable() then
      frame.startSizing = function(point)
        if WeakAurasOptionsSaved.lockPositions then
          return
        end
        mover.isMoving = true
        OptionsPrivate.Private.CancelAnimation(region, true, true, true, true, true)
        region:StartSizing(point)
        frame.text:ClearAllPoints()
        frame.text:SetPoint("CENTER", frame, "CENTER", 0, -15)
        frame.text:Show()
        mover:ClearAllPoints()
        mover:SetAllPoints(region)
        frame:SetScript("OnUpdate", function()
          frame.text:SetText(("(%.2f, %.2f)"):format(region:GetWidth(), region:GetHeight()))
          if data.width and data.height then
            frame:SizingSetData(data, region:GetWidth(), region:GetHeight(), 0, 0, scale)
          end
          region:ResetPosition()
          WeakAuras.Add(data, true)
          frame:ScaleCorners(region:GetWidth(), region:GetHeight())
          WeakAuras.FillOptions()
        end)

        AlignmentLines:CreateLines(mover.moving.data, point)
        AlignmentLines:Show()
        HighlightFrame:Show()
        mover.sizePoint = point
      end

      frame.doneSizing = function()
        if not mover.sizePoint then
          return
        end
        mover.isMoving = false
        region:StopMovingOrSizing()

        AlignmentLines:CleanUpLines()
        AlignmentLines:Hide()
        HighlightFrame:Hide()

        local width = region:GetWidth()
        local height = region:GetHeight()

        local align = (WeakAurasOptionsSaved.magnetAlign and not IsShiftKeyDown())
                      or (not WeakAurasOptionsSaved.magnetAlign and IsShiftKeyDown())

        local deltaX = 0
        local deltaY = 0
        if align then
          deltaX = mover.verticalDelta or 0
          deltaY = mover.horizontalDelta or 0
        end

        frame:SizingSetData(data, width, height, deltaX, deltaY, scale)

        region:ResetPosition()
        WeakAuras.Add(data, true)
        OptionsPrivate.Private.AddParents(data)
        WeakAuras.UpdateThumbnail(data)

        frame:ScaleCorners(region:GetWidth(), region:GetHeight())
        local xOff, yOff
        mover.selfPoint, mover.anchor, mover.anchorPoint, xOff, yOff = region:GetPoint(1)
        xOff = xOff or 0
        yOff = yOff or 0
        mover:ClearAllPoints()
        if data.regionType == "group" then
          mover:SetWidth((region.trx - region.blx) * scale)
          mover:SetHeight((region.try - region.bly) * scale)
          mover:SetPoint("BOTTOMLEFT", mover.anchor, mover.anchorPoint,
                         (xOff + region.blx) * scale,
                         (yOff + region.bly) * scale)
        else
          mover:SetWidth(region:GetWidth() * scale)
          mover:SetHeight(region:GetHeight() * scale)
          mover:SetPoint(mover.selfPoint, mover.anchor, mover.anchorPoint, xOff * scale, yOff * scale)
        end
        frame.text:Hide()
        frame:SetScript("OnUpdate", nil)
        WeakAuras.FillOptions()
        OptionsPrivate.Private.Animate("display", data.uid, "main", data.animation.main,
                                       OptionsPrivate.Private.EnsureRegion(data.id), false, nil, true)
        mover.sizePoint = nil
      end

      frame.bottomleft:SetScript("OnMouseDown", function() frame.startSizing("BOTTOMLEFT") end)
      frame.bottomleft:SetScript("OnMouseUp", function() frame.doneSizing("BOTTOMLEFT") end)
      frame.bottomleft:SetScript("OnEnter", frame.bottomleft.Highlight)
      frame.bottomleft:SetScript("OnLeave", frame.bottomleft.Clear)
      frame.bottom:SetScript("OnMouseDown", function() frame.startSizing("BOTTOM") end)
      frame.bottom:SetScript("OnMouseUp", function() frame.doneSizing("BOTTOM") end)
      frame.bottom:SetScript("OnEnter", frame.bottom.Highlight)
      frame.bottom:SetScript("OnLeave", frame.bottom.Clear)
      frame.bottomright:SetScript("OnMouseDown", function() frame.startSizing("BOTTOMRIGHT") end)
      frame.bottomright:SetScript("OnMouseUp", function() frame.doneSizing("BOTTOMRIGHT") end)
      frame.bottomright:SetScript("OnEnter", frame.bottomright.Highlight)
      frame.bottomright:SetScript("OnLeave", frame.bottomright.Clear)
      frame.right:SetScript("OnMouseDown", function() frame.startSizing("RIGHT") end)
      frame.right:SetScript("OnMouseUp", function() frame.doneSizing("RIGHT") end)
      frame.right:SetScript("OnEnter", frame.right.Highlight)
      frame.right:SetScript("OnLeave", frame.right.Clear)
      frame.topright:SetScript("OnMouseDown", function() frame.startSizing("TOPRIGHT") end)
      frame.topright:SetScript("OnMouseUp", function() frame.doneSizing("TOPRIGHT") end)
      frame.topright:SetScript("OnEnter", frame.topright.Highlight)
      frame.topright:SetScript("OnLeave", frame.topright.Clear)
      frame.top:SetScript("OnMouseDown", function() frame.startSizing("TOP") end)
      frame.top:SetScript("OnMouseUp", function() frame.doneSizing("TOP") end)
      frame.top:SetScript("OnEnter", frame.top.Highlight)
      frame.top:SetScript("OnLeave", frame.top.Clear)
      frame.topleft:SetScript("OnMouseDown", function() frame.startSizing("TOPLEFT") end)
      frame.topleft:SetScript("OnMouseUp", function() frame.doneSizing("TOPLEFT") end)
      frame.topleft:SetScript("OnEnter", frame.topleft.Highlight)
      frame.topleft:SetScript("OnLeave", frame.topleft.Clear)
      frame.left:SetScript("OnMouseDown", function() frame.startSizing("LEFT") end)
      frame.left:SetScript("OnMouseUp", function() frame.doneSizing("LEFT") end)
      frame.left:SetScript("OnEnter", frame.left.Highlight)
      frame.left:SetScript("OnLeave", frame.left.Clear)

      frame.bottomleft:Show()
      frame.bottom:Show()
      frame.bottomright:Show()
      frame.right:Show()
      frame.topright:Show()
      frame.top:Show()
      frame.topleft:Show()
      frame.left:Show()
    else
      frame.bottomleft:Hide()
      frame.bottom:Hide()
      frame.bottomright:Hide()
      frame.right:Hide()
      frame.topright:Hide()
      frame.top:Hide()
      frame.topleft:Hide()
      frame.left:Hide()
    end
    frame:Show()
  end

  mover.onUpdate = function(self, elaps)
    if not IsShiftKeyDown() then
      self.goalAlpha = 1
    else
      self.goalAlpha = 0.1
    end

    if self.currentAlpha ~= self.goalAlpha then
      self.currentAlpha = self.currentAlpha or self:GetAlpha()
      local newAlpha = (self.currentAlpha < self.goalAlpha) and self.currentAlpha + (elaps * 4) or self.currentAlpha - (elaps * 4)
      newAlpha = (newAlpha > 1 and 1) or (newAlpha < 0.1 and 0.1) or newAlpha
      mover:SetAlpha(newAlpha)
      frame:SetAlpha(newAlpha)
      self.currentAlpha = newAlpha
    end

    local db = OptionsPrivate.savedVars.db
    local region = self.moving.region
    local data = self.moving.data
    if not self.isMoving then
      local ok, selfPoint, anchor, anchorPoint = pcall(region.GetPoint, region, 1)
      if not ok then
        self:Hide()
        return
      end
      self.selfPoint, self.anchor, self.anchorPoint = selfPoint, anchor, anchorPoint
    end
    self.selfPointIcon:ClearAllPoints()
    self.selfPointIcon:SetPoint("CENTER", region, self.selfPoint)
    local selfX, selfY = self.selfPointIcon:GetCenter()
    selfX, selfY = selfX or 0, selfY or 0
    self.anchorPointIcon:ClearAllPoints()
    self.anchorPointIcon:SetPoint("CENTER", self.anchor, self.anchorPoint)
    local anchorX, anchorY = self.anchorPointIcon:GetCenter()
    anchorX, anchorY = anchorX or 0, anchorY or 0
    if data.parent and db.displays[data.parent] and db.displays[data.parent].regionType == "dynamicgroup" then
      self.selfPointIcon:Hide()
      self.anchorPointIcon:Hide()
    else
      self.selfPointIcon:Show()
      self.anchorPointIcon:Show()
    end

    local dX = selfX - anchorX
    local dY = selfY - anchorY
    local distance = sqrt(dX^2 + dY^2)
    local angle = atan2(dY, dX)

    local numInterim = floor(distance/40)

    for _, texture in pairs(self.interims) do
      texture:Hide()
    end
    for i = 1, numInterim  do
      local x = (distance - (i * 40)) * cos(angle)
      local y = (distance - (i * 40)) * sin(angle)
      self.interims[i] = EnsureTexture(self, self.interims[i])
      self.interims[i]:ClearAllPoints()
      self.interims[i]:SetPoint("CENTER", self.anchorPointIcon, "CENTER", x, y)
      self.interims[i]:Show()
    end

    frame.arrowTexture:Hide()
    frame.offscreenText:Hide()

    -- Check if the center is offscreen
    -- How many pixels of the aura need to be visible
    local margin = 30
    local x, y = mover:GetCenter()
    if x and y then
      if mover:GetRight() < margin or mover:GetLeft() + margin > GetScreenWidth() or mover:GetTop() < 20 or mover:GetBottom() + margin > GetScreenHeight() then
        local arrowX, arrowY = frame.arrowTexture:GetCenter()
        local arrowAngle = atan2(y - arrowY, x - arrowX)
        frame.offscreenText:Show()
        frame.arrowTexture:Show()
        frame.arrowTexture:SetRotation( (arrowAngle - 90) / 180 * math.pi)
      end
    end

    local regionScale = self.moving.region:GetScale()
    self.text:SetText(("(%.2f, %.2f)"):format(dX*1/regionScale, dY*1/regionScale))
    local midX = (distance / 2) * cos(angle)
    local midY = (distance / 2) * sin(angle)
    self.text:SetPoint("CENTER", self.anchorPointIcon, "CENTER", midX, midY)
    local left, right, top, bottom = frame:GetLeft(), frame:GetRight(), frame:GetTop(), frame:GetBottom()
    if (midX > 0 and (self.text:GetRight() or 0) > (left or 0))
    or (midX < 0 and (self.text:GetLeft() or 0) < (right or 0))
    then
      if midY > 0 and (self.text:GetTop() or 0) > (top or 0) then
        midY = midY - ((self.text:GetTop() or 0) - (bottom or 0))
      elseif midY < 0 and (self.text:GetBottom() or 0) < (top or 0) then
        midY = midY + ((top or 0) - (self.text:GetBottom() or 0))
      end
    end
    self.text:SetPoint("CENTER", self.anchorPointIcon, "CENTER", midX, midY)
    if self.isMoving then
      AlignmentLines:ShowLinesFor(IsControlKeyDown(), region, mover.sizePoint)
    end
  end

  frame.OptionsOpened = function()
    mover:Show()
    mover:RegisterEvent("MODIFIER_STATE_CHANGED")
    mover:SetScript("OnUpdate", mover.onUpdate)
    AlignmentLines:Show()
    HighlightFrame:Show()
  end

  frame.OptionsClosed = function()
    if frame.doneSizing then
      frame.doneSizing()
    end
    if mover.doneMoving then
      mover.doneMoving()
    end
    mover:UnregisterEvent("MODIFIER_STATE_CHANGED")
    mover:SetScript("OnUpdate", nil)
    mover:Hide()
    AlignmentLines:Hide()
    HighlightFrame:Hide()
  end

  return frame, mover
end

function OptionsPrivate.MoverSizer(parent)
  if not moversizer or not mover then
    moversizer, mover = ConstructMoverSizer(parent)
  end
  return moversizer, mover
end
