local addonName, TOAST = ...

-- Read-only adapter for the normalized bot-generated TOAST.trends export.
-- UI modules consume these helpers instead of knowing how snapshot indexes,
-- users, characters, and period summaries are stored in the export.

local characterIndex
local snapshotIndex
local indexedRoot
local DAYS_IN_MONTH = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }

local function FiniteNonNegative(v)
    local n = tonumber(v)
    if not n or n ~= n or n < 0 or n == math.huge then return nil end
    return n
end

local function NormalizeName(name)
    if type(name) ~= "string" then return "" end
    return string.lower(name)
end

local function NormalizeRealm(realm)
    if type(realm) ~= "string" then return "" end
    return string.lower((realm:gsub("[%s%-']", "")))
end

local function IdentityKey(name, realm)
    return NormalizeName(name) .. "|" .. NormalizeRealm(realm)
end

local function ValidRoot(root)
    return type(root) == "table" and tonumber(root.schema_version) == 1
end

local function ValidDate(date)
    if type(date) ~= "string" then return nil end
    local y, m, d = date:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)$")
    y, m, d = tonumber(y), tonumber(m), tonumber(d)
    if not y or not m or not d or m < 1 or m > 12 then return nil end
    local maxDay = DAYS_IN_MONTH[m]
    local leap = (y % 4 == 0 and y % 100 ~= 0) or y % 400 == 0
    if m == 2 and leap then maxDay = 29 end
    if d < 1 or d > maxDay then return nil end
    return date
end

local function EnsureIndexes()
    local root = TOAST.trends
    if indexedRoot == root and characterIndex and snapshotIndex then return end

    indexedRoot = root
    characterIndex = {}
    snapshotIndex = {}
    if not ValidRoot(root) then return end

    if type(root.snapshots) == "table" then
        for i, snapshot in ipairs(root.snapshots) do
            if type(snapshot) == "table" then
                local timestamp = tonumber(snapshot.timestamp)
                local date = ValidDate(snapshot.date)
                if timestamp and timestamp == timestamp and timestamp > 0
                    and timestamp ~= math.huge and date then
                    snapshotIndex[i] = {
                        snapshotID = snapshot.snapshot_id,
                        snapshotAt = snapshot.snapshot_at,
                        timestamp = timestamp,
                        date = date,
                        days = timestamp / 86400,
                    }
                end
            end
        end
    end

    if type(root.users) ~= "table" then return end
    for discordID, user in pairs(root.users) do
        if type(user) == "table" and type(user.characters) == "table" then
            for _, character in pairs(user.characters) do
                if type(character) == "table" and type(character.name) == "string"
                    and type(character.realm) == "string" then
                    local key = IdentityKey(character.name, character.realm)
                    local record = {
                        discordID = tostring(discordID),
                        discordNickname = user.discord_nickname,
                        character = character,
                    }
                    -- A duplicated normalized identity is ambiguous. Refuse it
                    -- instead of choosing whichever unordered pairs() row won.
                    if characterIndex[key] == nil then
                        characterIndex[key] = record
                    else
                        characterIndex[key] = false
                    end
                end
            end
        end
    end
end

function TOAST.GetCharacterTrend(name, realm)
    if type(name) ~= "string" or name == "" then return {} end
    EnsureIndexes()

    local record = characterIndex[IdentityKey(name, realm)]
    if type(record) ~= "table" then return {} end

    local points = {}
    local seenSnapshots = {}
    local scores = record.character.scores
    if type(scores) == "table" then
        for _, score in ipairs(scores) do
            if type(score) == "table" then
                local snapshotNumber = tonumber(score.snapshot)
                local snapshot = snapshotNumber and snapshotIndex[snapshotNumber] or nil
                local rio = FiniteNonNegative(score.rio)
                if snapshot and rio and not seenSnapshots[snapshotNumber] then
                    seenSnapshots[snapshotNumber] = true
                    points[#points + 1] = {
                        snapshot = snapshotNumber,
                        snapshotID = snapshot.snapshotID,
                        snapshotAt = snapshot.snapshotAt,
                        timestamp = snapshot.timestamp,
                        date = snapshot.date,
                        days = snapshot.days,
                        rio = rio,
                        resi = FiniteNonNegative(score.resi),
                        source = "trends",
                    }
                end
            end
        end
    end

    table.sort(points, function(a, b)
        if a.timestamp ~= b.timestamp then return a.timestamp < b.timestamp end
        return a.snapshot < b.snapshot
    end)
    return points
end

function TOAST.GetTrendPeriod(periodType)
    if periodType ~= "weekly" and periodType ~= "monthly" and periodType ~= "season" then
        return nil
    end
    local root = TOAST.trends
    if not ValidRoot(root) or type(root.periods) ~= "table" then return nil end
    local period = root.periods[periodType]
    if type(period) ~= "table" then return nil end
    return period
end

function TOAST.GetTrendsGeneratedAt()
    local root = TOAST.trends
    if not ValidRoot(root) or type(root.generated_at) ~= "string" then return nil end
    return root.generated_at
end
