local ADDON_NAME, ns = ...

-- Always unify namespace (prevents nil Mixins / split-table bugs)
ns = _G[ADDON_NAME] or ns
_G[ADDON_NAME] = ns

ns.mixins = ns.mixins or {}
local Mixins = ns.mixins

local U = ns.util

-- =========================================================
-- pom_mixins.lua
-- =========================================================

-- ------------------------------------------------------------
-- Safe font apply (prevents "Font not set" + bad paths)
-- ------------------------------------------------------------
local function SafeSetFont(fs, path, size, flags)
    if not (fs and fs.SetFont) then return end
    size  = tonumber(size) or 12
    flags = flags or ""

    local ok = pcall(fs.SetFont, fs, path, size, flags)
    if not ok then
        pcall(fs.SetFont, fs, (_G.STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF"), size, flags)
    end
end

-- ------------------------------------------------------------
-- Rare detection (classification)
-- ------------------------------------------------------------
local function IsRareUnit(unit)
    if not unit or not UnitExists(unit) then return false, nil end
    local cls = UnitClassification(unit)
    if cls == "rare" or cls == "rareelite" then
        return true, cls
    end
    return false, cls
end

-- ------------------------------------------------------------
-- DB helpers (READ-ONLY)
-- IMPORTANT:
--   Do NOT write defaults here. InitDB() in pom.lua owns all defaults+migrations.
-- ------------------------------------------------------------
local function GetBaseColorsDB(db)
    if db and type(db.baseColors) == "table" then
        return db.baseColors
    end
    if db and type(db.colors) == "table" then
        return db.colors
    end
    return nil
end

local function GetThreatDB(db)
    if db and type(db.threat) == "table" then
        return db.threat
    end
    return nil
end

-- ------------------------------------------------------------
-- Constructor
-- ------------------------------------------------------------
function Mixins:New(frame, skins)
    local o = {}
    for k, v in pairs(self) do
        if k ~= "New" then o[k] = v end
    end
    o.frame = frame
    o.skins = skins or ns.SKINS
    o.healthBar = frame.healthBar or (frame.HealthBarsContainer and frame.HealthBarsContainer.healthBar)
    if not o.healthBar then return o end
    o:Init()
    return o
end

-- ------------------------------------------------------------
-- Init (one-time per nameplate)
-- ------------------------------------------------------------
function Mixins:Init()
    local hb = self.healthBar
    if not hb then return end

    if not hb.pomBG then
        local bg = hb:CreateTexture(nil, "BACKGROUND")
        bg:SetAllPoints(hb)
        bg:SetTexture("Interface\\Buttons\\WHITE8x8")
        bg:SetVertexColor(0.25, 0.25, 0.25, 0.85)
        hb.pomBG = bg
    end

    if not hb.pomBorders then
        hb.pomBorders = U.MakeBorder(hb, "pomBorders")
    end

    if not hb.pomTextFrame then
        local tf = CreateFrame("Frame", nil, hb)
        tf:SetAllPoints(hb)
        tf:SetFrameLevel(hb:GetFrameLevel() + 20)
        hb.pomTextFrame = tf

        local fsName = tf:CreateFontString(nil, "OVERLAY")
        fsName:SetPoint("LEFT", tf, "LEFT", 4, 0)
        fsName:SetPoint("RIGHT", tf, "RIGHT", -40, 0)
        fsName:SetJustifyH("LEFT")
        fsName:SetWordWrap(false)
        hb.pomName = fsName

        local fsHealth = tf:CreateFontString(nil, "OVERLAY")
        fsHealth:SetPoint("RIGHT", tf, "RIGHT", -4, 0)
        fsHealth:SetJustifyH("RIGHT")
        hb.pomHealth = fsHealth
    end

    if not hb.pomThreatText then
        local ft = hb.pomTextFrame:CreateFontString(nil, "OVERLAY")
        ft:SetPoint("TOP", hb, "BOTTOM", 0, -2)
        hb.pomThreatText = ft
    end

    if not hb.pomRareText then
        local rt = hb.pomTextFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        rt:SetPoint("BOTTOM", hb, "TOP", 0, 2)
        rt:SetJustifyH("CENTER")
        rt:SetTextColor(0.8, 0.6, 1.0)
        rt:SetText("RARE")
        rt:Hide()
        hb.pomRareText = rt
    end

    if not hb.pomQuestIcon then
        local qi = hb:CreateTexture(nil, "OVERLAY")
        qi:SetTexture("Interface\\TargetingFrame\\PortraitQuestBadge")
        qi:SetSize(20, 20)
        qi:SetPoint("RIGHT", hb, "LEFT", -5, 5)
        qi:Hide()
        hb.pomQuestIcon = qi

        local qt = hb.pomTextFrame:CreateFontString(nil, "OVERLAY")
        qt:SetPoint("TOP", qi, "BOTTOM", 0, 2)
        qt:SetTextColor(1, 0.82, 0)
        hb.pomQuestText = qt
    end

    if not hb.pomArrows then
        hb.pomArrows = {}

        local arrowL = self.frame:CreateTexture(nil, "OVERLAY")
        arrowL:SetTexture("Interface\\Buttons\\UI-MicroStream-Yellow")
        arrowL:SetSize(24, 24)
        arrowL:Hide()
        hb.pomArrows.left = arrowL

        local arrowR = self.frame:CreateTexture(nil, "OVERLAY")
        arrowR:SetTexture("Interface\\Buttons\\UI-MicroStream-Yellow")
        arrowR:SetSize(24, 24)
        arrowR:SetTexCoord(1, 0, 0, 1)
        arrowR:Hide()
        hb.pomArrows.right = arrowR
    end

    if not hb.pomWarningGlow then
        local glow = hb:CreateTexture(nil, "OVERLAY", nil, 6)
        glow:SetAllPoints(hb)
        glow:SetTexture("Interface\\Buttons\\WHITE8x8")
        glow:SetVertexColor(1, 1, 0, 0.6)
        glow:SetBlendMode("ADD")
        glow:Hide()
        hb.pomWarningGlow = glow

        local ag = glow:CreateAnimationGroup()
        ag:SetLooping("BOUNCE")
        local a1 = ag:CreateAnimation("Alpha")
        a1:SetFromAlpha(0)
        a1:SetToAlpha(1)
        a1:SetDuration(0.4)
        a1:SetSmoothing("IN_OUT")
        hb.pomWarningAnim = ag
    end

    if not hb.pomTargetBorder then
        hb.pomTargetBorder = U.MakeBorder(hb, "pomTargetBorder")
        for i = 1, 4 do hb.pomTargetBorder[i]:Hide() end
    end
end

-- ------------------------------------------------------------
-- Visuals (fonts / name anchor)
-- ------------------------------------------------------------
function Mixins:UpdateVisuals()
    local hb = self.healthBar
    if not hb then return end
    local db = PlateOMaticDB or {}

    local tf = hb.pomTextFrame
    local fsName = hb.pomName
    if tf and fsName then
        fsName:ClearAllPoints()
        local pos = db.nameAnchor or "LEFT"

        if pos == "TOP" then
            fsName:SetPoint("BOTTOM", tf, "TOP", 0, 2)
            fsName:SetJustifyH("CENTER")
            fsName:SetPoint("LEFT", tf, "LEFT", -50, 0)
            fsName:SetPoint("RIGHT", tf, "RIGHT", 50, 0)
        elseif pos == "CENTER" then
            fsName:SetPoint("CENTER", tf, "CENTER", 0, 1)
            fsName:SetPoint("LEFT", tf, "LEFT", 4, 0)
            fsName:SetPoint("RIGHT", tf, "RIGHT", -40, 0)
            fsName:SetJustifyH("CENTER")
        else
            fsName:SetPoint("LEFT", tf, "LEFT", 4, 0)
            fsName:SetPoint("RIGHT", tf, "RIGHT", -40, 0)
            fsName:SetJustifyH("LEFT")
        end
    end

    if hb.pomName then
        SafeSetFont(hb.pomName, db.fontPath, db.fontSize, db.fontOutline)
        hb.pomName:SetTextColor(1, 1, 1)
        hb.pomName:Show()
    end

    if hb.pomHealth then
        SafeSetFont(hb.pomHealth, db.fontPath, db.fontSize, db.fontOutline)
        hb.pomHealth:SetTextColor(1, 1, 1)
        hb.pomHealth:Show()
    end

    if hb.pomThreatText then
        SafeSetFont(hb.pomThreatText, db.fontPath, (tonumber(db.fontSize) or 10) - 1, db.fontOutline)
    end

    if hb.pomRareText then
        SafeSetFont(hb.pomRareText, db.fontPath, (tonumber(db.fontSize) or 10) - 2, db.fontOutline)
    end

    if hb.pomQuestText then
        SafeSetFont(hb.pomQuestText, db.fontPath, (tonumber(db.fontSize) or 10) - 2, db.fontOutline)
    end
end

-- ------------------------------------------------------------
-- Heights (ONLY calls heights module; safe to run always)
-- ------------------------------------------------------------
function Mixins:UpdateHeight()
    local hb = self.healthBar
    if not hb then return end

    if ns and ns.heights and ns.heights.ApplyForUnit then
        ns.heights:ApplyForUnit(hb, self.frame.unit)
    end
end

-- ------------------------------------------------------------
-- Quest logic (tooltip scan)
-- ------------------------------------------------------------
function Mixins:UpdateQuest()
    local hb = self.healthBar
    if not hb or not hb.pomQuestIcon then return end

    local db = PlateOMaticDB or {}
    local showIcon = db.showQuestIcon
    local showProg = db.showQuestProg

    if not showIcon then
        hb.pomQuestIcon:Hide()
        if hb.pomQuestText then hb.pomQuestText:Hide() end
        return
    end

    local unit = self.frame.unit
    local isQuest = false
    local progressText = ""

    if C_TooltipInfo and C_TooltipInfo.GetUnit then
        local data = C_TooltipInfo.GetUnit(unit)
        if data then
            for _, line in ipairs(data.lines) do
                if line.type == 17 or line.type == 18 then
                    isQuest = true
                end
                if showProg and line.leftText and type(line.leftText) == "string" then
                    local ok, current, total = pcall(string.match, line.leftText, "(%d+)/(%d+)")
                    if ok and current and total then
                        progressText = current .. "/" .. total
                    else
                        local ok2, perc = pcall(string.match, line.leftText, "(%d+%%)")
                        if ok2 and perc then progressText = perc end
                    end
                end
            end
        end
    end

    if isQuest then
        hb.pomQuestIcon:Show()
        if showProg and progressText ~= "" then
            hb.pomQuestText:SetText(progressText)
            hb.pomQuestText:Show()
        else
            hb.pomQuestText:Hide()
        end
    else
        hb.pomQuestIcon:Hide()
        hb.pomQuestText:Hide()
    end
end

-- ------------------------------------------------------------
-- Name + HP text
-- ------------------------------------------------------------
function Mixins:UpdateName()
    local hb = self.healthBar
    if not hb or not hb.pomName then return end
    hb.pomName:SetText(UnitName(self.frame.unit) or "")
end

function Mixins:UpdateHealth()
    local hb = self.healthBar
    if not hb or not hb.pomHealth then return end
    local unit = self.frame.unit

    if not U.IsSafeUnit(unit) then
        hb.pomHealth:SetText("")
        return
    end

    if UnitIsDeadOrGhost(unit) then
        hb.pomHealth:SetText("0%")
        return
    end

    local percentValue
    if UnitHealthPercent then
        local scaling = (CurveConstants and CurveConstants.ScaleTo100) or 1
        percentValue = UnitHealthPercent(unit, true, scaling)
    else
        local maxH = UnitHealthMax(unit) or 1
        local curH = UnitHealth(unit, true) or 0
        percentValue = (curH / maxH) * 100
    end

    hb.pomHealth:SetText(string.format("%d%%", percentValue))
end

-- =========================================================
-- UpdateState (skins + base colors + threat + target visuals)
-- =========================================================
function Mixins:UpdateState()
    local hb = self.healthBar
    if not hb then return end

    local db = PlateOMaticDB or {}
    local unit = self.frame.unit

    -- RARE label
    if hb.pomRareText then
        local isRare, cls = IsRareUnit(unit)
        if isRare then
            hb.pomRareText:SetText((cls == "rareelite") and "RARE+" or "RARE")
            hb.pomRareText:Show()
        else
            hb.pomRareText:Hide()
        end
    end

    -- Scale + target visuals
    local currentScale = db.globalScale or 1.0
    local isTarget = UnitIsUnit("target", unit)

    if isTarget then
        currentScale = currentScale * (db.targetScale or 1.15)

        if hb.pomArrows then
            if db.showTargetArrow then
                local arrL, arrR = hb.pomArrows.left, hb.pomArrows.right
                arrL:ClearAllPoints()
                arrR:ClearAllPoints()

                if db.arrowPointingOut then
                    arrL:SetPoint("RIGHT", hb, "LEFT", -4, 0); arrL:SetRotation(1.57)
                    arrR:SetPoint("LEFT", hb, "RIGHT", 4, 0);  arrR:SetRotation(-1.57)
                else
                    arrL:SetPoint("RIGHT", hb, "LEFT", -4, 0); arrL:SetRotation(-1.57)
                    arrR:SetPoint("LEFT", hb, "RIGHT", 4, 0);  arrR:SetRotation(1.57)
                end

                arrL:Show()
                arrR:Show()
            else
                hb.pomArrows.left:Hide()
                hb.pomArrows.right:Hide()
            end
        end
    else
        if hb.pomArrows then
            hb.pomArrows.left:Hide()
            hb.pomArrows.right:Hide()
        end
    end

    self.frame:SetScale(currentScale)

    -- TypeKey for SKIN + BASE COLOR bucket
    local lvl = UnitLevel(unit)
    local pl  = UnitLevel("player")
    local cls = UnitClassification(unit)

    local typeKey = "normal"
    if lvl == -1 or (lvl and pl and lvl >= pl + 2) then
        typeKey = "highlevel"
    elseif lvl and pl and lvl > pl then
        typeKey = "plusOne"
    elseif cls == "elite" then
        if UnitPowerType(unit) == 0 then typeKey = "caster" else typeKey = "melee" end
    end

    -- Skin texture
    local skins = (type(self.skins) == "table") and self.skins or ((type(ns.SKINS) == "table") and ns.SKINS or nil)
    local skinID = (db.skins and db.skins[typeKey]) or 23
    local skin = (skins and (skins[skinID] or skins[23])) or nil

    if skin and skin.texture then
        hb:SetStatusBarTexture(skin.texture)
    else
        hb:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
    end

    -- Base color
    local bdb = GetBaseColorsDB(db)
    local cBase = (bdb and bdb[typeKey]) or { r=0.8, g=0.8, b=0.8 }
    local finalR, finalG, finalB = cBase.r or 0.8, cBase.g or 0.8, cBase.b or 0.8

    if skin and skin.ignoreColor then
        finalR, finalG, finalB = 1, 1, 1
    end

    -- Neutral lock (reaction==4 and not in combat)
    local neutralLock = false
    if not (skin and skin.ignoreColor) and bdb and bdb.neutral then
        local reaction = tonumber(UnitReaction(unit, "player") or 0) or 0
        if reaction == 4 and not UnitIsPlayer(unit) then
            local nc = bdb.neutral
            finalR = nc.r or finalR
            finalG = nc.g or finalG
            finalB = nc.b or finalB
            neutralLock = not UnitAffectingCombat(unit)
        end
    end

    -- PvP: if active AND disableThreatInPvP => never run threat here.
    local pvpNoThreat = false
    if ns and ns.pvp and ns.pvp.IsActive and ns.pvp:IsActive() then
        local pdb = db.pvp
        if pdb and pdb.disableThreatInPvP then
            pvpNoThreat = true
        end
    end

    -- Solo override: keep ONLY base colors
    local isSolo = (not IsInGroup()) and (not IsInRaid())
    if db.useBaseColorsSolo and isSolo and not (skin and skin.ignoreColor) and not neutralLock then
        hb:SetStatusBarColor(finalR, finalG, finalB)

        if hb.pomThreatText then
            hb.pomThreatText:SetText("")
            hb.pomThreatText:Hide()
        end

        if hb.pomWarningGlow and hb.pomWarningAnim and self.frame.pomIsFlashing then
            self.frame.pomIsFlashing = false
            if self.frame.pomFlashTimer then self.frame.pomFlashTimer:Cancel() end
            hb.pomWarningGlow:Hide()
            hb.pomWarningAnim:Stop()
        end

    elseif pvpNoThreat then
        hb:SetStatusBarColor(finalR, finalG, finalB)
        if hb.pomThreatText then
            hb.pomThreatText:SetText("")
            hb.pomThreatText:Hide()
        end
        if hb.pomWarningGlow and hb.pomWarningAnim and self.frame.pomIsFlashing then
            self.frame.pomIsFlashing = false
            if self.frame.pomFlashTimer then self.frame.pomFlashTimer:Cancel() end
            hb.pomWarningGlow:Hide()
            hb.pomWarningAnim:Stop()
        end

    else
        -- Threat override
        local tdb = GetThreatDB(db) or {}

        local tr, tg, tb, shouldFlash, threatPct = nil, nil, nil, false, 0
        if not neutralLock and tdb.enabled and ns and ns.threat and ns.threat.GetState then
            tr, tg, tb, shouldFlash, threatPct = ns.threat:GetState(unit)
        end

        if not neutralLock and tr and not (skin and skin.ignoreColor) then
            finalR, finalG, finalB = tr, tg, tb
        end

        hb:SetStatusBarColor(finalR, finalG, finalB)

        if hb.pomThreatText and (not neutralLock) and tdb.showThreatPerc then
            local minPct = tonumber(tdb.pctTextMin) or 1
            if threatPct and threatPct >= minPct then
                hb.pomThreatText:SetText(string.format("%.0f%%", threatPct))
                hb.pomThreatText:Show()

                if threatPct >= 100 then
                    hb.pomThreatText:SetTextColor(1, 0.2, 0.2)
                elseif threatPct >= 70 then
                    hb.pomThreatText:SetTextColor(1, 1, 0)
                else
                    hb.pomThreatText:SetTextColor(0.6, 0.6, 0.6)
                end
            else
                hb.pomThreatText:SetText("")
                hb.pomThreatText:Hide()
            end
        elseif hb.pomThreatText then
            hb.pomThreatText:SetText("")
            hb.pomThreatText:Hide()
        end

        if hb.pomWarningGlow and hb.pomWarningAnim then
            if neutralLock then
                if self.frame.pomIsFlashing then
                    self.frame.pomIsFlashing = false
                    if self.frame.pomFlashTimer then self.frame.pomFlashTimer:Cancel() end
                    hb.pomWarningGlow:Hide()
                    hb.pomWarningAnim:Stop()
                end
            else
                local wantFlash = (tdb.flashAggro and shouldFlash) and true or false

                if wantFlash then
                    if not self.frame.pomIsFlashing then
                        self.frame.pomIsFlashing = true
                        hb.pomWarningGlow:Show()
                        hb.pomWarningAnim:Play()

                        local dur = tonumber(tdb.flashDuration) or 0
                        if dur > 0 then
                            if self.frame.pomFlashTimer then self.frame.pomFlashTimer:Cancel() end
                            self.frame.pomFlashTimer = C_Timer.NewTimer(dur, function()
                                if self.frame then
                                    self.frame.pomIsFlashing = false
                                    if hb and hb.pomWarningGlow then
                                        hb.pomWarningGlow:Hide()
                                        hb.pomWarningAnim:Stop()
                                    end
                                end
                            end)
                        end
                    end
                else
                    if self.frame.pomIsFlashing then
                        self.frame.pomIsFlashing = false
                        if self.frame.pomFlashTimer then self.frame.pomFlashTimer:Cancel() end
                        hb.pomWarningGlow:Hide()
                        hb.pomWarningAnim:Stop()
                    end
                end
            end
        end
    end

    -- Borders
    if hb.pomBorders then
        U.LayoutBorder(hb.pomBorders, hb, 2)
        for i = 1, 4 do hb.pomBorders[i]:SetColorTexture(0, 0, 0, 1) end
    end

    -- Target border
    if hb.pomTargetBorder then
        if isTarget then
            local c = db.targetBorderColor or { r = 1, g = 0.82, b = 0, a = 1 }
            local r, g, b, a = c.r or 1, c.g or 1, c.b or 1, c.a or 1
            for i = 1, 4 do hb.pomTargetBorder[i]:SetColorTexture(r, g, b, a) end
            U.LayoutBorder(hb.pomTargetBorder, hb, 3)
            for i = 1, 4 do hb.pomTargetBorder[i]:Show() end
        else
            for i = 1, 4 do hb.pomTargetBorder[i]:Hide() end
        end
    end
end
