local 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

-- ------------------------------------------------------------
-- 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

    -- Background texture (follows bar size)
    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

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

    -- Text frame + Name + HP
    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

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

    -- RARE text (above bar)  [FIX: use template so font is set before SetText]
    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") -- safe now (font already exists via template)
        rt:Hide()
        hb.pomRareText = rt
    end

    -- Quest icon + progress text
    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

    -- Target arrows
    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

    -- Warning glow (flash)
    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

    -- Target border (separate)
    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

    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)
        -- color set in Init
    end

    if hb.pomQuestText then
        SafeSetFont(hb.pomQuestText, db.fontPath, (tonumber(db.fontSize) or 10) - 2, db.fontOutline)
    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
    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 + colors + threat + target visuals)
-- =========================================================
function Mixins:UpdateState()
    local hb = self.healthBar
    if not hb then return end

    local db = PlateOMaticDB
    local unit = self.frame.unit

    -- ---------------------------------------------------------
    -- RARE label (above bar)
    -- ---------------------------------------------------------
    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

    -- ---------------------------------------------------------
    -- Target scaling + arrows
    -- ---------------------------------------------------------
    local currentScale = db.globalScale or 1.0
    local isTarget = UnitIsUnit("target", unit)

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

        if db.showTargetArrow and hb.pomArrows 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()
        end
    else
        if hb.pomArrows then
            hb.pomArrows.left:Hide()
            hb.pomArrows.right:Hide()
        end
    end

    self.frame:SetScale(currentScale)

    -- ---------------------------------------------------------
    -- TypeKey classification (skin + base + height)
    -- ---------------------------------------------------------
    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 + height (safe)
    -- ---------------------------------------------------------
    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
    hb:SetHeight((db.heights and db.heights[typeKey]) or 16)

    -- ---------------------------------------------------------
    -- Base color (typeKey) unless ignoreColor -> white
    -- ---------------------------------------------------------
    local cBase = (db.colors and db.colors[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 mob color (NEW)
    -- ---------------------------------------------------------
    local neutralLock = false
    if not (skin and skin.ignoreColor) and db.colors and db.colors.neutral then
        local reaction = tonumber(UnitReaction(unit, "player") or 0) or 0
        if reaction == 4 and not UnitIsPlayer(unit) then
            local nc = db.colors.neutral
            finalR = nc.r or finalR
            finalG = nc.g or finalG
            finalB = nc.b or finalB
            neutralLock = not UnitAffectingCombat(unit)
        end
    end

    -- ---------------------------------------------------------
    -- SOLO OVERRIDE (NEW)
    -- ---------------------------------------------------------
    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)

        -- Stop any flashing
        if hb.pomWarningGlow and hb.pomWarningAnim 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
        end

        -- Hide threat text in solo override
        if hb.pomThreatText then
            hb.pomThreatText:SetText("")
            hb.pomThreatText:Hide()
        end
    else
        -- ---------------------------------------------------------
        -- Read threat from PLAYER -> unit
        -- ---------------------------------------------------------
        local _, status, threatPct = UnitDetailedThreatSituation("player", unit)
        if status == nil then status = UnitThreatSituation("player", unit) end
        status = tonumber(status) or 0
        threatPct = tonumber(threatPct) or 0

        local role = GetSpecializationRole(GetSpecialization() or 1)

        -- =========================================================
        -- Group Combat Color (DPS/HEAL only)
        -- =========================================================
        if (not neutralLock) and db.groupAggroColorEnabled
            and role ~= "TANK"
            and (IsInGroup() or IsInRaid())
            and UnitAffectingCombat("player")
            and db.colors and db.colors.groupAggro
            and not (skin and skin.ignoreColor)
        then
            local gc = db.colors.groupAggro
            finalR, finalG, finalB = gc.r or finalR, gc.g or finalG, gc.b or finalB
        end

        -- =========================================================
        -- Tank MT/OT override (pom_tank.lua)
        -- =========================================================
        local tankOverride = false
        local tankFlash = false

        if (not neutralLock) and db.tankModeEnabled and role == "TANK" and UnitAffectingCombat("player") and ns and ns.GetTankModeState then
            local tr, tg, tb, tf = ns:GetTankModeState(unit)
            if tr then
                finalR, finalG, finalB = tr, tg, tb
                tankFlash = tf and true or false
                tankOverride = true
            end
        end

        -- =========================================================
        -- Threat Colors
        -- =========================================================
        if (not neutralLock) and db.colorBarByThreat then
            if role == "TANK" then
                if (status > 0) and (not tankOverride) then
                    if status >= 3 then
                        local tc = db.colors.threatTankSafe
                        finalR, finalG, finalB = tc.r, tc.g, tc.b
                    elseif status >= 1 then
                        local mc = db.colors.threatMedium
                        finalR, finalG, finalB = mc.r, mc.g, mc.b
                    end
                end
            else
                if threatPct >= 60 then
                    local t = (threatPct - 60) / 40
                    if t < 0 then t = 0 elseif t > 1 then t = 1 end

                    local y = db.colors.threatMedium
                    local r = db.colors.threatHigh
                    finalR = y.r + (r.r - y.r) * t
                    finalG = y.g + (r.g - y.g) * t
                    finalB = y.b + (r.b - y.b) * t
                end
            end
        end

        hb:SetStatusBarColor(finalR, finalG, finalB)

        -- ---------------------------------------------------------
        -- Threat % text (always YOUR threatPct)
        -- ---------------------------------------------------------
        if (not neutralLock) and db.showThreatPerc and hb.pomThreatText then
            if threatPct and threatPct > 0 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:Hide()
        end

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

        -- =========================================================
        -- Flash warning
        -- =========================================================
        if neutralLock and 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

        if (not neutralLock) and db.flashAggro and hb.pomWarningGlow then
            local inGroup = IsInGroup() or IsInRaid()
            local playerInCombat = UnitAffectingCombat("player")

            local shouldFlash = false
            if inGroup and playerInCombat then
                if role == "TANK" and db.tankModeEnabled then
                    shouldFlash = tankFlash
                else
                    shouldFlash = (threatPct >= 60) or (status >= 3)
                end
            end

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

                    if db.flashDuration and db.flashDuration > 0 then
                        if self.frame.pomFlashTimer then self.frame.pomFlashTimer:Cancel() end
                        self.frame.pomFlashTimer = C_Timer.NewTimer(db.flashDuration, 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

    -- ---------------------------------------------------------
    -- Base border (always black)
    -- ---------------------------------------------------------
    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 (user color)
    -- ---------------------------------------------------------
    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
