---@diagnostic disable: undefined-field, undefined-global
---@type string
local AddonName = ...
---@class Private
local Private = select(2, ...)
local wt = Private.Ace
---@class WTAPI
local WTAPI = WTAPI

-- The addon's core game-event + comm-dispatch hub: owns the two OnEvent
-- trampolines (mainFrame, encounterFrame) that route Blizzard game events to
-- named handler methods, and registers the addon's PARTY/PARTY_POLICE comm
-- handlers via Private:RegisterCommEvent.
--
-- Expected Private.* dependencies: Private.Ace (wt, from WildThings.lua),
-- Private.CheckInstanceID (instance allowlist check),
-- Private:RegisterCommEvent (from Init.lua), and the error-containment
-- helper added to Utility.lua in 24-01 (wraps every OnEvent trampoline
-- below) -- all must be defined before this file loads or before the
-- relevant handler fires (verify actual load order in WildThings.toc).
--
-- Timing: mainFrame and encounterFrame are created and SetScript'd for
-- OnEvent at file-load time, but their handlers only fire after login/reload
-- once the relevant events are registered and dispatched by the WoW client.
-- encounterFrame itself is created lazily, on the first ENCOUNTER_START.
-- The RegisterCommEvent calls below run at file-load time; the actual
-- AceComm:RegisterComm("WT_MSG", ...) wiring happens later, from
-- wt:OnInitialize() (see Transmission.lua).
local mainFrame = nil
if not mainFrame then mainFrame = CreateFrame("Frame") end

local encounterFrame

local moduleLoaded = false

-- BigWigs_StartBar(module, key, text, duration, icon) dispatches every bar BigWigs
-- creates. Break timers are identified by key == "break" (canonical convention) or by
-- text matching "break"/"pause" (localisation fallback for custom/WIP boss modules).
-- Dispatches to Private:OnBreakTimerStart(duration) which BreakReminder.lua defines at
-- runtime -- the function only needs to exist when a bar actually fires, not at
-- file-load time (the standard Core.lua → feature-module dispatch pattern).
function mainFrame:BigWigs_StartBar(module, key, text, duration, icon)
    if key == "break" or (text and (text:lower():match("break") or text:lower():match("pause"))) then
        if Private.OnBreakTimerStart then
            Private:OnBreakTimerStart(duration)
        end
    end
end

function mainFrame:OnEvent(event, ...)
    Private.safecall(self[event], self, event, ...)
end

function mainFrame:PLAYER_ENTERING_WORLD(event, isInitialLogin, isReloadingUi)
    local _, _, _, _, _, _, _, instanceID, _, _ = GetInstanceInfo()

    if Private.CheckInstanceID(instanceID) then
        if BigWigsLoader then
            BigWigsLoader.RegisterMessage(WTAPI, "BigWigs_StartBar", function(...) mainFrame:BigWigs_StartBar(...) end)
            moduleLoaded = true
            print("|cff99ff00Wild Things:|r ", "AddOn activated.")

            if wt:CheckPartyModeRequirements() then
                print("|cff99ff00Wild Things:|r ", "|cffff0000Attenzione:|r You are Raid Lead and have Party Mode enabled.")
            end

            mainFrame:RegisterEvent("CHAT_MSG_RAID")
            mainFrame:RegisterEvent("CHAT_MSG_RAID_LEADER")
            mainFrame:RegisterEvent("CHAT_MSG_YELL")
        end
    else
        if BigWigsLoader and moduleLoaded then
            BigWigsLoader.UnregisterMessage(WTAPI, "BigWigs_StartBar")
            moduleLoaded = false
            print("|cff99ff00Wild Things:|r ", "AddOn deactivated.")

            mainFrame:UnregisterEvent("CHAT_MSG_RAID")
            mainFrame:UnregisterEvent("CHAT_MSG_RAID_LEADER")
            mainFrame:UnregisterEvent("CHAT_MSG_YELL")
        end
    end
end

