--[[
============================================================================
Enhanced Mount Tracker  -  Modules/SourceInfo.lua
============================================================================
Turns a raw MountData record into the human-readable "how do I get this"
information shown in the detail panel and the tooltip.

SourceInfo:Describe(spellID, rec) -> table:
  headline     short source label              ("Boss Drop", "Vendor", ...)
  howto        source-aware action sentence    ("Drops from Elegon")
  detail       richer description if we have one (Rarity sourceText) or nil
  dropRate     "1.6%" / "Guaranteed" / nil
  rateKnown    boolean
  requirement  "Requires level 90 / Alliance / Druid" or nil
  removed      boolean
  removedText  string or nil
============================================================================
]]--

local ADDON_NAME, NS = ...
local EMT = NS.EMT
if not EMT then return end

EMT.SourceInfo = EMT.SourceInfo or {}
local SI = EMT.SourceInfo

-- Short label per source code.
local HEADLINE = {
    B = "Boss Drop",       R = "Rare Creature",   V = "Vendor",
    P = "PvP Reward",      C = "Crafted",         F = "Reputation Reward",
    H = "World Event",     K = "Black Market",    W = "World Drop",
    L = "Class / Racial",  M = "Promotional",     Q = "Quest",
    A = "Achievement",     ["?"] = "Special",
}

local CLASS_NAME = {
    [1] = "Warrior", [2] = "Paladin", [3] = "Hunter", [4] = "Rogue",
    [5] = "Priest",  [6] = "Death Knight", [7] = "Shaman", [8] = "Mage",
    [9] = "Warlock", [10] = "Monk",   [11] = "Druid",
}

-- ----------------------------------------------------------------------------
-- ACTION SENTENCE  ("how to get it")
-- ----------------------------------------------------------------------------

local function howToGet(spellID, rec)
    local src = rec.src or "?"
    local NPC = EMT.NPCNames
    -- prefer the explicit source name (boss/vendor/NPC) reconciled from the
    -- mount databases; fall back to resolving rec.n at runtime.
    local who = rec.sn or (rec.n and NPC and NPC:Get(rec.n))
    local function dropLine(default)
        if who and rec.loc then return "Drops from " .. who .. " in " .. rec.loc end
        if who then return "Drops from " .. who end
        return default
    end

    -- pure in-game-store mounts (no other known source) still report the store
    if rec.u == 3 and not who then return "Available from the in-game Store" end

    if src == "B" then
        return dropLine("Drops from a dungeon or raid boss")

    elseif src == "R" then
        return dropLine("Drops from a rare creature")

    elseif src == "V" then
        local s = who and ("Purchased from " .. who) or "Purchased from a vendor"
        if rec.cost then s = s .. " for " .. rec.cost end
        return s

    elseif src == "P" then
        if rec.cost then return "PvP reward — costs " .. rec.cost end
        return who and ("PvP reward from " .. who) or "Earned through PvP"

    elseif src == "C" then
        local prof = EMT.Professions and EMT.Professions[spellID]
        local s = prof and ("Crafted by " .. prof) or "Crafted by a profession"
        if rec.cost then s = s .. " (" .. rec.cost .. ")" end
        return s

    elseif src == "F" then
        local s = who and ("Reputation reward from " .. who)
            or "Earned through faction reputation"
        if rec.cost then s = s .. " for " .. rec.cost end
        return s

    elseif src == "H" then
        return who and ("World event — " .. who)
            or "Obtained during a holiday or world event"

    elseif src == "K" then
        local s = "Bought from the Black Market Auction House"
        if who then s = s .. " (drops from " .. who ..
            (rec.loc and (" in " .. rec.loc) or "") .. ")" end
        return s

    elseif src == "W" then
        return dropLine("Rare world drop")

    elseif src == "L" then
        return "A class or racial mount"

    elseif src == "M" then
        return "From a promotion, the in-game shop, or the TCG"

    elseif src == "Q" then
        return who and ("Quest reward — " .. who) or "Quest reward"

    elseif src == "A" then
        return "Awarded for an achievement"
    end

    if who then return "From " .. who .. (rec.loc and (" in " .. rec.loc) or "") end
    return "Source varies"
end

-- ----------------------------------------------------------------------------
-- REQUIREMENT LINE
-- ----------------------------------------------------------------------------

