-------------------------------------------------------------------------------
-- CircleProgress.lua
-- Circular progress ring using vertex offset + tex coord manipulation.
-------------------------------------------------------------------------------
local _, ns = ...

ns.CircleProgress = {}

local floor = math.floor
local tan = math.tan
local rad = math.rad

-- Default texture coordinates for a full quad
local defaultTexCoord = {
    ULx = 0, ULy = 0,
    LLx = 0, LLy = 1,
    URx = 1, URy = 0,
    LRx = 1, LRy = 1,
}

-- Exact coordinates for 45-degree increments (starting from top, clockwise)
local exactAngles = {
    { 0.5, 0 },  -- 0°
    { 1,   0 },  -- 45°
    { 1, 0.5 },  -- 90°
    { 1,   1 },  -- 135°
    { 0.5, 1 },  -- 180°
    { 0,   1 },  -- 225°
    { 0, 0.5 },  -- 270°
    { 0,   0 },  -- 315°
}

local function angleToCoord(angle)
    angle = angle % 360
    if angle % 45 == 0 then
        local index = floor(angle / 45) + 1
        return exactAngles[index][1], exactAngles[index][2]
    end

    -- Convert to radians for tan()
    local r = rad(angle)
    if angle < 45 then
        return 0.5 + tan(r) / 2, 0
    elseif angle < 135 then
        return 1, 0.5 + tan(rad(angle - 90)) / 2
    elseif angle < 225 then
        return 0.5 - tan(r) / 2, 1
    elseif angle < 315 then
        return 0, 0.5 - tan(rad(angle - 90)) / 2
    else
        return 0.5 + tan(r) / 2, 0
    end
end

local pointOrder = { "LL", "UL", "UR", "LR", "LL", "UL", "UR", "LR", "LL", "UL", "UR", "LR" }

-- Precomputed field-name lookups so MoveCorner/SetAngle don't concatenate
-- corner names with "x"/"y"/"vx"/"vy" on every render frame. With up to
-- 3 SetAngle calls and 3 MoveCorner calls per SetAngle, the naive form
-- allocated ~36 throwaway strings per frame while a ring was visible.
local cornerFields = {}
local cornerDefaultX = {}
local cornerDefaultY = {}
for _, corner in ipairs({ "UL", "LL", "UR", "LR" }) do
    cornerFields[corner] = {
        x  = corner .. "x",
        y  = corner .. "y",
        vx = corner .. "vx",
        vy = corner .. "vy",
    }
    cornerDefaultX[corner] = defaultTexCoord[corner .. "x"]
    cornerDefaultY[corner] = defaultTexCoord[corner .. "y"]
end

-------------------------------------------------------------------------------
-- TextureCoord object — manages one quad's tex coords + vertex offsets
-------------------------------------------------------------------------------
local coordMethods = {}
local coordMT = { __index = coordMethods }

function coordMethods:MoveCorner(width, height, corner, x, y)
    local f = cornerFields[corner]
    local rx = cornerDefaultX[corner] - x
    local ry = cornerDefaultY[corner] - y
    self[f.vx] = -rx * width
    self[f.vy] = ry * height
    self[f.x] = x
    self[f.y] = y
end

function coordMethods:SetFull()
    self.ULx = 0;  self.ULy = 0
    self.LLx = 0;  self.LLy = 1
    self.URx = 1;  self.URy = 0
    self.LRx = 1;  self.LRy = 1
    self.ULvx = 0; self.ULvy = 0
    self.LLvx = 0; self.LLvy = 0
    self.URvx = 0; self.URvy = 0
    self.LRvx = 0; self.LRvy = 0
end

function coordMethods:Apply()
    self.texture:SetVertexOffset(UPPER_RIGHT_VERTEX, self.URvx, self.URvy)
    self.texture:SetVertexOffset(UPPER_LEFT_VERTEX, self.ULvx, self.ULvy)
    self.texture:SetVertexOffset(LOWER_RIGHT_VERTEX, self.LRvx, self.LRvy)
    self.texture:SetVertexOffset(LOWER_LEFT_VERTEX, self.LLvx, self.LLvy)
    self.texture:SetTexCoord(self.ULx, self.ULy, self.LLx, self.LLy, self.URx, self.URy, self.LRx, self.LRy)
end

function coordMethods:ShowCoord()
    self:Apply()
    self.texture:Show()
end

function coordMethods:HideCoord()
    self.texture:Hide()
end