function mainFrame:ENCOUNTER_START(event, ...)
    if encounterFrame == nil then
        encounterFrame = CreateFrame("Frame")

        function encounterFrame:ENCOUNTER_START(encounterID, encounterName, difficultyID, groupSize)
            if not WTAPI then return end

            if wt:CheckPartyModeRequirements() then
                wt:PartyMode_EncounterStart()
            end
        end

        function encounterFrame:ENCOUNTER_END(encounterID, encounterName, difficultyID, groupSize, success)
            encounterFrame:UnregisterAllEvents()

            if WildThingsSavedVars.update.party_mode then
                local index = UnitInRaid("player")
                if index then
                    local rank = select(2, GetRaidRosterInfo(index))
                    if rank == 2 then
                        wt:PartyMode_EncounterEnd()
                    end
                end
            end
        end

        -- Trampoline required because encounterFrame:SetScript("OnEvent", ...) below referenced
        -- an undefined handler, so ENCOUNTER_END/PLAYER_ENTERING_WORLD never dispatched. Unlike
        -- mainFrame's trampoline, this one must NOT forward the event name: encounterFrame's
        -- methods use positional signatures (ENCOUNTER_START/ENCOUNTER_END take the WoW event
        -- payload args directly, no leading event string), matching the direct
        -- encounterFrame:ENCOUNTER_START(...) call below. PLAYER_ENTERING_WORLD is registered
        -- but has no method here, so nil-guard the lookup rather than erroring on call.
        function encounterFrame:OnEvent(event, ...)
            local handler = self[event]
            Private.safecall(handler, self, ...)
        end

        encounterFrame:SetScript("OnEvent", encounterFrame.OnEvent)
    end

    encounterFrame:RegisterEvent("ENCOUNTER_END")
    encounterFrame:RegisterEvent("PLAYER_ENTERING_WORLD")

    encounterFrame:ENCOUNTER_START(...)
end

