local _, ns = ...

------------------------------------------------------------
-- Bloodmallet JSON → spec-id lookup parser.
--
-- Loaded once per session (lazily on first Get call). Output shape:
--   ns.PI.Parsed[mode] = {
--     parsed          = bool,
--     title           = string,
--     timestamp       = string,
--     recognizedCount = N,
--     specs           = { [specID] = { specID, specName, baseScore } },
--   }
--
-- Parser pattern adapted from the BestPI addon.
------------------------------------------------------------

ns.PI = ns.PI or {}
local PI = ns.PI
PI.Parsed = PI.Parsed or {}

-- Bloodmallet spec key → WoW specID. The spec key format comes from
-- bloodmallet's dataset ("Balance Druid", "Beast_Mastery Hunter", etc.)
-- and is stable across seasons.
local SPEC_NAME_TO_ID = {
    ["Affliction Warlock"]        = 265,
    ["Arcane Mage"]               = 62,
    ["Arms Warrior"]              = 71,
    ["Assassination Rogue"]       = 259,
    ["Balance Druid"]             = 102,
    ["Beast_Mastery Hunter"]      = 253,
    ["Blood Death Knight"]        = 250,
    ["Brewmaster Monk"]           = 268,
    ["Demonology Warlock"]        = 266,
    ["Destruction Warlock"]       = 267,
    ["Devastation Evoker"]        = 1467,
    ["Devourer Demon Hunter"]     = 1480,
    ["Elemental Shaman"]          = 262,
    ["Enhancement Shaman"]        = 263,
    ["Feral Druid"]               = 103,
    ["Fire Mage"]                 = 63,
    ["Frost Death Knight"]        = 251,
    ["Frost Mage"]                = 64,
    ["Fury Warrior"]              = 72,
    ["Havoc Demon Hunter"]        = 577,
    ["Marksmanship Hunter"]       = 254,
    ["Outlaw Rogue"]              = 260,
    ["Protection Paladin"]        = 66,
    ["Protection Warrior"]        = 73,
    ["Retribution Paladin"]       = 70,
    ["Shadow Priest"]             = 258,
    ["Subtlety Rogue"]            = 261,
    ["Survival Hunter"]           = 255,
    ["Unholy Death Knight"]       = 252,
    ["Vengeance Demon Hunter"]    = 581,
    ["Windwalker Monk"]           = 269,
    ["Guardian Druid"]            = 104,
}

local SPEC_ID_TO_NAME = {}
for name, id in pairs(SPEC_NAME_TO_ID) do
    SPEC_ID_TO_NAME[id] = SPEC_ID_TO_NAME[id] or name
end

-- "{Spec Name}" style rows are bloodmallet's alt-talent variants; we
-- only score the primary profile, so these are dropped at parse time.
local function IsBraceVariant(s)
    return type(s) == "string" and s:sub(1, 1) == "{" and s:sub(-1, -1) == "}"
end

local function ResolveSpecID(rawName)
    if type(rawName) ~= "string" then return nil end
    local trimmed = rawName:gsub("^%s+", ""):gsub("%s+$", "")
    if trimmed == "" then return nil end
    return SPEC_NAME_TO_ID[trimmed]
end

-- Prefer the game's localized spec name so UI matches the player's
-- language. Falls back to the bloodmallet key if the API is missing.
local function LocalizedSpecName(specID, fallback)
    local specIDNumber = tonumber(specID)
    if specIDNumber and GetSpecializationInfoByID then
        local _, specName, _, _, _, classFile = GetSpecializationInfoByID(specIDNumber)
        if type(specName) == "string" and specName ~= "" then
            local className = classFile
                and LOCALIZED_CLASS_NAMES_MALE
                and LOCALIZED_CLASS_NAMES_MALE[classFile]
            if type(className) == "string" and className ~= "" then
                return specName .. " " .. className
            end
            return specName
        end
    end
    return fallback or SPEC_ID_TO_NAME[specIDNumber] or ("Spec " .. tostring(specID))
end

local function DecodeJson(raw)
    if type(raw) ~= "string" or raw == "" then return nil end
    if not (C_EncodingUtil and C_EncodingUtil.DeserializeJSON) then
        return nil
    end
    local ok, decoded = pcall(C_EncodingUtil.DeserializeJSON, raw)
    if ok and type(decoded) == "table" then return decoded end
    return nil
end

local function ParseMode(mode)
    local decoded = DecodeJson(PI.RawData and PI.RawData[mode])
    local out = {
        mode            = mode,
        parsed          = false,
        title           = nil,
        timestamp       = nil,
        recognizedCount = 0,
        specs           = {},
    }
    if type(decoded) ~= "table" or type(decoded.data) ~= "table" then
        return out
    end

    out.parsed    = true
    out.title     = decoded.title
    out.timestamp = decoded.timestamp

    for rawName, rawScore in pairs(decoded.data) do
        if not IsBraceVariant(rawName) then
            local specID = ResolveSpecID(rawName)
            local score  = tonumber(rawScore)
            if specID and score and score > 0 then
                out.specs[specID] = {
                    specID    = specID,
                    specName  = LocalizedSpecName(specID, rawName),
                    baseScore = score,
                }
                out.recognizedCount = out.recognizedCount + 1
            end
        end
    end
    return out
end

-- Public API --------------------------------------------------------

function PI:ParseAll()
    self.Parsed.ST     = ParseMode("ST")
    self.Parsed.CLEAVE = ParseMode("CLEAVE")
    self.Parsed.AOE    = ParseMode("AOE")
end

-- Returns the parsed table for a mode, lazily parsing on first access.
function PI:GetMode(mode)
    if type(mode) ~= "string" then return nil end
    local key = mode:upper()
    if not self.Parsed[key] then
        self.Parsed[key] = ParseMode(key)
    end
    return self.Parsed[key]
end

function PI:GetBaseScore(specID, mode)
    local m = self:GetMode(mode)
    if not m then return nil end
    local entry = m.specs and m.specs[specID]
    return entry and entry.baseScore or nil
end

function PI:GetSpecName(specID)
    return LocalizedSpecName(specID, SPEC_ID_TO_NAME[specID])
end

-- Does bloodmallet rank this spec for any of the three fight modes?
-- Used to filter out healer/tank specs from PI target consideration.
function PI:IsSpecScored(specID)
    if not specID or specID == 0 then return false end
    for _, mode in ipairs({ "ST", "CLEAVE", "AOE" }) do
        if self:GetBaseScore(specID, mode) then return true end
    end
    return false
end
