local ADDON_NAME, ns = ...

-- Unify namespace table across all files
ns = _G[ADDON_NAME] or ns
_G[ADDON_NAME] = ns

-- Make the addon table globally reachable (mixins/config uses _G.PlateOMatic)
_G.PlateOMatic = ns

ns.util = ns.util or {}
local U = ns.util

-- =========================================================
-- Defaults helper (deep, only fills missing keys)
-- =========================================================
function U.CopyDefaults(src, dst)
    if type(dst) ~= "table" then dst = {} end
    if type(src) ~= "table" then return dst end

    for k, v in pairs(src) do
        if type(v) == "table" then
            dst[k] = U.CopyDefaults(v, dst[k])
        elseif dst[k] == nil then
            dst[k] = v
        end
    end

    return dst
end

function U.IsNameplateUnit(unit)
    return unit and type(unit) == "string" and unit:find("nameplate", 1, true)
end

function U.IsSafeUnit(unit)
    return unit and UnitCanAttack("player", unit)
end

-- 12.1: unit identity comes back as a SECRET on restricted nameplates. A
-- secret can be stored and passed along, but never compared or tested -- the
-- read itself succeeds and the `==` two lines later is what raises, which is
-- why a pcall around the API call catches nothing.
--
-- These all answer nil for "cannot know", so callers fall back to their
-- default rather than guessing.
local issecretvalue = _G.issecretvalue

local function Plain(ok, v)
    if not ok then return nil end
    if issecretvalue and issecretvalue(v) then return nil end
    return v
end

function U.PlainClass(unit)
    if not (unit and UnitClassBase) then return nil end

    local class = Plain(pcall(UnitClassBase, unit))
    if type(class) ~= "string" then return nil end

    return class
end

function U.PlainClassification(unit)
    if not (unit and UnitClassification) then return nil end

    local cls = Plain(pcall(UnitClassification, unit))
    if type(cls) ~= "string" then return nil end

    return cls
end

function U.PlainNumber(v)
    local n = Plain(true, v)
    if type(n) ~= "number" then return nil end

    return n
end

-- Tri-state on purpose: true / false / nil, where nil is "restricted". A
-- caller that folds nil into false silently treats every restricted unit as
-- a negative, which is rarely what it wants.
function U.PlainBool(ok, v)
    local b = Plain(ok, v)
    if type(b) ~= "boolean" then return nil end

    return b
end

-- Unit-token comparison is its own restricted operation in 12.1 (hence
-- C_Secrets.CanCompareUnitTokens), so UnitIsUnit can answer with a secret
-- boolean and `and true or false` on it raises.
--
-- Use this only where the answer picks OUR geometry, and read "cannot know"
-- as false. Where the answer feeds a Blizzard setter instead, pass the raw
-- return straight through -- SetAlphaFromBoolean is built to take a secret.
function U.PlainIsUnit(a, b)
    if not (a and b) then return false end

    return U.PlainBool(pcall(UnitIsUnit, a, b)) == true
end

-- 12.1: while auras are secret the UnitAura APIs hard-error on a tainted
-- path instead of returning secrets, and the raise aborts whatever handler
-- chain we are sitting in. Gate every aura scan on this.
local Secrets = C_Secrets
function U.AurasAreSecret()
    if not (Secrets and Secrets.HasSecretRestrictions and Secrets.HasSecretRestrictions()) then
        return false
    end

    return Secrets.ShouldAurasBeSecret() and true or false
end

-- Identity secrecy is decided PER UNIT, unlike auras/cooldowns/stats which
-- are global for whatever content you are standing in. That asymmetry is why
-- a screen of nameplates can render half styled and half not at the same
-- instant, and it is the reason the U.Plain* helpers exist at all.
--
-- Note the signature: this one takes a unit and errors without one.
--
-- The Plain* helpers are still the safety net -- use them on every read.
-- This is for deciding up front that a unit's whole identity-dependent
-- styling pass is not worth attempting.
function U.UnitIdentitySecret(unit)
    if not unit then return false end
    if not (Secrets and Secrets.ShouldUnitIdentityBeSecret) then return false end

    return Plain(pcall(Secrets.ShouldUnitIdentityBeSecret, unit)) == true
end