local function requirement(rec)
    local parts = {}
    if rec.lvl and rec.lvl > 1 then
        parts[#parts + 1] = "Level " .. rec.lvl
    end
    if rec.r == 1 then
        parts[#parts + 1] = "Horde only"
    elseif rec.r == 2 then
        parts[#parts + 1] = "Alliance only"
    end
    if rec.cl and #rec.cl > 0 then
        local names = {}
        for _, cid in ipairs(rec.cl) do
            names[#names + 1] = CLASS_NAME[cid] or ("class " .. cid)
        end
        parts[#parts + 1] = table.concat(names, " / ") .. " only"
    end
    if #parts == 0 then return nil end
    return table.concat(parts, "  •  ")
end

-- ----------------------------------------------------------------------------
-- DROP RATE
-- ----------------------------------------------------------------------------

local function dropRate(rec)
    if not rec.dr then return nil, false end
    if rec.dr >= 100 then return "Guaranteed drop", true end
    return string.format("%.2f%% drop chance", rec.dr), true
end

-- ----------------------------------------------------------------------------
-- PUBLIC
-- ----------------------------------------------------------------------------

-- v15.40.0: the Mount Journal's own source text is authoritative on this
-- client (GetMountInfoExtraByID is available — ZoneScan already uses it). The
-- static `src` codes mislabel some mounts (e.g. basic vendor mounts tagged 'R'
-- and shown as "Drops from a rare creature"), so prefer the journal text and
-- derive the category label from it. Cached; falls back to static when the
-- journal has no text for a mount.
local _jsCache = {}
local function journalSource(spellID)
    if not spellID then return nil end
    local c = _jsCache[spellID]
    if c then return c.code, c.text end
    local API = EMT.MountAPI
    local mountID = API and API.GetJournalMountID and API:GetJournalMountID(spellID)
    if not (mountID and C_MountJournal and C_MountJournal.GetMountInfoExtraByID) then return nil end
    local ok, _, _, sourceText = pcall(C_MountJournal.GetMountInfoExtraByID, mountID)
    if not (ok and type(sourceText) == "string" and sourceText ~= "") then return nil end
    local text = sourceText:gsub("%s*[\r\n]+%s*", " \226\128\148 ")   -- inline multi-line
    local lower = text:lower()
    local code = "?"
    if     lower:find("drop", 1, true)        then code = "B"
    elseif lower:find("vendor", 1, true)      then code = "V"
    elseif lower:find("profession", 1, true)
        or lower:find("craft", 1, true)       then code = "C"
    elseif lower:find("reputation", 1, true)  then code = "F"
    elseif lower:find("pvp", 1, true) or lower:find("honor", 1, true)
        or lower:find("arena", 1, true) or lower:find("conquest", 1, true) then code = "P"
    elseif lower:find("world event", 1, true) or lower:find("holiday", 1, true) then code = "H"
    elseif lower:find("black market", 1, true) then code = "K"
    elseif lower:find("trading card", 1, true) or lower:find("promot", 1, true)
        or lower:find("store", 1, true) or lower:find("shop", 1, true) then code = "M"
    end
    _jsCache[spellID] = { code = code, text = text }
    return code, text
end

function SI:Describe(spellID, rec)
    if not rec then return nil end
    local jcode, jtext = journalSource(spellID)
    local rateText, rateKnown = dropRate(rec)
    local howto = jtext or howToGet(spellID, rec)
    -- the journal's source text reliably omits price; surface the reconciled
    -- cost so vendor / crafted / PvP mounts show what they actually cost.
    if rec.cost and howto and not howto:find(rec.cost, 1, true) then
        howto = howto .. " — Cost: " .. rec.cost
    end
    return {
        headline    = (jcode and jcode ~= "?" and HEADLINE[jcode])
            or HEADLINE[rec.src or "?"] or "Special",
        howto       = howto,
        detail      = (EMT.HowTo and EMT.HowTo[spellID]) or rec.nt or nil,
        dropRate    = rateText,
        rateKnown   = rateKnown,
        requirement = requirement(rec),
        removed     = (rec.u == 1 or rec.u == 2),
        removedText = (rec.u == 2 and "No longer obtainable \226\128\148 removed from the game")
            or (rec.u == 1 and "Not implemented \226\128\148 cannot be obtained") or nil,
    }
end

-- A compact one-liner for the browser row's right-hand text.
function SI:ShortLine(spellID, rec)
    if not rec then return "" end
    if rec.u == 1 or rec.u == 2 then return "|cffff8800removed|r" end
    local tag = self:SourceTag(rec, spellID)
    if rec.dr then
        return tag .. " |cffaaaaaa" .. self:FormatRate(rec.dr) .. "|r"
    end
    return tag
end

-- Short, colour-coded source label shown on every row and tooltip line so the
-- source (Boss / Vendor / World / Crafted / Store / ...) is unmistakable at a
-- glance, independent of the grouping header.
local TAG = {
    B = "|cffff7777Boss|r",   R = "|cffffbb55Rare|r",    V = "|cff77dd77Vendor|r",
    P = "|cff7799ffPvP|r",    C = "|cffffaa44Craft|r",   F = "|cff55ccffRep|r",
    H = "|cffff88ccEvent|r",  K = "|cffbb88ffBMAH|r",    W = "|cffaabbaaWorld|r",
    L = "|cffcccc66Class|r",  M = "|cff66ccffStore|r",   Q = "|cff88ddaaQuest|r",
    A = "|cffddcc44Achv|r",   ["?"] = "|cff999999Other|r",
}
function SI:SourceTag(rec, spellID)
    if rec and rec.u == 3 then return TAG.M end   -- store/real-money
    if spellID then
        local jcode = journalSource(spellID)
        if jcode and jcode ~= "?" and TAG[jcode] then return TAG[jcode] end
    end
    return TAG[(rec and rec.src) or "?"] or TAG["?"]
end

-- Drop-rate text: keeps precision for the very rare ones (a 1-in-5000 world
-- drop is 0.02%%, not "0.0%%").
function SI:FormatRate(dr)
    if not dr then return nil end
    if dr >= 100 then return "100%" end
    if dr >= 1   then return string.format("%.1f%%", dr) end
    return string.format("%.2f%%", dr)
end
