--[[
    UI/SightingAlert.lua

    Compact encounter card for rare or exotic hunter beasts. It is separate
    from the persistent sighting log: a known creature still deserves an
    immediate alert when it walks into view.

    The optional target action is a secure macro button. Combat lockdown can
    stop addons changing that macro, so the ordinary card remains visible and
    states clearly when selection is unavailable.
]]

local _, ns = ...

local IsSecretValue = issecretvalue

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

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

local Theme = ns.UI.Theme
local DISPLAY_SECONDS = 14
local SOUND_COOLDOWN = 3
local ALERT_SOUND = "RAID_WARNING"

-- The brass plate along the bottom of the card artwork.
local PLATE_WIDTH, PLATE_HEIGHT = 158, 25
local PLATE_OFFSET_X, PLATE_OFFSET_Y = 22, 9

local CAPTURED_COLOR = { 0.45, 0.92, 0.55 }
local DEFEATED_COLOR = { 0.95, 0.45, 0.42 }

local alertedGUIDs = {}
local lastSoundAt = 0
local alertSerial = 0
local frame

local function IsRare(classification)
    return classification == "rare" or classification == "rareelite"
end

local function PlayEncounterSound()
    if ns.Config:Get("soundEnabled") == false then
        return
    end

    local now = GetTime()
    if now - lastSoundAt < SOUND_COOLDOWN then
        return
    end
    lastSoundAt = now

    local soundID = SOUNDKIT and (SOUNDKIT[ALERT_SOUND]
        or SOUNDKIT.UI_MAP_WAYPOINT_CHAT_SHARE)
    if soundID then
        PlaySound(soundID, "Master")
    end
end

local function HideAlert()
    if frame then
        frame:Hide()
    end
end

local function BuildFrame()
    if frame then
        return frame
    end

    local card = CreateFrame("Frame", nil, UIParent)
    card:SetSize(456, 152)
    card:SetPoint("LEFT", UIParent, "LEFT", 24, -28)
    card:SetFrameStrata("DIALOG")
    card:SetFrameLevel(180)
    card:SetClampedToScreen(true)
    card:EnableMouse(true)

    card.Artwork = card:CreateTexture(nil, "BACKGROUND")
    card.Artwork:SetAllPoints(card)
    card.Artwork:SetTexture(Theme.sightingAlertTexture)

    card.Portrait = card:CreateTexture(nil, "ARTWORK")
    card.Portrait:SetSize(72, 72)
    card.Portrait:SetPoint("LEFT", card, "LEFT", 48, 2)

    card.PortraitMask = card:CreateMaskTexture()
    card.PortraitMask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask",
        "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
    card.PortraitMask:SetAllPoints(card.Portrait)
    card.Portrait:AddMaskTexture(card.PortraitMask)

    card.Eyebrow = card:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
    card.Eyebrow:SetPoint("TOPLEFT", card, "TOPLEFT", 140, -26)
    card.Eyebrow:SetPoint("RIGHT", card, "RIGHT", -44, 0)
    card.Eyebrow:SetJustifyH("CENTER")
    card.Eyebrow:SetTextColor(Theme.color.gold[1], Theme.color.gold[2],
        Theme.color.gold[3])

    card.Name = card:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    card.Name:SetPoint("TOPLEFT", card.Eyebrow, "BOTTOMLEFT", 0, -2)
    card.Name:SetPoint("RIGHT", card, "RIGHT", -44, 0)
    card.Name:SetJustifyH("CENTER")
    card.Name:SetWordWrap(false)
    card.Name:SetTextColor(Theme.color.text[1], Theme.color.text[2],
        Theme.color.text[3])

    card.Family = card:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
    card.Family:SetPoint("TOPLEFT", card.Name, "BOTTOMLEFT", 0, -2)
    card.Family:SetPoint("RIGHT", card, "RIGHT", -44, 0)
    card.Family:SetJustifyH("CENTER")
    card.Family:SetWordWrap(false)
    card.Family:SetTextColor(Theme.color.muted[1], Theme.color.muted[2],
        Theme.color.muted[3])

    -- The artwork has an empty brass plate along the bottom edge. It used to
    -- sit under a secure targeting button that this client would not allow, and
    -- has been blank ever since. Measured from the authored 768x256 PNG and
    -- converted to the live 456x152 frame, the same figures the old button used.
    card.Plate = card:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    card.Plate:SetSize(PLATE_WIDTH, PLATE_HEIGHT)
    card.Plate:SetPoint("BOTTOM", card, "BOTTOM", PLATE_OFFSET_X, PLATE_OFFSET_Y)
    card.Plate:SetJustifyH("CENTER")
    card.Plate:SetJustifyV("MIDDLE")
    card.Plate:SetWordWrap(false)

    card.Close = CreateFrame("Button", nil, card)
    card.Close:SetSize(28, 28)
    card.Close:SetPoint("TOPRIGHT", card, "TOPRIGHT", -18, -14)
    card.Close:SetNormalFontObject(GameFontNormalLarge)
    card.Close:SetText("×")
    local closeFont = card.Close:GetFontString()
    if closeFont then
        closeFont:SetTextColor(0.35, 0.95, 0.86)
    end
    card.Close:SetScript("OnClick", HideAlert)

    frame = card
    card:Hide()
    return card
end

