-- ============================================================================
-- Vamoose's Companion - Pet Summoner
-- Auto-summons a companion's linked battle pet
-- ============================================================================

VC = VC or {}
VC.PetSummoner = {}

local RETRY_DELAY = 2.0

local function dbg(...)
    if VC.Store and VC.Store:GetState().config.debug then
        print("|cFFA0522D[VC:Pet]|r", ...)
    end
end

function VC.PetSummoner:Initialize()
    self._wantGUID   = nil    -- GUID we want summoned (nil = feature off)
    self._postCombat = nil    -- nil | true | "force"
    self._lastCall   = 0      -- GetTime() of last SummonPetByGUID call (debug only)
    self._summoning  = false  -- true for ~1.5s after SummonPetByGUID; blocks re-entrant calls
    self._retries    = 0      -- verify-retry counter, reset on success/give-up/GUID change

    self:RefreshWant()

    -- Any state change: compare _wantGUID before/after RefreshWant.
    -- If unchanged, ignore (theme, portrait, toast, mute, etc. don't affect us).
    -- If changed, summon the new pet or dismiss the old one.
    VC.EventBus:Register("VC_STATE_CHANGED", function(data)
        if data.action ~= "SET_CONFIG" and data.action ~= "SET_LINKED_PET" then return end
        local oldWant = self._wantGUID
        self:RefreshWant()
        if self._wantGUID == oldWant then return end -- nothing changed for us
        self._retries = 0 -- new target, reset retry budget
        dbg("STATE_CHANGED", data.action,
            "old:", oldWant and oldWant:sub(-8) or "nil",
            "new:", self._wantGUID and self._wantGUID:sub(-8) or "nil")
        if self._wantGUID then
            self:TrySummon(true)
        elseif oldWant then
            if InCombatLockdown() then
                self._postCombat = "dismiss"
                dbg("  -> dismiss deferred (combat)")
            else
                local cur = C_PetJournal.GetSummonedPetGUID()
                if cur then
                    dbg("  -> DISMISSING (want cleared)")
                    C_PetJournal.DismissSummonedPet(cur)
                end
            end
        end
    end)

    local f = CreateFrame("Frame")
    f:RegisterEvent("PLAYER_ENTERING_WORLD")
    f:RegisterEvent("PET_BATTLE_CLOSE")
    f:RegisterEvent("UNIT_EXITED_VEHICLE")
    f:RegisterEvent("PLAYER_REGEN_ENABLED")
    f:RegisterEvent("PLAYER_MOUNT_DISPLAY_CHANGED")
    f:SetScript("OnEvent", function(_, event, arg1)
        dbg("EVENT", event,
            "want:", self._wantGUID and self._wantGUID:sub(-8) or "nil",
            "mounted:", IsMounted(), "summoning:", self._summoning)
        if event == "PLAYER_REGEN_ENABLED" then
            if self._postCombat == "dismiss" then
                self._postCombat = nil
                if not self._wantGUID then
                    local cur = C_PetJournal.GetSummonedPetGUID()
                    if cur then
                        dbg("  -> executing deferred dismiss")
                        C_PetJournal.DismissSummonedPet(cur)
                    end
                elseif self._wantGUID then
                    -- User linked a new pet during combat while dismiss was pending
                    C_Timer.After(RETRY_DELAY, function() self:TrySummon(true) end)
                end
            elseif self._postCombat then
                local force = self._postCombat == "force"
                self._postCombat = nil
                C_Timer.After(RETRY_DELAY, function() self:TrySummon(force) end)
            elseif self._wantGUID and VC.Store:GetState().config.autoResummon
                   and not C_PetJournal.GetSummonedPetGUID() then
                C_Timer.After(RETRY_DELAY, function() self:TrySummon() end)
            end
            return
        end
        if not self._wantGUID then return end
        if event == "PLAYER_MOUNT_DISPLAY_CHANGED" then
            if not VC.Store:GetState().config.autoResummon then return end
            if IsMounted() then return end -- mounting, pet dismissed by WoW
            C_Timer.After(RETRY_DELAY, function() self:TrySummon() end)
        elseif event == "PLAYER_ENTERING_WORLD" or event == "PET_BATTLE_CLOSE"
            or (event == "UNIT_EXITED_VEHICLE" and arg1 == "player") then
            if not VC.Store:GetState().config.autoResummon then return end
            C_Timer.After(RETRY_DELAY, function() self:TrySummon() end)
        end
    end)

    -- Catch-up: Initialize runs after login via C_Timer
    C_Timer.After(RETRY_DELAY, function() self:TrySummon() end)
end

function VC.PetSummoner:RefreshWant()
    local state = VC.Store:GetState()
    if state.config.autoSummonEnabled then
        self._wantGUID = state.config.linkedPets[state.config.activeCompanion]
    else
        self._wantGUID = nil
    end
end

function VC.PetSummoner:TrySummon(force)
    local want = self._wantGUID
    if not want then return end
    local current = C_PetJournal.GetSummonedPetGUID()
    dbg("TrySummon", "force:", force and "YES" or "no",
        "cur:", current and current:sub(-8) or "nil",
        "want:", want:sub(-8), "summoning:", self._summoning,
        "combat:", InCombatLockdown())
    if InCombatLockdown() then
        self._postCombat = force and "force" or self._postCombat or true
        dbg("  -> deferred (combat)")
        return
    end
    if IsMounted() or IsFlying() then
        dbg("  -> skip (mounted/flying)")
        return
    end
    if current == want then dbg("  -> skip (already out)"); return end
    if current and not force then dbg("  -> skip (different pet, no force)"); return end
    if self._summoning then dbg("  -> skip (summon in progress)"); return end
    local isSummonable, err, errText = C_PetJournal.GetPetSummonInfo(want)
    if not isSummonable then
        dbg("  -> skip (not summonable:", errText or err or "unknown", ")")
        return
    end
    -- Schedule verify timer BEFORE the API call so _summoning is always cleared
    -- even if SummonPetByGUID errors (prevents permanent _summoning=true lockout)
    local verifyGUID = want
    local retryNum = self._retries + 1
    C_Timer.After(1.5, function()
        self._summoning = false
        if InCombatLockdown() then return end -- TrySummon will re-evaluate after combat
        if self._wantGUID ~= verifyGUID then
            self._retries = 0
            return -- GUID changed or cleared, stale verify
        end
        local now = C_PetJournal.GetSummonedPetGUID()
        if now == verifyGUID then
            dbg("  -> verify: confirmed out")
            self._retries = 0
        elseif retryNum < 3 then
            dbg("  -> verify: NOT out, retry", retryNum)
            self._retries = retryNum
            self:TrySummon()
        else
            dbg("  -> verify: NOT out after", retryNum, "retries, giving up")
            self._retries = 0
        end
    end)
    dbg("  -> SUMMONING", want:sub(-8))
    self._summoning = true
    self._lastCall = GetTime()
    C_PetJournal.SummonPetByGUID(want)
end
