BladW = BladW or {}
BladW.Announcement = BladW.Announcement or {}

-- pour que les clients téléchargent le son
resource.AddFile("sound/announce.mp3")

-- coupe une chaîne à maxChars caractères (propre avec les accents)
local function LimitChars(str, maxChars)
    str = tostring(str or "")
    local n = utf8.len(str)
    if not n or n <= maxChars then return str end
    local endByte = utf8.offset(str, maxChars + 1)
    return endByte and string.sub(str, 1, endByte - 1) or str
end

-- ouvre le menu pour le joueur (faut avoir le droit)
concommand.Add("bladw_amenu", function(ply, cmd)
    if not IsValid(ply) or not BladW.Announcement.HasAccess(ply) then return end

    net.Start("bladWOpenAnnouncementMenu")
    net.Send(ply)
end)

-- /bannonce (ou !bannonce) dans le chat = ouvre le menu
hook.Add("PlayerSay", "BladW:AnnouncementChatCmd", function(ply, text)
    local cmd = string.lower(string.Trim(text))
    if cmd ~= "/bannonce" and cmd ~= "!bannonce" then return end

    -- que ceux qui ont le droit ouvrent le menu, mais on cache la commande pour tout le monde
    if IsValid(ply) and BladW.Announcement.HasAccess(ply) then
        net.Start("bladWOpenAnnouncementMenu")
        net.Send(ply)
    end

    return "" -- commande reconnue -> on la retire du chat
end)

-- reconstruit l'annonce finale : le preset sert de base, title/text passent devant
function BladW.Announcement.Resolve(entry)
    local presets = BladW.Announcement.Presets or {}
    local base    = (entry.preset and presets[entry.preset]) or {}

    return {
        title    = (entry.title and entry.title ~= "" and entry.title) or base.title or "Annonce",
        text     = (entry.text  and entry.text  ~= "" and entry.text ) or base.text  or "",
        style    = entry.style or base.style or "",
        duration = tonumber(entry.duration) or tonumber(base.duration) or 0,
        target   = entry.target or base.target, -- usergroup ciblé (nil = tout le monde)
    }
end

-- envoie l'annonce à tout le monde. false si rien à envoyer ou si une annonce est déjà en cours
function BladW.Announcement.Broadcast(entry)
    -- déjà une annonce à l'écran ? on n'en empile pas une autre
    if CurTime() < (BladW.Announcement.activeUntil or 0) then return false end

    local a = BladW.Announcement.Resolve(entry)
    if a.text == "" then return false end

    -- destinataires (nil = tout le monde) :
    --  liste de joueurs (menu) > liste de groupes (menu) + target unique (preset/schedule)
    local recipients
    if istable(entry.players) and #entry.players > 0 then
        recipients = {}
        for _, p in ipairs(entry.players) do
            if IsValid(p) and p:IsPlayer() then recipients[#recipients + 1] = p end
        end
    elseif (istable(entry.groups) and #entry.groups > 0)
        or (a.target and a.target ~= "" and a.target ~= "all") then
        local set = {}
        if istable(entry.groups) then
            for _, g in ipairs(entry.groups) do set[g] = true end
        end
        if a.target and a.target ~= "" and a.target ~= "all" then set[a.target] = true end

        recipients = {}
        for _, p in ipairs(player.GetAll()) do
            if set[p:GetUserGroup()] then recipients[#recipients + 1] = p end
        end
    end

    if recipients and #recipients == 0 then return false end -- personne à qui envoyer

    local Conf   = BladW.Announcement.Conf or {}
    local maxLen = tonumber(Conf.announcement_max_length) or 120
    local dur    = a.duration > 0 and a.duration or (tonumber(Conf.announcement_duration) or 10)

    -- on bloque les suivantes le temps que celle-ci s'affiche
    BladW.Announcement.activeUntil = CurTime() + dur

    net.Start("bladWShowAnnouncement")
        net.WriteString(LimitChars(a.title, 64))
        net.WriteString(LimitChars(a.text, maxLen))
        net.WriteString(a.style or "")
        net.WriteUInt(math.Clamp(a.duration, 0, 65535), 16)
    if recipients then net.Send(recipients) else net.Broadcast() end

    return true
end

-- un admin veut poster une annonce -> je la diffuse
net.Receive("bladWSendAnnouncement", function(len, ply)
    if not IsValid(ply) or not BladW.Announcement.HasAccess(ply) then return end

    local preset = net.ReadString()
    local title  = net.ReadString()
    local text   = net.ReadString()

    -- destinataires : "all" / "group" (liste de groupes) / "players" (liste de joueurs)
    -- le choix du menu est explicite -> il prime sur le target du preset
    local scope = net.ReadString()
    local target, groups, players = "all", nil, nil
    if scope == "group" then
        target = "" -- vide = on ignore le target du preset, on prend la liste
        groups = {}
        for _ = 1, net.ReadUInt(8) do groups[#groups + 1] = net.ReadString() end
    elseif scope == "players" then
        target = ""
        players = {}
        for _ = 1, net.ReadUInt(8) do
            local p = net.ReadEntity()
            if IsValid(p) and p:IsPlayer() then players[#players + 1] = p end
        end
    end

    local ok = BladW.Announcement.Broadcast({
        preset  = preset ~= "" and preset or nil,
        title   = title,
        text    = text,
        target  = target,
        groups  = groups,
        players = players,
    })

    -- je réponds à l'admin : publié -> il ferme le menu, refusé -> il garde son texte
    local left = math.Clamp(math.ceil((BladW.Announcement.activeUntil or 0) - CurTime()), 0, 255)
    net.Start("bladWAnnouncementResult")
        net.WriteBool(ok)
        net.WriteUInt(left, 8)
    net.Send(ply)
end)
