--[[ Exact, per-character progress for named rare hunter beasts. ]]

local _, ns = ...

local RareState = ns.NewModule("RareState")
ns.Collection = ns.Collection or {}
ns.Collection.RareState = RareState

local TAME_BEAST_SPELL_ID = 1515
local TAME_CONFIRM_WINDOW = 15
local PET_CONFIRM_DELAYS = { 0, 0.25, 0.75, 1.5, 3, 6 }
local pendingTame

local function CreatureIDFromGUID(guid)
    if not guid then return nil end
    local unitType, _, _, _, _, creatureID = strsplit("-", guid)
    return (unitType == "Creature" or unitType == "Pet")
        and tonumber(creatureID) or nil
end

local function ClearPendingTame(candidate)
    if not candidate or pendingTame == candidate then
        pendingTame = nil
    end
end

local function RememberTameTarget()
    local npcID = CreatureIDFromGUID(UnitGUID("target"))
    if not npcID or not ns.Data.RareCatalog:Get(npcID) then
        pendingTame = nil
        return
    end

    local candidate = { npcID = npcID, startedAt = GetTime() }
    pendingTame = candidate
    C_Timer.After(TAME_CONFIRM_WINDOW, function()
        ClearPendingTame(candidate)
    end)
end

function RareState:IsCaptured(npcID)
    local character = ns.Database:GetCharacterData()
    return character and character.capturedRares[npcID] == true or false
end

function RareState:IsKilled(npcID)
    local character = ns.Database:GetCharacterData()
    return character and character.killedRares
        and character.killedRares[npcID] == true or false
end

function RareState:IsCompleted(npcID)
    return self:IsCaptured(npcID) or self:IsKilled(npcID)
end

function RareState:GetOutcome(npcID)
    local captured = self:IsCaptured(npcID)
    local killed = self:IsKilled(npcID)
    if captured and killed then return "both" end
    if captured then return "captured" end
    if killed then return "killed" end
    return nil
end

function RareState:SetCaptured(npcID, captured)
    local character = ns.Database:GetCharacterData()
    if not character or not ns.Data.RareCatalog:Get(npcID) then return false end
    local value = captured == true
    if (character.capturedRares[npcID] == true) == value then return false end
    character.capturedRares[npcID] = value or nil
    ns.Events:Send("RARE_COLLECTION_CHANGED", npcID, value)
    return true
end

function RareState:SetKilled(npcID, killed)
    local character = ns.Database:GetCharacterData()
    if not character or not ns.Data.RareCatalog:Get(npcID) then return false end
    character.killedRares = character.killedRares or {}
    local value = killed == true
    if (character.killedRares[npcID] == true) == value then return false end
    character.killedRares[npcID] = value or nil
    ns.Events:Send("RARE_COLLECTION_CHANGED", npcID, value, "killed")
    return true
end

function RareState:MarkSeen(npcID)
    local character = ns.Database:GetCharacterData()
    if not character or not ns.Data.RareCatalog:Get(npcID) then return false end
    local mapID = C_Map and C_Map.GetBestMapForUnit
        and C_Map.GetBestMapForUnit("player") or nil
    local position = mapID and C_Map.GetPlayerMapPosition
        and C_Map.GetPlayerMapPosition(mapID, "player") or nil
    local previous = character.seenRares[npcID]
    character.seenRares[npcID] = {
        seenAt = time(), mapID = mapID,
        x = position and position.x or nil,
        y = position and position.y or nil,
    }
    if not previous or time() - (previous.seenAt or 0) > 30 then
        ns.Events:Send("RARE_SIGHTING_CHANGED", npcID)
    end
    return previous == nil
end

function RareState:GetSightings()
    local character = ns.Database:GetCharacterData()
    return character and character.seenRares or {}
end

function RareState:GetProgress(npcIDs)
    local completed, total = 0, 0
    if npcIDs then
        for npcID in pairs(npcIDs) do
            total = total + 1
            if self:IsCompleted(npcID) then completed = completed + 1 end
        end
    else
        for _, record in ipairs(ns.Data.RareCatalog:GetAll()) do
            total = total + 1
            if self:IsCompleted(record.npcID) then completed = completed + 1 end
        end
    end
    return completed, total
end

-- Stable data only proves a named rare when no other record shares its skin.
function RareState:ReconcileStable(pets)
    local character = ns.Database:GetCharacterData()
    if not character then return 0 end
    local marked = 0
    for _, pet in ipairs(pets or {}) do
        local rare = pet.displayID
            and ns.Data.RareCatalog:GetUniqueByDisplayID(pet.displayID)
        if rare and not character.capturedRares[rare.npcID] then
            character.capturedRares[rare.npcID] = true
            marked = marked + 1
        end
    end
    if marked > 0 then ns.Events:Send("RARE_COLLECTION_CHANGED") end
    return marked