function coordMethods:SetAngle(width, height, angle1, angle2)
    local index = floor((angle1 + 45) / 90)

    local middleCorner = pointOrder[index + 1]
    local startCorner  = pointOrder[index + 2]
    local endCorner1   = pointOrder[index + 3]
    local endCorner2   = pointOrder[index + 4]

    self:MoveCorner(width, height, middleCorner, 0.5, 0.5)
    self:MoveCorner(width, height, startCorner, angleToCoord(angle1))

    local edge1 = floor((angle1 - 45) / 90)
    local edge2 = floor((angle2 - 45) / 90)

    if edge1 == edge2 then
        self:MoveCorner(width, height, endCorner1, angleToCoord(angle2))
    else
        self:MoveCorner(width, height, endCorner1,
            cornerDefaultX[endCorner1], cornerDefaultY[endCorner1])
    end

    self:MoveCorner(width, height, endCorner2, angleToCoord(angle2))
end

local function CreateCoord(texture)
    return setmetatable({
        ULx = 0, ULy = 0, LLx = 0, LLy = 1,
        URx = 1, URy = 0, LRx = 1, LRy = 1,
        ULvx = 0, ULvy = 0, LLvx = 0, LLvy = 0,
        URvx = 0, URvy = 0, LRvx = 0, LRvy = 0,
        texture = texture,
    }, coordMT)
end

-------------------------------------------------------------------------------
-- CircleProgress object — the public API
-------------------------------------------------------------------------------
local ringMethods = {}
local ringMT = { __index = ringMethods }

function ringMethods:SetTexturePath(path)
    for i = 1, 3 do
        self.textures[i]:SetTexture(path, "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
    end
end

function ringMethods:SetColor(r, g, b, a)
    for i = 1, 3 do
        self.textures[i]:SetVertexColor(r, g, b, a)
    end
end

function ringMethods:SetRingSize(size)
    self.ringWidth = size
    self.ringHeight = size
    self.parentFrame:SetSize(size, size)
end

--- Set progress from 0.0 (empty) to 1.0 (full ring).
--- Ring depletes clockwise from top.
function ringMethods:SetProgress(progress)
    self._lastProgress = progress

    local angle1 = (1 - progress) * 360
    local angle2 = 360

    local width = self.ringWidth
    local height = self.ringHeight

    if progress >= 1 then
        self.coords[1]:SetFull()
        self.coords[1]:ShowCoord()
        self.coords[2]:HideCoord()
        self.coords[3]:HideCoord()
        return
    end

    if progress <= 0 then
        self.coords[1]:HideCoord()
        self.coords[2]:HideCoord()
        self.coords[3]:HideCoord()
        return
    end

    local index1 = floor((angle1 + 45) / 90)
    local index2 = floor((angle2 + 45) / 90)

    if index1 + 1 >= index2 then
        self.coords[1]:SetAngle(width, height, angle1, angle2)
        self.coords[1]:ShowCoord()
        self.coords[2]:HideCoord()
        self.coords[3]:HideCoord()
    elseif index1 + 3 >= index2 then
        local firstEnd = (index1 + 1) * 90 + 45
        self.coords[1]:SetAngle(width, height, angle1, firstEnd)
        self.coords[1]:ShowCoord()
        self.coords[2]:SetAngle(width, height, firstEnd, angle2)
        self.coords[2]:ShowCoord()
        self.coords[3]:HideCoord()
    else
        local firstEnd = (index1 + 1) * 90 + 45
        local secondEnd = firstEnd + 180
        self.coords[1]:SetAngle(width, height, angle1, firstEnd)
        self.coords[1]:ShowCoord()
        self.coords[2]:SetAngle(width, height, firstEnd, secondEnd)
        self.coords[2]:ShowCoord()
        self.coords[3]:SetAngle(width, height, secondEnd, angle2)
        self.coords[3]:ShowCoord()
    end
end

function ringMethods:Show()
    self.parentFrame:Show()
    self:SetProgress(self._lastProgress or 1)
end

function ringMethods:Hide()
    self.parentFrame:Hide()
    for i = 1, 3 do
        self.textures[i]:Hide()
    end
end

-------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------
--- Create a new circular progress ring.
--- @param parent Frame The parent frame to attach to (the ring sizes itself)
--- @return table ring object with :SetTexturePath, :SetColor, :SetRingSize, :SetProgress, :Show, :Hide
function ns.CircleProgress.Create(parent, drawLayer)
    local layer = drawLayer or "ARTWORK"
    local ring = setmetatable({
        parentFrame = parent,
        textures = {},
        coords = {},
        ringWidth = 0,
        ringHeight = 0,
        _lastProgress = 1,
    }, ringMT)

    for i = 1, 3 do
        local tex = parent:CreateTexture(nil, layer)
        tex:SetSnapToPixelGrid(false)
        tex:SetTexelSnappingBias(0)
        tex:SetAllPoints(parent)
        ring.textures[i] = tex
        ring.coords[i] = CreateCoord(tex)
    end

    return ring
end
