--[[
    UI/NameplateMarker.lua

    Labels the nameplate of every tameable beast with what kind of skin it is,
    so the world tells you whether something is worth stopping for.

        Common    white     an ordinary family skin
        Rare      blue      a rare or rare elite spawn
        Exotic    purple    an exotic family, Beast Mastery only

    The colours are the game's own item quality colours, so they read without
    being explained. A beast met for the first time this session also gets a
    gold outline.

    This is also where most sightings get logged: a nameplate appearing is an
    encounter, so walking through a zone fills the journal without clicking on
    anything.

    ---------------------------------------------------------------------------
    Unverified APIs
    ---------------------------------------------------------------------------

    `C_NamePlate.GetNamePlateForUnit` and the NAME_PLATE_UNIT_* events are not
    in use by any addon on this machine, so they ship capability-checked. If
    they are absent the module does nothing and the rest of the addon is
    unaffected; the start-up log says which it was. See docs/wow-api.md.
]]

local _, ns = ...

local NameplateMarker = ns.NewModule("NameplateMarker")
ns.UI = ns.UI or {}
ns.UI.NameplateMarker = NameplateMarker

local BADGE_HEIGHT = 18

-- A rare is worth stopping for, so its badge is taller and carries its name.
local NOTABLE_HEIGHT = 24
local NOTABLE_FONT = "GameFontNormalLarge"
local ORDINARY_FONT = "GameFontNormalSmall"
local BADGE_PADDING = 12
local NEW_OUTLINE = 2

-- The game's own quality colours: white for common, blue for rare, purple for
-- epic. Reusing them means the scale needs no legend.
local QUALITY_COLORS = {
    COMMON = { r = 0.90, g = 0.90, b = 0.90 },
    RARE   = { r = 0.10, g = 0.55, b = 0.95 },
    EXOTIC = { r = 0.72, g = 0.30, b = 0.95 },
}

local NEW_COLOR = { r = 1.00, g = 0.82, b = 0.10 }

-- Nameplate frame -> badge. Nameplates are recycled by the client, so badges
-- are keyed by the plate and reused with it rather than created per creature.
local badges = {}

-- Unit token -> nameplate. By the time NAME_PLATE_UNIT_REMOVED fires, asking
-- the client for that unit's plate no longer answers, so the pairing has to be
-- remembered from when the plate appeared.
local platesByUnit = {}

local available = false
local IsSecretValue = issecretvalue

local function IsSecret(value)
    return IsSecretValue and IsSecretValue(value)
end

--=============================================================================
-- What kind of skin is this?
--=============================================================================

local function Colored(text, color)
    return ("|cff%02x%02x%02x%s|r"):format(
        color.r * 255, color.g * 255, color.b * 255, text)
end