end

function RareState:OnEnable()
    ns.Events:RegisterEvent("PARTY_KILL", self,
        function(_, _, targetGUID)
            if (issecretvalue and issecretvalue(targetGUID))
                or not targetGUID then
                return
            end
            local npcID = CreatureIDFromGUID(targetGUID)
            if not npcID or not ns.Data.RareCatalog:Get(npcID) then return end
            self:MarkSeen(npcID)
            if self:SetKilled(npcID, true) then
                ns.Debug:Log("KILL", "recorded defeated rare %d", npcID)
            end
        end)

    local function OnTameStarted(_, unit, _, spellID)
        if unit == "player" and spellID == TAME_BEAST_SPELL_ID then
            RememberTameTarget()
        end
    end
    ns.Events:RegisterEvent("UNIT_SPELLCAST_START", self, OnTameStarted)
    ns.Events:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START", self, OnTameStarted)

    local function ResolveActiveRare()
        local npcID = CreatureIDFromGUID(UnitGUID("pet"))
        local rare = npcID and ns.Data.RareCatalog:Get(npcID)
        if rare then return rare, "npcID" end

        -- Some modern tames replace the wild NPC id in the Pet GUID. The
        -- freshly tamed pet keeps the rare's proper name, so a globally unique
        -- catalogue name plus a matching localized family is safe fallback
        -- evidence. Shared names are deliberately rejected by RareCatalog.
        local petName = UnitName("pet")
        rare = ns.Data.RareCatalog:GetUniqueByName(petName)
        if not rare then return nil end
        local family = rare.family and ns.Data.API:GetFamily(rare.family)
        local expectedFamily = family and ns.L[family.nameKey]
        local activeFamily = UnitCreatureFamily("pet")
        if activeFamily and expectedFamily and activeFamily ~= expectedFamily then
            return nil
        end

        return rare, "unique name"
    end

    local function CaptureActiveRare(expectedNPCID)
        local rare, evidence = ResolveActiveRare()
        if not rare or (expectedNPCID and rare.npcID ~= expectedNPCID) then
            return false
        end

        local candidate = pendingTame
        if candidate and candidate.npcID == rare.npcID then
            ClearPendingTame(candidate)
        end
        if self:SetCaptured(rare.npcID, true) then
            ns.Debug:Log("TAME", "captured rare %d from active pet (%s)",
                rare.npcID, evidence)
        end
        return true
    end

    local function ConfirmTame()
        local candidate = pendingTame
        if not candidate or GetTime() - candidate.startedAt > TAME_CONFIRM_WINDOW then
            ClearPendingTame(candidate)
            return false
        end

        return CaptureActiveRare(candidate.npcID)
    end

    local function SchedulePetChecks(requirePending)
        for _, delay in ipairs(PET_CONFIRM_DELAYS) do
            C_Timer.After(delay, requirePending and ConfirmTame or CaptureActiveRare)
        end
    end

    ns.Events:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED", self,
        function(_, unit, _, spellID)
            if unit ~= "player" or spellID ~= TAME_BEAST_SPELL_ID then return end
            SchedulePetChecks(true)
        end)

    ns.Events:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP", self,
        function(_, unit, _, spellID)
            if unit == "player" and spellID == TAME_BEAST_SPELL_ID then
                SchedulePetChecks(true)
            end
        end)

    ns.Events:RegisterEvent("UNIT_PET", self, function(_, unit)
        if unit == "player" then SchedulePetChecks(false) end
    end)

    -- UNIT_PET may precede the final Pet GUID and a reload has no pending tame
    -- at all. A known rare NPC id on the active pet is sufficient proof that
    -- this character owns it, so reconcile it independently of the cast.
    ns.Events:RegisterEvent("PLAYER_ENTERING_WORLD", self, function()
        SchedulePetChecks(false)
    end)

    local function OnTameFailed(_, unit, _, spellID)
        if unit == "player" and spellID == TAME_BEAST_SPELL_ID then
            pendingTame = nil
        end
    end
    ns.Events:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED", self, OnTameFailed)
    ns.Events:RegisterEvent("UNIT_SPELLCAST_FAILED", self, OnTameFailed)
    ns.Events:RegisterEvent("UNIT_SPELLCAST_FAILED_QUIET", self, OnTameFailed)
end