-- Hard-hide helper (re-hides if Blizzard shows it again on target/hover updates)
local function LockHide(obj)
    if not obj then return end
    if obj.IsForbidden and obj:IsForbidden() then return end

    if obj.Hide then pcall(obj.Hide, obj) end

    if not obj.__pom_lockhide then
        if obj.Show then
            hooksecurefunc(obj, "Show", function(self)
                if self and self.Hide then pcall(self.Hide, self) end
            end)
        end
        if obj.SetShown then
            hooksecurefunc(obj, "SetShown", function(self, shown)
                if shown and self and self.Hide then pcall(self.Hide, self) end
            end)
        end
        if obj.SetAlpha then
            hooksecurefunc(obj, "SetAlpha", function(self, a)
                if a and a > 0 and self and self.SetAlpha then
                    pcall(self.SetAlpha, self, 0)
                end
            end)
            pcall(obj.SetAlpha, obj, 0)
        end

        obj.__pom_lockhide = true
    end
end

function U.LockAlpha(tex)
    if not tex then return end
    tex:SetAlpha(0)
    tex:Hide()
    if not tex.pomLocked then
        hooksecurefunc(tex, "SetAlpha", function(self, a)
            if a and a > 0 then self:SetAlpha(0) end
        end)
        tex.pomLocked = true
    end
end

function U.KillBlizzard(frame, db)
    if not frame then return end
    if frame.IsForbidden and frame:IsForbidden() then return end

    local hb = frame.healthBar or (frame.HealthBarsContainer and frame.HealthBarsContainer.healthBar)

    -- Textures / borders
    if hb and hb.border then U.LockAlpha(hb.border) end
    if frame.HealthBarsContainer and frame.HealthBarsContainer.border then
        U.LockAlpha(frame.HealthBarsContainer.border)
    end
    if frame.selectionHighlight then U.LockAlpha(frame.selectionHighlight) end

    -- Kill ALL Blizzard name/number text to avoid double text
    LockHide(frame.name)
    LockHide(frame.Name)
    LockHide(frame.level)

    if hb then
        LockHide(hb.text)
        LockHide(hb.Text)
        LockHide(hb.TextString)
        LockHide(hb.LeftText)
        LockHide(hb.RightText)
        LockHide(hb.unitNameFontString)
        LockHide(hb.UnitName)
        LockHide(hb.unitName)
        LockHide(hb.unitNameText)
    end

    if frame.UnitFrame and frame.UnitFrame.name then
        LockHide(frame.UnitFrame.name)
    end

    if db and db.hideClassIcon and frame.classificationIndicator then
        LockHide(frame.classificationIndicator)
    end
end

function U.RefreshAllPlates(applyFunc)
    if not C_NamePlate or not C_NamePlate.GetNamePlates then return end
    for _, plate in pairs(C_NamePlate.GetNamePlates()) do
        if plate and plate.UnitFrame then
            applyFunc(plate.UnitFrame)
        end
    end
end

function U.MakeBorder(frame, key)
    if frame[key] then return frame[key] end
    local b = {}
    for i = 1, 4 do
        local t = frame:CreateTexture(nil, "OVERLAY")
        t:SetColorTexture(0, 0, 0, 1)
        b[i] = t
    end
    frame[key] = b
    return b
end

function U.LayoutBorder(borders, anchor, size)
    if not borders or not anchor then return end
    local top, bottom, left, right = borders[1], borders[2], borders[3], borders[4]

    top:ClearAllPoints()
    top:SetPoint("BOTTOMLEFT", anchor, "TOPLEFT", -size, 0)
    top:SetPoint("BOTTOMRIGHT", anchor, "TOPRIGHT", size, 0)
    top:SetHeight(size)

    bottom:ClearAllPoints()
    bottom:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", -size, 0)
    bottom:SetPoint("TOPRIGHT", anchor, "BOTTOMRIGHT", size, 0)
    bottom:SetHeight(size)

    left:ClearAllPoints()
    left:SetPoint("TOPRIGHT", anchor, "TOPLEFT", 0, size)
    left:SetPoint("BOTTOMRIGHT", anchor, "BOTTOMLEFT", 0, -size)
    left:SetWidth(size)

    right:ClearAllPoints()
    right:SetPoint("TOPLEFT", anchor, "TOPRIGHT", 0, size)
    right:SetPoint("BOTTOMLEFT", anchor, "BOTTOMRIGHT", 0, -size)
    right:SetWidth(size)
end