-- READY-05: cooldown check on raid ready-checks. Vision-Angleichung:
-- the CHAT/DECLINE trigger gate (raid + difficulty + trigger_mode +
-- wt_only) is centralized in Private:ShouldRunReadyCheckCooldown; the
-- BROADCAST leg (cooldown window popup) has its own gate in
-- Private:ShouldRunReadyCheckCooldownBroadcast which omits the
-- trigger_mode check -- so a sender with trigger_mode="disabled" still
-- contributes their cooldown data to the WT-Blackhand raid lead's
-- popup (user clarification 2026-07-26: "das disabled betrifft nur
-- seine Seite, also das in Chat schreiben"). Core.lua gates the
-- initial READY_CHECK AND the post-combat PLAYER_REGEN_ENABLED
-- resume via the BROADCAST gate (so a player who left the raid
-- during combat, dropped back to Normal, or whose leader's guild
-- check now fails does NOT get a stale post-combat broadcast) --
-- the chat/decline leg inside RunCooldownCheck re-gates via
-- ShouldRunReadyCheckCooldown, so a player with trigger_mode="disabled"
-- OR both chat toggles off still skips the local chat writing. If a
-- READY_CHECK fires while the player is in combat, the work is deferred
-- by setting `pendingCooldownCheck` and re-firing PLAYER_REGEN_ENABLED
-- after combat ends (mirrors the RosterArrange:pendingCombatArrange
-- defer pattern) -- applies to BOTH legs since both live inside
-- RunCooldownCheck. All handlers run through this file's existing
-- safecall-wrapped OnEvent trampoline above -- no new frame/wrapping
-- needed.

-- Session-local pending flag: a READY_CHECK arrived while in combat, defer
-- the cooldown check until combat ends. Not SavedVars-persisted (unlike
-- RosterArrange's pending_arrange) -- a reload-during-combat would
-- re-resolve the *next* READY_CHECK via the live gate, there's no source
-- value to replay.
local pendingCooldownCheck = false

function mainFrame:READY_CHECK(event, initiatorName, readyCheckTimeLeft)
    -- WR-01 fix: use the BROADCAST gate here (no trigger_mode check) so the
    -- broadcast leg fires even when trigger_mode="disabled". The chat/decline
    -- path inside RunCooldownCheck re-gates via ShouldRunReadyCheckCooldown
    -- (which DOES include trigger_mode) so a sender with disabled mode still
    -- skips the local chat writing -- "das disabled betrifft nur seine Seite,
    -- also das in Chat schreiben" (user clarification 2026-07-26).
    if not Private:ShouldRunReadyCheckCooldownBroadcast() then return end

    if InCombatLockdown() then
        pendingCooldownCheck = true
        return
    end

    Private:RunCooldownCheck()
end

function mainFrame:READY_CHECK_FINISHED(event, preempted)
    Private:ResetCooldownThrottle()
    pendingCooldownCheck = false
end

-- Deferred resume: re-fire the SAME broadcast gate (raid may have been
-- left, difficulty changed) before re-issuing the deferred broadcast +
-- chat/decline (RunCooldownCheck handles its own chat/decline gating
-- internally). If the gate now fails, silently drop the pending state.
function mainFrame:PLAYER_REGEN_ENABLED(event)
    if not pendingCooldownCheck then return end
    pendingCooldownCheck = false
    if Private:ShouldRunReadyCheckCooldownBroadcast() then
        Private:RunCooldownCheck()
    end
end

-- ACCESS-03: live guild-rank re-evaluation bridge. mainFrame is file-local to
-- this file, so Access.lua (which defines Private:ReEvaluateOwnRank) cannot
-- register these events itself -- this trampoline hosts them and delegates,
-- mirroring the READY_CHECK -> Private:RunCooldownCheck() shape above. Both
-- take a leading `event` param since they ride mainFrame:OnEvent (which
-- forwards the event string), unlike encounterFrame's no-event-arg handlers.
function mainFrame:PLAYER_GUILD_UPDATE(event, unitTarget)
    Private:ReEvaluateOwnRank()
end

function mainFrame:GUILD_ROSTER_UPDATE(event)
    Private:ReEvaluateOwnRank()
end

-- LIVE-01/LIVE-02: persistent, whole-session ENCOUNTER_END observer, distinct
-- from the lazy encounterFrame:ENCOUNTER_END above (positional signature,
-- assignment-feature-scoped, created only after the first ENCOUNTER_START of
-- the session, unregistered/re-registered every cycle). Two frames
-- independently registering ENCOUNTER_END is safe -- each gets its own
-- dispatch (WoW fires the event to every registered frame).
function mainFrame:ENCOUNTER_END(event, encounterID, encounterName, difficultyID, groupSize, success)
    Private:HandleTimelineEncounterEnd(encounterID, difficultyID, success)
end

Private:RegisterCommEvent("PARTY", function(sender, sound, duration)
    if wt:CheckPartyModeReceiverRequirements() then
        wt:ActivateParty(sound, duration)
    end
end)

Private:RegisterCommEvent("PARTY_POLICE", function(sender, name, duration)
    if wt:CheckPartyModeReceiverRequirements() then
        wt:ActivatePartyPolice(name, duration)
    end
end)

local function OnMemeChat(self, event, ...)
    if not WildThingsSavedVars.update.memes then return end

    local text = ...
    if text == "!ad" then
        wt:ActivateAd(6.8)
    elseif text == "!meme" then
        -- Existence-guarded so a load-order regression degrades to a no-op instead
        -- of a nil call (Memes.lua/Private.OnMemeCommand -- MEME-01..07).
        if Private.OnMemeCommand then
            Private.OnMemeCommand(...)
        end
    end
end
mainFrame.CHAT_MSG_RAID = OnMemeChat
mainFrame.CHAT_MSG_RAID_LEADER = OnMemeChat
mainFrame.CHAT_MSG_YELL = OnMemeChat

mainFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
mainFrame:RegisterEvent("ENCOUNTER_START")
mainFrame:RegisterEvent("READY_CHECK")
mainFrame:RegisterEvent("READY_CHECK_FINISHED")
mainFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
mainFrame:RegisterEvent("PLAYER_GUILD_UPDATE")
mainFrame:RegisterEvent("GUILD_ROSTER_UPDATE")
mainFrame:RegisterEvent("ENCOUNTER_END")

mainFrame:SetScript("OnEvent", mainFrame.OnEvent)
