-- =============================================================
-- Plate-o-Matic (Mixins)
-- - All nameplate logic lives here (per-frame object)
-- - Castbar: ONLY moves Blizzard text inside + applies sizing/text-size
-- - Target border: colored border shown only on current target
-- =============================================================

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

local U = ns.util

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

    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
        hb.pomName:SetFont(db.fontPath, db.fontSize, db.fontOutline)
        hb.pomName:SetTextColor(1, 1, 1)
        hb.pomName:Show()
    end

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

    if hb.pomThreatText then
        hb.pomThreatText:SetFont(db.fontPath, db.fontSize - 1, db.fontOutline)
    end

    if hb.pomQuestText then
        hb.pomQuestText:SetFont(db.fontPath, db.fontSize - 2, db.fontOutline)
    end
end

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

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
        percentValue = UnitHealth(unit, true) / UnitHealthMax(unit) * 100
    end

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

-- ------------------------------------------------------------
-- Castbar layout (NO styling, only size + text inside)
-- ------------------------------------------------------------
function Mixins:UpdateCastbarLayout()
    local c = self.frame.castBar
    if not c then return end

    local db = PlateOMaticDB
    c:SetHeight(db.castBarHeight or 10)
    c:SetScale(db.castBarScale or 1.0)

    if c.Text then
        c.Text:ClearAllPoints()
        c.Text:SetPoint("CENTER", c, "CENTER", 0, 0)
        if c.Text.SetJustifyH then c.Text:SetJustifyH("CENTER") end
        if c.Text.SetWordWrap then c.Text:SetWordWrap(false) end

        local sz = db.castTextSize or db.fontSize or 10
        if c.Text.SetFont then
            c.Text:SetFont(db.fontPath, sz, db.fontOutline)
        end
        c.Text:Show()
    end

    if self.frame.CastBarBorder then self.frame.CastBarBorder:Hide() end
    if self.frame.CastBarShield then self.frame.CastBarShield:Hide() end
end

-- ------------------------------------------------------------
-- State (colors/skins, threat, target border)
-- ------------------------------------------------------------
function Mixins:UpdateState()
    local hb = self.healthBar
    if not hb then return end

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

    -- Scale + 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)

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

    local skins = self.skins or ns.SKINS
    local skinID = (db.skins and db.skins[typeKey]) or 23
    local skin = skins[skinID] or skins[23]

    hb:SetStatusBarTexture(skin.texture)
    hb:SetHeight((db.heights and db.heights[typeKey]) or 16)

    -- Base color
    local finalR, finalG, finalB = db.colors[typeKey].r, db.colors[typeKey].g, db.colors[typeKey].b
    if skin.ignoreColor then finalR, finalG, finalB = 1, 1, 1 end

    -- Threat values (robust)
    local _, status, threatPct = UnitDetailedThreatSituation("player", unit)
    if status == nil then
        status = UnitThreatSituation("player", unit)
    end
    status = tonumber(status) or 0

    local role = GetSpecializationRole(GetSpecialization() or 1)

    if db.colorBarByThreat and status > 0 then
        if db.tankModeEnabled and role == "TANK" then
            if status >= 3 then
                finalR, finalG, finalB = db.colors.threatTankSafe.r, db.colors.threatTankSafe.g, db.colors.threatTankSafe.b
            elseif status == 2 then
                finalR, finalG, finalB = db.colors.threatMedium.r, db.colors.threatMedium.g, db.colors.threatMedium.b
            else
                finalR, finalG, finalB = db.colors.threatHigh.r, db.colors.threatHigh.g, db.colors.threatHigh.b
            end
        else
            if status >= 3 then
                finalR, finalG, finalB = db.colors.threatHigh.r, db.colors.threatHigh.g, db.colors.threatHigh.b
            elseif status >= 1 then
                finalR, finalG, finalB = db.colors.threatMedium.r, db.colors.threatMedium.g, db.colors.threatMedium.b
            end
        end
    end

    hb:SetStatusBarColor(finalR, finalG, finalB)

    -- Threat text
    if 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

    -- Flash warning (FIXED: no solo instance spam + stable combat/threat conditions)
    if db.flashAggro and hb.pomWarningGlow then
        local inGroup = IsInGroup() or IsInRaid()
        local playerInCombat = UnitAffectingCombat("player")

        local shouldFlash = false
        if inGroup and playerInCombat then
            if db.tankModeEnabled and role == "TANK" then
                -- Tank: flash only when LOSING threat (1-2). Having aggro (3) is fine.
                shouldFlash = (status >= 1 and status <= 2)
            else
                -- Non-tank: flash when YOU have aggro (3).
                shouldFlash = (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

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