
local AceSerializer = assert(LibStub('AceSerializer-3.0', true), 'BroadHandler-1.0 requires AceSerializer-3.0')
local CallbackHandler = assert(LibStub('CallbackHandler-1.0', true), 'BroadHandler-1.0 requires CallbackHandler-1.0')
local CTL = assert(ChatThrottleLib, 'BroadHandler-1.0 requires ChatThrottleLib')
local Base64 = assert(LibStub('NetEaseBase64-1.0', true), 'BroadHandler-1,0 requires NetEaseBase64-1.0')

local MAJOR, MINOR = 'NetEaseBroadMiddleware-2.0', 2
local BroadMiddleware,oldminor = LibStub:NewLibrary(MAJOR, MINOR)
if not BroadMiddleware then return end

local unpack = unpack
local CanAccessValue = canaccessvalue
local IsSecretValue = issecretvalue

local PACKET_FIRST = '#'
local PACKET_NEXT  = '&'
local PACKET_LAST  = '$'
local PACKET_ONE   = '@'
local MAX_LEN = 254

local msgCacheMeta = {__index = function(t, k)
    t[k] = {}
    return t[k]
end}

BroadMiddleware.EventHandler = BroadMiddleware.EventHandler or CreateFrame('Frame')

BroadMiddleware.channels = BroadMiddleware.channels or {}
BroadMiddleware.handlers = BroadMiddleware.handlers or setmetatable({}, {
    __index = function(t, k)
        t[k] = {}
        t[k].msgCache = setmetatable({}, msgCacheMeta)
        t[k].FireBroad = CallbackHandler:New(t[k], 'RegisterBroad', 'UnregisterBroad', 'UnregisterAllBroad').Fire
        t[k].FireServerBroad = CallbackHandler:New(t[k], 'RegisterServerBroad', 'UnregisterServerBroad', 'UnregisterAllServerBroad').Fire
        return t[k]
    end
})

local handlers = BroadMiddleware.handlers
local channels = BroadMiddleware.channels
local EventHandler = BroadMiddleware.EventHandler

local function IsBroadSecretValue(value)
    if type(CanAccessValue) == 'function' then
        local ok, canAccess = pcall(CanAccessValue, value)
        if not ok or not canAccess then
            return true
        end
    end

    if type(IsSecretValue) == 'function' then
        local ok, isSecret = pcall(IsSecretValue, value)
        if not ok or isSecret then
            return true
        end
    end

    return false
end

local function IsBroadSafeString(value)
    if value == nil or IsBroadSecretValue(value) then
        return false
    end
    return type(value) == 'string' and value ~= ''
end

local function IsBroadValidChannelName(channelName)
    return IsBroadSafeString(channelName)
        and not channelName:find('%z')
        and not channelName:find('-', 1, true)
end

local function IsBroadSafeCommand(cmd)
    return IsBroadSafeString(cmd) and #cmd <= 64
end

local function Pack(...)
    return { n = select('#', ...), ... }
end

local function WipeTable(tbl)
    if not tbl then
        return
    end
    if wipe then
        wipe(tbl)
        return
    end
    for key in pairs(tbl) do
        tbl[key] = nil
    end
end

local function NormalizePlayerName(name)
    if not IsBroadSafeString(name) then
        return nil
    end
    local ok, normalized = pcall(Ambiguate, name, 'none')
    if ok and IsBroadSafeString(normalized) then
        return normalized
    end
end

local function GetChannelId(channelName)
    if not IsBroadValidChannelName(channelName) or type(GetChannelName) ~= 'function' then
        return nil
    end
    local ok, channelId = pcall(GetChannelName, channelName)
    if ok and type(channelId) == 'number' and channelId > 0 then
        return channelId
    end
end

local function ResetHandlerCaches(handler)
    if not handler or type(handler.msgCache) ~= 'table' then
        return
    end

    for _, cache in pairs(handler.msgCache) do
        if type(cache) == 'table' then
            WipeTable(cache)
        end
    end
end

EventHandler:UnregisterAllEvents()
EventHandler:RegisterEvent('CHAT_MSG_CHANNEL')
EventHandler:SetScript('OnEvent', function(self, event, msg, target, _, _, _, flag, _, channelId, channelName)
    if not IsBroadValidChannelName(channelName) then
        return
    end

    local channelUsers = channels[channelName]
    if type(channelUsers) ~= 'table' then
        return
    end

    local handler = rawget(handlers, channelName)
    if not handler then
        return
    end

    if InCombatLockdown and InCombatLockdown() then
        ResetHandlerCaches(handler)
        BroadMiddleware.combatSkippedPackets = (BroadMiddleware.combatSkippedPackets or 0) + 1
        return
    end

    if not IsBroadSafeString(msg) then
        ResetHandlerCaches(handler)
        BroadMiddleware.secretStringSkippedPackets = (BroadMiddleware.secretStringSkippedPackets or 0) + 1
        return
    end

    local ok, control, data = pcall(string.match, msg, '^([#&$@])(.*)$')
    if not ok then
        ResetHandlerCaches(handler)
        BroadMiddleware.secretStringSkippedPackets = (BroadMiddleware.secretStringSkippedPackets or 0) + 1
        return
    end
    if not control or not IsBroadSafeString(data) then
        return
    end

    target = NormalizePlayerName(target)
    if not target then
        ResetHandlerCaches(handler)
        return
    end

    local cache = handler.msgCache[target]

    if control == PACKET_FIRST then
        tinsert(WipeTable(cache) or cache, data)
    elseif control == PACKET_NEXT or control == PACKET_LAST then
        tinsert(cache, data)
    end

    if control == PACKET_LAST or control == PACKET_ONE then
        if control == PACKET_LAST then
            data = tconcat(cache)
            WipeTable(cache)
        end
        self:DealPacket(handler, target, flag, self:Decode(data))
    end
end)

