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

-- planificateur d'annonces (intervalle + cron), lit BladW.Announcement.Schedules

local TIMER_PREFIX = "BladW_Announcement_Sched_"
local CRON_TIMER   = "BladW_Announcement_Cron"

-- je garde les noms des timers sur la table pour bien les nettoyer au reload
BladW.Announcement._timers = BladW.Announcement._timers or {}

local function Warn(msg)
    MsgC(Color(255, 200, 50), "[BladW Annonce] " .. msg .. "\n")
end

-- "30m" / "90s" / "2h" / "1d" -> secondes
local function ParseInterval(str)
    if isnumber(str) then return str end
    if not isstring(str) then return nil end

    local num = tonumber(string.match(str, "^%s*([%d%.]+)"))
    if not num then return nil end

    local unit = string.lower(string.match(str, "([smhd])%s*$") or "s")
    local mult = ({ s = 1, m = 60, h = 3600, d = 86400 })[unit] or 1
    return num * mult
end

-- un champ cron ("*", "*/5", "1-5", "1,3,5", "7") -> fonction(v) qui dit si ça matche
local function CompileField(field, lo, hi)
    local ranges = {}

    for part in string.gmatch(field, "[^,]+") do
        local base, step = part, 1

        local slash = string.find(part, "/", 1, true)
        if slash then
            base = string.sub(part, 1, slash - 1)
            step = tonumber(string.sub(part, slash + 1)) or 1
        end

        local a, b
        if base == "*" then
            a, b = lo, hi
        else
            local dash = string.find(base, "-", 1, true)
            if dash then
                a = tonumber(string.sub(base, 1, dash - 1))
                b = tonumber(string.sub(base, dash + 1))
            else
                a = tonumber(base)
                b = a
            end
        end

        if a and b then
            ranges[#ranges + 1] = { a = a, b = b, step = math.max(step, 1) }
        end
    end

    return function(v)
        for _, r in ipairs(ranges) do
            if v >= r.a and v <= r.b and ((v - r.a) % r.step == 0) then return true end
        end
        return false
    end
end

-- "min heure jour mois jour-semaine" -> les matchers, nil si c'est pas 5 champs
local function CompileCron(str)
    local fields = {}
    for tok in string.gmatch(str, "%S+") do fields[#fields + 1] = tok end
    if #fields ~= 5 then return nil end

    return {
        min  = CompileField(fields[1], 0, 59),
        hour = CompileField(fields[2], 0, 23),
        day  = CompileField(fields[3], 1, 31),
        mon  = CompileField(fields[4], 1, 12),
        wday = CompileField(fields[5], 0, 6), -- 0 = dimanche
    }
end

local function CronMatches(c, t)
    local wday = t.wday - 1 -- os.date : 1=dim..7=sam, moi je veux 0..6
    return c.min(t.min) and c.hour(t.hour) and c.day(t.day) and c.mon(t.month) and c.wday(wday)
end

-- (re)fabrique tous les timers depuis la config
function BladW.Announcement.RebuildSchedules()
    -- on vire les anciens
    for _, name in ipairs(BladW.Announcement._timers) do timer.Remove(name) end
    timer.Remove(CRON_TIMER)
    BladW.Announcement._timers = {}

    local schedules = BladW.Announcement.Schedules or {}
    local crons     = {}

    for i, entry in ipairs(schedules) do
        if entry.cron then
            local compiled = CompileCron(entry.cron)
            if compiled then
                crons[#crons + 1] = { entry = entry, cron = compiled, lastMinute = -1 }
            else
                Warn('cron invalide (attendu "m h jm mo js") : "' .. tostring(entry.cron) .. '"')
            end

        elseif entry.every then
            local secs = ParseInterval(entry.every)
            if secs and secs > 0 then
                local name = TIMER_PREFIX .. i
                timer.Create(name, secs, 0, function()
                    BladW.Announcement.Broadcast(entry)
                end)
                BladW.Announcement._timers[#BladW.Announcement._timers + 1] = name
            else
                Warn('intervalle invalide (ex "30m") : "' .. tostring(entry.every) .. '"')
            end

        else
            Warn("entrée #" .. i .. " ignorée : ni 'every' ni 'cron'.")
        end
    end

    -- un seul timer check tous les cron (tick 20s, une fois par minute qui matche)
    if #crons > 0 then
        timer.Create(CRON_TIMER, 20, 0, function()
            local now       = os.time()
            local minuteKey = math.floor(now / 60)
            local t         = os.date("*t", now)

            for _, c in ipairs(crons) do
                if c.lastMinute ~= minuteKey and CronMatches(c.cron, t) then
                    c.lastMinute = minuteKey
                    BladW.Announcement.Broadcast(c.entry)
                end
            end
        end)
    end
end

-- le config (shared) charge après server/, du coup j'attends une frame que Presets/Schedules soient là
timer.Simple(0, function()
    BladW.Announcement.RebuildSchedules()
end)
