-------------------------------------------------------------------------------
-- PreviewGrid.lua
--
-- A full-screen alignment grid, shown whenever ANY tracker element is
-- previewing and db.global.showGridWhenPreviewing is not false. Same idea and
-- the same look as Buzzard Frames' setup-mode grid: 50-unit spacing measured
-- OUTWARD FROM THE CENTRE, grey lines, a gold centre crosshair.
--
-- Measuring outward from the centre rather than from a corner is the whole
-- point of the thing. Every element position is stored as an offset against
-- UIParent and "Center Horizontal"/"Center Vertical" both resolve to the screen
-- centre, so a grid laid out from the bottom-left corner would put its lines on
-- values that mean nothing to anything the user can actually set. Centre-out
-- means the gold cross IS the zero offset and every grey line is a round number
-- away from it.
--
-- BACKGROUND strata, mouse disabled: this sits under every tracker and under
-- the folder bounding box (FolderMover.lua), and can never take a click from
-- either.
--
-- DRIVEN FROM THE PREVIEW METHODS, not from the options UI. Preview state is
-- changed by ShowElementPreview / HideElementPreview / HidePreview in
-- Tracker.lua, and those are reached from the row eyes, the node eyes, the
-- global eye, right-click navigation, spell disable, panel close and the
-- leaving-combat catch-up. Hooking the three methods covers all of it; hooking
-- the UI would have meant chasing each entry point and missing the ones that
-- are not UI at all.
-------------------------------------------------------------------------------
local addonName, ns = ...
local HET = ns.addon

local MAX_ELEMENTS = ns.MAX_ELEMENTS or 6

local GRID_STEP  = 50                    -- virtual units between lines
local LINE_COLOR = { 0.6, 0.6, 0.6 }     -- grey grid
local LINE_ALPHA = 0.25
local CTR_COLOR  = { 0.9, 0.7, 0.2 }     -- gold centre crosshair
local CTR_ALPHA  = 0.55

-------------------------------------------------------------------------------
-- Is anything previewing?
-------------------------------------------------------------------------------
-- Scanned rather than counted. A counter would have to be incremented and
-- decremented at every one of the paths listed in the header, and a single
-- missed decrement leaves the grid stuck on screen with no way back — whereas a
-- scan cannot drift out of step with the thing it measures. It runs on preview
-- toggles only (coalesced to one per frame below), never per update.
function ns.AnyPreviewActive()
    local trackers = ns.trackers
    if type(trackers) ~= "table" then return false end
    for _, t in pairs(trackers) do
        if type(t) == "table" and t.IsElementPreviewActive then
            for i = 1, MAX_ELEMENTS do
                if t:IsElementPreviewActive(i) then return true end
            end
        end
    end
    return false
end

-------------------------------------------------------------------------------
-- The grid
-------------------------------------------------------------------------------
local grid

local function BuildLines(root)
    for i = 1, #root.lines do root.lines[i]:Hide() end
    wipe(root.lines)

    local W, H = UIParent:GetWidth(), UIParent:GetHeight()
    if not W or not H or W <= 0 or H <= 0 then return end
    local cx, cy = W / 2, H / 2

    local function Line(r, g, b, a)
        local t = root:CreateTexture(nil, "BACKGROUND", nil, 2)
        t:SetColorTexture(r, g, b, a)
        root.lines[#root.lines + 1] = t
        return t
    end
    -- The half-pixel either side of the coordinate is what keeps a line 1px
    -- wide and centred ON the value rather than 1px to one side of it.
    local function VLine(x, r, g, b, a)
        local t = Line(r, g, b, a)
        t:SetPoint("TOPLEFT",     root, "BOTTOMLEFT", x - 0.5, H)
        t:SetPoint("BOTTOMRIGHT", root, "BOTTOMLEFT", x + 0.5, 0)
    end
    local function HLine(y, r, g, b, a)
        local t = Line(r, g, b, a)
        t:SetPoint("TOPLEFT",     root, "BOTTOMLEFT", 0, y + 0.5)
        t:SetPoint("BOTTOMRIGHT", root, "BOTTOMLEFT", W, y - 0.5)
    end

    VLine(cx, CTR_COLOR[1], CTR_COLOR[2], CTR_COLOR[3], CTR_ALPHA)
    HLine(cy, CTR_COLOR[1], CTR_COLOR[2], CTR_COLOR[3], CTR_ALPHA)

    -- Stepping outward in BOTH directions at once, stopping when neither side
    -- is still on screen. Walking left-to-right instead would put the step
    -- boundary wherever the loop happened to start and leave the two halves
    -- disagreeing about where the lines fall on an odd screen width.
    local x = GRID_STEP
    while true do
        local drew = false
        if cx - x > 0 then VLine(cx - x, LINE_COLOR[1], LINE_COLOR[2], LINE_COLOR[3], LINE_ALPHA) drew = true end
        if cx + x < W then VLine(cx + x, LINE_COLOR[1], LINE_COLOR[2], LINE_COLOR[3], LINE_ALPHA) drew = true end
        if not drew then break end
        x = x + GRID_STEP
    end
    local y = GRID_STEP
    while true do
        local drew = false
        if cy - y > 0 then HLine(cy - y, LINE_COLOR[1], LINE_COLOR[2], LINE_COLOR[3], LINE_ALPHA) drew = true end
        if cy + y < H then HLine(cy + y, LINE_COLOR[1], LINE_COLOR[2], LINE_COLOR[3], LINE_ALPHA) drew = true end
        if not drew then break end
        y = y + GRID_STEP
    end
end

local function EnsureGrid()
    if grid then return grid end
    grid = CreateFrame("Frame", "BuzzardAurasPreviewGrid", UIParent)
    grid:SetAllPoints(UIParent)
    grid:SetFrameStrata("BACKGROUND")
    grid:SetFrameLevel(1)
    grid:EnableMouse(false)
    grid:Hide()
    grid.lines = {}
    BuildLines(grid)
    -- Resolution and UI-scale changes both land here, and the line positions
    -- are baked as offsets, so they have to be rebuilt rather than rescaled.
    grid:HookScript("OnSizeChanged", function(self) BuildLines(self) end)
    return grid
end

-- Default-on, and read as "not false" rather than as a plain truth test: that
-- way the feature is on for every existing profile the moment this ships,
-- without depending on a defaults merge having reached db.global first.
function ns.GridEnabled()
    local g = HET and HET.db and HET.db.global
    if not g then return true end
    return g.showGridWhenPreviewing ~= false
end

local function Apply()
    if not ns.GridEnabled() or not ns.AnyPreviewActive() then
        if grid then grid:Hide() end
        return
    end
    EnsureGrid():Show()
end

-- Coalesced to one evaluation per frame. HideAllPreviews walks every tracker
-- calling HidePreview on each, so an immediate scan per call would be O(N^2)
-- over the tracker list for a result that only matters once, after the last
-- one.
local queued = false
function ns.UpdatePreviewGrid()
    if queued then return end
    queued = true
    C_Timer.After(0, function()
        queued = false
        Apply()
    end)
end

-- Immediate variant for the options toggle, where the user is looking straight
-- at the thing and a frame of lag reads as the click not registering.
function ns.ApplyPreviewGrid()
    Apply()
end