--- Builds the badge text, already coloured.
--
-- The parts carry their own colour codes because the label mixes two meanings:
-- "this is new to you" in gold and "this is what it is" in its quality colour.
-- A gold outline alone was not enough — a colour has to be learned, a word does
-- not.
--
-- @return string label, boolean isNew
local function DescribeSkin(unit, creatureID)
    local L = ns.L
    local rareRecord = ns.Data.RareCatalog:Get(creatureID)

    local classification = UnitClassification and UnitClassification(unit) or nil
    if IsSecret(classification) then classification = nil end
    local isRare = rareRecord ~= nil
        or classification == "rare" or classification == "rareelite"

    -- Exotic is a property of the family, and the client only gives us its
    -- localized name, so this resolves through the name bridge in the registry.
    local familyName = UnitCreatureFamily and UnitCreatureFamily(unit) or nil
    if IsSecret(familyName) then familyName = nil end
    local family = ns.Data.API:GetFamilyByLocalizedName(familyName)
        or (rareRecord and rareRecord.family
            and ns.Data.API:GetFamily(rareRecord.family))
    local isExotic = family ~= nil and family.exotic == true

    local isNew = ns.Collection.SightingTracker:IsNewThisSession(creatureID)

    -- Exotic outranks rare for the colour: it is the harder requirement, and a
    -- hunter who cannot tame exotics needs to see that first.
    local quality = isExotic and "EXOTIC" or (isRare and "RARE" or "COMMON")

    local parts = {}

    -- First, because it is the part that is news.
    if isNew then
        parts[#parts + 1] = Colored(L["SKIN_NEW"], NEW_COLOR)
    end

    -- A rare gets its name in the badge, in its quality colour. Blizzard's own
    -- name text on the plate cannot be restyled without attaching to frames
    -- this addon deliberately leaves alone, so the emphasis goes here instead,
    -- where it costs nothing and can be as loud as it needs to be.
    --
    -- Ordinary skins stay a single quiet word: a world where every boar shouts
    -- teaches the player to stop reading the badge at all.
    if isRare or isExotic then
        local name = rareRecord and ns.Collection.ClientFacts:ResolveName(rareRecord)
        if not name then
            name = UnitName(unit)
            if IsSecret(name) then name = nil end
        end
        if name then
            parts[#parts + 1] = Colored(name, QUALITY_COLORS[quality])
        end
    end

    local traits = {}
    if isRare then
        traits[#traits + 1] = L["SKIN_RARE"]
    end
    if isExotic then
        traits[#traits + 1] = L["SKIN_EXOTIC"]
    end
    if #traits == 0 then
        traits[#traits + 1] = L["SKIN_COMMON"]
    end

    parts[#parts + 1] = Colored(table.concat(traits, " · "), QUALITY_COLORS[quality])

    return table.concat(parts, " · "), isNew, (isRare or isExotic)
end

--=============================================================================
-- Badge
--=============================================================================

local function AcquireBadge(plate)
    local badge = badges[plate]
    if badge then
        return badge
    end

    badge = CreateFrame("Frame", nil, plate)
    badge:SetHeight(BADGE_HEIGHT)
    badge:SetPoint("BOTTOM", plate, "TOP", 0, 2)
    badge:SetFrameStrata("HIGH")

    badge.Outline = badge:CreateTexture(nil, "BACKGROUND")
    badge.Outline:SetPoint("TOPLEFT", badge, "TOPLEFT", -NEW_OUTLINE, NEW_OUTLINE)
    badge.Outline:SetPoint("BOTTOMRIGHT", badge, "BOTTOMRIGHT", NEW_OUTLINE, -NEW_OUTLINE)

    badge.Background = badge:CreateTexture(nil, "BORDER")
    badge.Background:SetAllPoints(badge)
    badge.Background:SetColorTexture(0, 0, 0, 0.75)

    badge.Label = badge:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
    badge.Label:SetPoint("CENTER", badge, "CENTER", 0, 0)

    badges[plate] = badge

    return badge
end

--=============================================================================
-- Events
--=============================================================================

local function OnNameplateAdded(unit)
    -- Retail Midnight protects nameplate identity while a unit is not safe for
    -- addons to inspect. Do not feed that secret GUID into the sighting parser.
    if IsSecret(UnitGUID(unit)) then
        return
    end

    local plate = C_NamePlate.GetNamePlateForUnit(unit)
    if not plate then
        return
    end

    platesByUnit[unit] = plate

    -- SightingTracker watches this same event, so recording no longer depends
    -- on this module. The call stays for ordering: both handlers sit in one
    -- table and fire in undefined order, and a badge drawn first would ask
    -- IsNewThisSession before the sighting was logged and quietly lose the
    -- "new" mark on the very encounter that earned it. InspectUnit is
    -- idempotent, so whichever runs second costs one GUID split.
    local creatureID = ns.Collection.SightingTracker:InspectUnit(unit)
    if not creatureID then
        return
    end

    -- Only beasts with a pet family are marked, the same filter the sighting
    -- log uses. Everything else in the world stays untouched.
    if (not UnitCreatureFamily or not UnitCreatureFamily(unit))
        and not ns.Data.RareCatalog:Get(creatureID) then
        return
    end

    local label, isNew, isNotable = DescribeSkin(unit, creatureID)

    local badge = AcquireBadge(plate)

    -- Frames are recycled, so both branches must always be set.
    badge.Label:SetFontObject(isNotable and NOTABLE_FONT or ORDINARY_FONT)
    badge:SetHeight(isNotable and NOTABLE_HEIGHT or BADGE_HEIGHT)

    -- The label carries its own colour codes, so the base colour must not tint
    -- them: white leaves each part exactly as it was written.
    badge.Label:SetTextColor(1, 1, 1)
    badge.Label:SetText(label)
    badge:SetWidth(badge.Label:GetStringWidth() + BADGE_PADDING)

    if isNew then
        badge.Outline:SetColorTexture(NEW_COLOR.r, NEW_COLOR.g, NEW_COLOR.b, 0.9)
        badge.Outline:Show()
    else
        badge.Outline:Hide()
    end

    badge:Show()
end

local function OnNameplateRemoved(unit)
    local plate = platesByUnit[unit]
    platesByUnit[unit] = nil

    local badge = plate and badges[plate]
    if badge then
        badge:Hide()
    end
end

--=============================================================================
-- Lifecycle
--=============================================================================

function NameplateMarker:OnEnable()
    -- Badges were disabled here on the theory that anchoring addon children to
    -- modern nameplates was what raised the client's "blocked from an action
    -- only available to the Blizzard UI" dialog. That theory is now disproven:
    -- the diagnostic named Frame:RegisterEvent, and the culprit was a pair of
    -- restricted event registrations in Collection/RareState.lua. 0.21.5 also
    -- shipped these badges with no dialog at all.
    --
    -- A cosmetic feature is not removed on a hunch. Restored.
    available = C_NamePlate ~= nil and C_NamePlate.GetNamePlateForUnit ~= nil

    if not available then
        ns.Debug:Warn("PLATE", "C_NamePlate is unavailable; nameplate badges disabled")
        return
    end

    ns.Events:RegisterEvent("NAME_PLATE_UNIT_ADDED", self, function(_, unit)
        OnNameplateAdded(unit)
    end)

    ns.Events:RegisterEvent("NAME_PLATE_UNIT_REMOVED", self, function(_, unit)
        OnNameplateRemoved(unit)
    end)

    ns.Debug:Log("PLATE", "nameplate badges active")
end

--- @return boolean Whether the client exposes what this needs.
function NameplateMarker:IsAvailable()
    return available
end
