---@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: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

-- BigWigs_StartBar dispatch: every bar BigWigs creates lands here.
--
-- The leading `event` parameter is LOAD-BEARING, not decoration. This is NOT Ace3's
-- CallbackHandler -- BigWigs' loader carries its own hand-rolled callbackMap and invokes a
-- callback registered as a plain function via `securecallfunction(v, event, ...)`
-- (BigWigs/Loader.lua:1645), i.e. it PREPENDS the message name to the payload. Omitting the
-- parameter shifts every argument one slot right (module="BigWigs_StartBar",
-- key=<plugin table>, text=nil, duration=<bar text>, icon=<seconds>), which made the guard
-- below false for every bar and silently disabled the whole feature. BigWigs' own consumer
-- declares it the same way -- BigWigs_Plugins/Bars.lua:1903
-- `function plugin:BigWigs_StartBar(_, module, key, text, time, icon, ...)`.
--
-- Real payload for a `/break 10` (BigWigs_Plugins/Break.lua:150):
--   module = the Break plugin table, key = nil, text = L.breakBar ("Break time" enUS /
--   "Pause" deDE), duration = 600 (Break.lua:185 converts minutes*60), icon = 134062.
-- Break bars therefore carry NO key -- only `text` identifies them, which is what the
-- localisation match does. The `key == "break"` limb cannot match a BigWigs break bar and
-- is kept solely for custom/WIP boss modules that set a string key themselves (comparing
-- a numeric spellID key against a string is simply false in Lua, never an error).
--
-- 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).
--
-- Registration is idempotent and unconditional: EnsureBreakBarRegistered()
-- runs at file scope and re-runs from PLAYER_ENTERING_WORLD so a BigWigs
-- load that completes after Core.lua is still picked up. Once registered,
-- the subscription stays for the whole session -- the feature is purely
-- local and there is no path that calls UnregisterMessage.
local breakBarRegistered = false

local function EnsureBreakBarRegistered()
    if breakBarRegistered or not BigWigsLoader then return end
    BigWigsLoader.RegisterMessage(WTAPI, "BigWigs_StartBar", function(...)
        mainFrame:BigWigs_StartBar(...)
    end)
    breakBarRegistered = true
end

EnsureBreakBarRegistered()

function mainFrame:BigWigs_StartBar(event, 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)
    EnsureBreakBarRegistered()

    if isInitialLogin and wt:CheckPartyModeRequirements() then
        print("|cff99ff00Wild Things:|r ", "|cffff0000Attenzione:|r You are Raid Lead and have Party Mode enabled.")
    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 IsInRaid() then return end
    if not WildThingsSavedVars.update.memes then return end

    local text = ...
    -- Midnight secret values (RosterKeys.lua's Private.HasSecretValue): comparing
    -- `text` raises when the payload is secret. CHAT_MSG_YELL in particular fires
    -- for arbitrary nearby players, whose identity -- and with it their chat
    -- payload -- is secret on instanced maps and in combat.
    if Private.HasSecretValue(text) then return end

    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")

-- Chat events feeding OnMemeChat: registered here, unconditionally, and
-- deliberately NOT inside an activation gate. The previous gate was
-- Private.CheckInstanceID(instanceID), which has been always-false since
-- 88d79da deleted DefaultNotes/ (the only writer of Private.activeInstances).
-- The handler (mainFrame.CHAT_MSG_* = OnMemeChat below) gates on
-- WildThingsSavedVars.update.memes, and Private.OnMemeCommand gates on
-- author identity, combat and the 7s cooldown -- no instance restriction
-- intended (matches quick-task 260728-50i's "anyone in the raid" scope).
-- Pattern follows worktree-debug-meme-ad-chat-commands (commit 0e5d510).
mainFrame:RegisterEvent("CHAT_MSG_RAID")
mainFrame:RegisterEvent("CHAT_MSG_RAID_LEADER")
mainFrame:RegisterEvent("CHAT_MSG_YELL")

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