-- ============================================================================
-- Vamoose's Companion - Pet Summoner
-- Auto-summons a companion's linked battle pet.
--
-- Desired-state reconciler: every trigger (config change, world event,
-- combat end, verify retry) funnels into ScheduleReconcile; Reconcile derives
-- the action from want vs current. Two one-shot flags carry transition
-- intents across combat: _force (want changed -- may replace a foreign pet)
-- and _dismiss (want cleared -- put the current pet away).
--
-- The _summoning cooldown is load-bearing, not paranoia:
-- GetSummonedPetGUID() has a ~0.5-1.5s nil window after SummonPetByGUID,
-- and a repeat SummonPetByGUID on the same GUID toggle-DISMISSES the pet.
-- ============================================================================

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

local RECONCILE_DELAY = 2.0

local function dbg(...)
    if 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 / no link)
    self._summoning = false -- inside the post-summon nil window; never re-enter
    self._retried   = false -- one verify-retry per target (login catch-up case)
    self._force     = false -- one-shot: want changed, may replace a foreign pet
    self._dismiss   = false -- one-shot: want cleared, dismiss whatever is out
    self._timer     = nil   -- debounce handle (cancel-replace)

    self:RefreshWant()

    -- Any state change: recompute want; a transition arms the one-shot intents
    VC.EventBus:Register("VC_STATE_CHANGED", function()
        local oldWant = self._wantGUID
        self:RefreshWant()
        if self._wantGUID == oldWant then return end
        dbg("want changed:", oldWant and oldWant:sub(-8) or "nil",
            "->", self._wantGUID and self._wantGUID:sub(-8) or "nil")
        self._retried = false
        self._force   = self._wantGUID ~= nil
        self._dismiss = self._wantGUID == nil and oldWant ~= nil
        self:ScheduleReconcile(0.1) -- user action: act now, not on the world-event debounce
    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)
        if event == "UNIT_EXITED_VEHICLE" and arg1 ~= "player" then return end
        dbg("event:", event)
        local cfg = VC.Store:GetState().config
        if event == "PLAYER_REGEN_ENABLED" then
            -- Always replay pending transition intents; ambient resummon
            -- after combat respects the autoResummon opt-in
            if self._force or self._dismiss or cfg.autoResummon then
                self:ScheduleReconcile()
            end
        elseif self._wantGUID and cfg.autoResummon then
            -- World dismissed the pet (zoning, pet battle, vehicle, dismount)
            self:ScheduleReconcile()
        end
    end)

    self:ScheduleReconcile() -- login catch-up
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:ScheduleReconcile(delay)
    if self._timer then self._timer:Cancel() end
    self._timer = C_Timer.NewTimer(delay or RECONCILE_DELAY, function()
        self._timer = nil
        self:Reconcile()
    end)
end

function VC.PetSummoner:Reconcile()
    if InCombatLockdown() then return end -- pending intents survive; REGEN reschedules
    -- M+ keeps secrets active between pulls while InCombatLockdown is false
    local mounted, flying = IsMounted(), IsFlying()
    if issecretvalue(mounted) or issecretvalue(flying) then return end -- exception(boundary): secret in M+ between pulls
    if self._summoning then return end

    local want, current = self._wantGUID, C_PetJournal.GetSummonedPetGUID()
    dbg("reconcile", "want:", want and want:sub(-8) or "nil",
        "cur:", current and current:sub(-8) or "nil",
        self._force and "[force]" or "", self._dismiss and "[dismiss]" or "")

    if self._dismiss then
        self._dismiss = false
        if not want and current then
            dbg("  -> dismissing (unlinked)")
            C_PetJournal.DismissSummonedPet(current)
            return
        end
    end
    if not want then return end
    if current == want then self._force = false; self._retried = false; return end
    if mounted or flying then return end -- summon would fail; _force stays armed for the next trigger
    if current and not self._force then return end -- don't stomp a manually summoned pet
    if not C_PetJournal.GetPetSummonInfo(want) then
        dbg("  -> not summonable")
        return
    end
    self._force = false

    -- Verify after the nil window. Scheduled BEFORE the API call so the
    -- cooldown always clears even if SummonPetByGUID errors.
    local verifyGUID = want
    C_Timer.After(1.5, function()
        self._summoning = false
        if self._wantGUID ~= verifyGUID then return end
        if C_PetJournal.GetSummonedPetGUID() == verifyGUID then
            self._retried = false
            dbg("  -> verify: out")
        elseif not self._retried then
            self._retried = true
            dbg("  -> verify: not out, one retry")
            self:ScheduleReconcile()
        else
            dbg("  -> verify: not out after retry, giving up")
        end
    end)
    dbg("  -> SUMMONING", want:sub(-8))
    self._summoning = true
    C_PetJournal.SummonPetByGUID(want)
end