local function SetPortrait(card, unit, appearance, rareRecord)
    local applied = false
    local displayID = appearance and appearance.displayIDs
        and appearance.displayIDs[1]
        or (rareRecord and rareRecord.displayIDs and rareRecord.displayIDs[1])

    if displayID and SetPortraitTextureFromCreatureDisplayID then
        applied = pcall(SetPortraitTextureFromCreatureDisplayID,
            card.Portrait, displayID)
    elseif SetPortraitTexture then
        applied = pcall(SetPortraitTexture, card.Portrait, unit)
    end

    if not applied then
        card.Portrait:SetTexture("Interface\\Icons\\Ability_Hunter_BeastCall")
        card.Portrait:SetTexCoord(0.08, 0.92, 0.08, 0.92)
    else
        card.Portrait:SetTexCoord(0, 1, 0, 1)
    end
end

local function ShowEncounter(unit, creatureID, appearance, familyName,
    classification, guid)
    if not guid or alertedGUIDs[guid] then
        return
    end
    alertedGUIDs[guid] = true

    local card = BuildFrame()
    local rareRecord = ns.Data.RareCatalog:Get(creatureID)
    -- The live name is localized by the client ("Rey Krush" on esES).
    local name = UnitName(unit)
    if IsSecret(name) then name = nil end
    name = name or (appearance and ns.L[appearance.nameKey])
        or (rareRecord and rareRecord.name)
        or ("NPC %d"):format(creatureID)
    if IsSecret(familyName) then familyName = nil end
    local family = appearance and ns.Data.API:GetFamily(appearance.family)
        or ns.Data.API:GetFamilyByLocalizedName(familyName)
        or (rareRecord and rareRecord.family
            and ns.Data.API:GetFamily(rareRecord.family))
    local displayFamilyName = familyName
        or (family and ns.L[family.nameKey])
        or ns.L["RARE_CATALOG_FAMILY_UNKNOWN"]
    local isExotic = family and family.exotic == true
    local tracked = ns.Navigation and ns.Navigation.NativeWaypoint
        and ns.Navigation.NativeWaypoint:IsTrackedCreature(creatureID)

    card.Eyebrow:SetText(tracked and ns.L["ALERT_TRACKED_BEAST"]
        or (isExotic and ns.L["ALERT_EXOTIC_BEAST"]
            or (IsRare(classification) and ns.L["ALERT_RARE_BEAST"]
                or ns.L["ALERT_NOTABLE_BEAST"])))
    card.Name:SetText(name)
    card.Family:SetText(ns.L["ALERT_FAMILY_FORMAT"]:format(displayFamilyName))

    -- One card serves both messages, so anything the other one sets must be
    -- cleared here: an encounter is not an outcome.
    card.Plate:SetText("")
    card.Plate:Hide()

    SetPortrait(card, unit, appearance, rareRecord)

    alertSerial = alertSerial + 1
    local serial = alertSerial
    card:Show()
    PlayEncounterSound()

    C_Timer.After(DISPLAY_SECONDS, function()
        if serial == alertSerial then
            HideAlert()
        end
    end)

    ns.Debug:Log("ALERT", "notable beast detected: %d (%s)",
        creatureID, tostring(name))
end

-- A completed dossier deserves a separate confirmation from the encounter
-- warning: it is emitted only after RareState has persisted the capture or
-- defeat, never merely because a beast was seen.
local function ShowCompletion(npcID, outcome)
    local rare = ns.Data.RareCatalog:Get(npcID)
    if not rare then return end

    local card = BuildFrame()

    local family = rare.family and ns.Data.API:GetFamily(rare.family)
    local familyName = family and ns.L[family.nameKey]
        or ns.L["RARE_CATALOG_FAMILY_UNKNOWN"]
    local captured = outcome == "captured"
    local color = captured and CAPTURED_COLOR or DEFEATED_COLOR

    card.Eyebrow:SetText(ns.L["ALERT_PROGRESS_RECORDED"])
    card.Name:SetText(ns.Collection.ClientFacts:ResolveName(rare))
    card.Family:SetText(familyName)

    -- The outcome is the whole point of this card, so it goes where the eye
    -- lands last and in a colour that answers before the word is read.
    card.Plate:SetText(captured and ns.L["ALERT_COMPLETION_CAPTURED"]
        or ns.L["ALERT_COMPLETION_KILLED"])
    card.Plate:SetTextColor(color[1], color[2], color[3])
    card.Plate:Show()

    SetPortrait(card, nil, nil, rare)

    alertSerial = alertSerial + 1
    local serial = alertSerial
    card:Show()
    PlayEncounterSound()
    C_Timer.After(DISPLAY_SECONDS, function()
        if serial == alertSerial then HideAlert() end
    end)

    ns.Debug:Log("PROGRESS", "rare %d recorded as %s", npcID, outcome)
end

function SightingAlert:OnEnable()
    -- Build the secure action while the login sequence is outside combat, so
    -- a first sighting that occurs mid-fight can still display its normal UI.
    BuildFrame()

    ns.Events:Subscribe("BEAST_DETECTED", self,
        function(_, unit, creatureID, appearance, familyName,
            classification, guid)
            ShowEncounter(unit, creatureID, appearance, familyName,
                classification, guid)
        end)

    ns.Events:Subscribe("RARE_COLLECTION_CHANGED", self,
        function(_, npcID, completed, outcome)
            if completed and (outcome == "captured" or outcome == "killed") then
                ShowCompletion(npcID, outcome)
            end
        end)

    ns.Events:RegisterEvent("PLAYER_REGEN_ENABLED", self, function()
        HideAlert()
    end)
end