function EventHandler:Decode(data)
    if not IsBroadSafeString(data) then
        return false
    end

    local decodeOK, decoded = pcall(Base64.DeCode, Base64, data)
    if not decodeOK or type(decoded) ~= 'string' then
        return false
    end

    local result = Pack(pcall(AceSerializer.Deserialize, AceSerializer, decoded))
    if not result[1] then
        return false
    end
    return unpack(result, 2, result.n)
end

function EventHandler:Encode(...)
    local serializeOK, serialized = pcall(AceSerializer.Serialize, AceSerializer, ...)
    if not serializeOK or type(serialized) ~= 'string' then
        return false
    end

    local encodeOK, encoded = pcall(Base64.EnCode, Base64, serialized)
    if not encodeOK or type(encoded) ~= 'string' then
        return false
    end

    return true, encoded
end

function EventHandler:DealPacket(handler, target, flag, ok, cmd, ...)
    if not ok or not IsBroadSafeCommand(cmd) or not IsBroadSafeString(target) then
        return
    end
    if not handler then
        return
    end

    local serverBroad = IsBroadSafeString(flag) and flag == 'GM'
    if serverBroad then
        if type(handler.FireServerBroad) == 'function' then
            handler:FireServerBroad(cmd, ...)
        end
    elseif type(handler.FireBroad) == 'function' then
        handler:FireBroad(cmd, target, ...)
    end
end

function BroadMiddleware:Listen(channelName)
    if not IsBroadValidChannelName(channelName) then
        return false, 'INVALID_CHANNEL'
    end

    local old = channels[self]
    if old == channelName then
        local handler = handlers[channelName]
        self.RegisterBroad = handler.RegisterBroad
        self.UnregisterBroad = handler.UnregisterBroad
        self.UnregisterAllBroad = handler.UnregisterAllBroad
        self.RegisterServerBroad = handler.RegisterServerBroad
        self.UnregisterServerBroad = handler.UnregisterServerBroad
        self.UnregisterAllServerBroad = handler.UnregisterAllServerBroad
        self.SendBroad = BroadMiddleware.Send
        return true
    elseif IsBroadValidChannelName(old) then
        local oldHandler = rawget(handlers, old)
        if oldHandler then
            oldHandler.UnregisterAllBroad(self)
            oldHandler.UnregisterAllServerBroad(self)
        end
        if type(channels[old]) == 'table' then
            channels[old][self] = nil
            if not next(channels[old]) then
                channels[old] = nil
            end
        end
    end

    local handler = handlers[channelName]

    self.RegisterBroad = handler.RegisterBroad
    self.UnregisterBroad = handler.UnregisterBroad
    self.UnregisterAllBroad = handler.UnregisterAllBroad
    self.RegisterServerBroad = handler.RegisterServerBroad
    self.UnregisterServerBroad = handler.UnregisterServerBroad
    self.UnregisterAllServerBroad = handler.UnregisterAllServerBroad
    self.SendBroad = BroadMiddleware.Send

    channels[channelName] = channels[channelName] or {}
    channels[channelName][self] = true
    channels[self] = channelName
    return true
end

function BroadMiddleware:Send(cmd, ...)
    if not IsBroadSafeCommand(cmd) then
        return false, 'INVALID_CMD'
    end

    local channelName = channels[self]
    if not IsBroadValidChannelName(channelName) then
        return false, 'INVALID_CHANNEL'
    end

    local channelId = GetChannelId(channelName)
    if type(channelId) ~= 'number' then
        return false, 'NO_CHANNEL_ID'
    end

    local encodeOK, data = EventHandler:Encode(cmd, ...)
    if not encodeOK then
        return false, 'ENCODE_FAILED'
    end

    local chatType = 'CHANNEL'
    local dataLen = #data
    local queueName = cmd

    local function sendPacket(packet)
        return pcall(CTL.SendChatMessage, CTL, 'NORMAL', cmd, packet, chatType, nil, channelId, queueName)
    end

    if dataLen < MAX_LEN then
        local ok, reason = sendPacket(PACKET_ONE .. data)
        return ok, reason
    end

    local chunk = strsub(data, 1, MAX_LEN)
    local ok, reason = sendPacket(PACKET_FIRST .. chunk)
    if not ok then
        return false, reason
    end

    local pos = 1 + MAX_LEN
    while pos + MAX_LEN <= dataLen do
        chunk = strsub(data, pos, pos + MAX_LEN - 1)
        ok, reason = sendPacket(PACKET_NEXT .. chunk)
        if not ok then
            return false, reason
        end
        pos = pos + MAX_LEN
    end

    chunk = strsub(data, pos)
    ok, reason = sendPacket(PACKET_LAST .. chunk)
    if not ok then
        return false, reason
    end

    return true
end
