-- MythicTrackerRunHistory.lua
-- Tracks every M+ run with full group data.
-- Exposes globals: MTS_ClearHistory, MTS_DrawRunHistory

local HISTORY_MAX = 300

-- ─── Frame Pool ──────────────────────────────────────────────────────────────

local historyFrames = {}

function MTS_ClearHistory()
    for _, obj in ipairs(historyFrames) do
        if obj and obj.Hide then obj:Hide() end
        if obj and obj.SetScript then
            pcall(function() obj:SetScript("OnEnter", nil) end)
            pcall(function() obj:SetScript("OnLeave", nil) end)
        end
    end
    historyFrames = {}
end

-- ─── Draw Helpers ─────────────────────────────────────────────────────────────

local function HPT(sc, size, r, g, b, text, x, y)
    local fs = sc:CreateFontString(nil, "OVERLAY")
    fs:SetFont("Fonts\\FRIZQT__.TTF", size, "OUTLINE")
    fs:SetTextColor(r, g, b, 1)
    fs:SetJustifyH("LEFT")
    fs:SetPoint("TOPLEFT", sc, "TOPLEFT", x, y)
    fs:SetText(text or "")
    table.insert(historyFrames, fs)
    return fs
end

local function HLine(sc, r, g, b, a, x, y, w)
    local t = sc:CreateTexture(nil, "ARTWORK")
    t:SetColorTexture(r, g, b, a or 1)
    t:SetSize(w or 688, 1)
    t:SetPoint("TOPLEFT", sc, "TOPLEFT", x, y)
    table.insert(historyFrames, t)
    return t
end

local function ClassColor(classFile)
    local cc = RAID_CLASS_COLORS and classFile and RAID_CLASS_COLORS[classFile]
    if cc then return cc.r, cc.g, cc.b end
    return 0.75, 0.75, 0.75
end

local function ScoreColor(score)
    score = score or 0
    if     score >= 3000 then return 0.95, 0.40, 1.00
    elseif score >= 2500 then return 1.00, 0.50, 0.00
    elseif score >= 2000 then return 0.10, 0.70, 1.00
    elseif score >= 1500 then return 0.20, 0.85, 0.30
    elseif score >  0    then return 0.65, 0.65, 0.65
    else                      return 0.35, 0.35, 0.35
    end
end

local function FormatDate(ts)
    if not ts then return "Unknown" end
    -- Respect the player's in-game clock setting
    local use24 = GetCVar and GetCVar("timeMgrUseMilitaryTime") == "1"
    local fmt   = use24 and "%b %d %Y  %H:%M" or "%b %d %Y  %I:%M %p"
    local ok, result = pcall(date, fmt, ts)
    if ok and result then return result end
    return tostring(ts)
end

-- ─── DB Helpers ──────────────────────────────────────────────────────────────

local function GetRunHistory()
    if not MythicTrackerSeasonDB then MythicTrackerSeasonDB = {} end
    if not MythicTrackerSeasonDB.runHistory then
        MythicTrackerSeasonDB.runHistory = {}
    end
    return MythicTrackerSeasonDB.runHistory
end

local function AddRun(entry)
    local h = GetRunHistory()
    table.insert(h, 1, entry)
    while #h > HISTORY_MAX do table.remove(h) end
end

-- ─── Encounter Index ─────────────────────────────────────────────────────────

local function BuildEncounterIndex(history)
    local idx = {}
    for _, run in ipairs(history) do
        if run.players then
            for _, p in ipairs(run.players) do
                if not p.isSelf and p.name and p.name ~= "Unknown" then
                    local key = (p.name or "") .. "-" .. (p.realm or "")
                    if not idx[key] then
                        idx[key] = { count=0, name=p.name, realm=p.realm, class=p.class, runs={} }
                    end
                    idx[key].count = idx[key].count + 1
                    table.insert(idx[key].runs, {
                        dungeon = run.dungeon,
                        level   = run.level,
                        timed   = run.timed,
                        ts      = run.ts,
                    })
                end
            end
        end
    end
    return idx
end

-- ─── Filter State ─────────────────────────────────────────────────────────────

local historyFilter = "all"

-- ─── Group Capture ───────────────────────────────────────────────────────────

-- Cache of pre-inspected specs: [unit] = specName
local inspectCache = {}

local function RequestGroupInspects()
    -- Fire NotifyInspect for each group member so spec data is ready by run end
    local inRaid = IsInRaid and IsInRaid()
    local n = (GetNumGroupMembers and GetNumGroupMembers()) or 0
    for i = 1, n do
        local unit = inRaid and ("raid" .. i) or ("party" .. i)
        pcall(function()
            if UnitExists(unit) and CanInspect and CanInspect(unit) then
                NotifyInspect(unit)
            end
        end)
    end
end

local function CaptureGroupMembers()
    local members = {}
    local units   = { "player" }
    local inRaid  = IsInRaid and IsInRaid()
    local n       = (GetNumGroupMembers and GetNumGroupMembers()) or 0
    for i = 1, n do
        table.insert(units, inRaid and ("raid" .. i) or ("party" .. i))
    end

    for _, unit in ipairs(units) do
        local exists = false
        pcall(function() exists = UnitExists(unit) end)
        if exists then
            local name, realm = "Unknown", ""
            pcall(function() name, realm = UnitName(unit) end)
            name  = name  or "Unknown"
            realm = realm or ""
            if realm == "" then pcall(function() realm = GetRealmName() or "" end) end

            local classFile, className = "", ""
            pcall(function() className, classFile = UnitClass(unit) end)

            local spec = ""
            pcall(function()
                if unit == "player" then
                    local idx2 = GetSpecialization and GetSpecialization()
                    if idx2 then
                        local _, sname = GetSpecializationInfo(idx2)
                        spec = sname or ""
                    end
                else
                    -- Try inspect cache first, then live API
                    local unitKey = name .. "-" .. realm
                    if inspectCache[unitKey] and inspectCache[unitKey] ~= "" then
                        spec = inspectCache[unitKey]
                    else
                        local specID = GetInspectSpecialization and GetInspectSpecialization(unit)
                        if specID and specID > 0 then
                            local _, sname = GetSpecializationInfoByID(specID)
                            spec = sname or ""
                        end
                    end
                end
            end)

            local score = 0
            pcall(function()
                if C_PlayerInfo and C_PlayerInfo.GetPlayerMythicPlusRatingSummary then
                    local s = C_PlayerInfo.GetPlayerMythicPlusRatingSummary(unit)
                    if s then score = s.currentSeasonScore or 0 end
                end
            end)

            local role = ""
            pcall(function()
                local r = UnitGroupRolesAssigned and UnitGroupRolesAssigned(unit)
                if     r == "TANK"    then role = "Tank"
                elseif r == "HEALER"  then role = "Healer"
                elseif r == "DAMAGER" then role = "DPS"
                end
            end)

            table.insert(members, {
                name      = name,
                realm     = realm,
                class     = classFile,
                className = className,
                spec      = spec,
                score     = score,
                role      = role,
                isSelf    = (unit == "player"),
            })
        end
    end
    return members
end

-- ─── Event Listener ──────────────────────────────────────────────────────────

local rhFrame   = CreateFrame("Frame")
local pendingRH = {}

rhFrame:RegisterEvent("CHALLENGE_MODE_START")
rhFrame:RegisterEvent("CHALLENGE_MODE_COMPLETED")
rhFrame:RegisterEvent("INSPECT_READY")
rhFrame:RegisterEvent("CHAT_MSG_SYSTEM")
rhFrame:RegisterEvent("UI_INFO_MESSAGE")
rhFrame:RegisterEvent("CHAT_MSG_RAID_BOSS_EMOTE")

local lastCompletedLevelRH = nil

rhFrame:SetScript("OnEvent", function(self, event, arg1, arg2, arg3, arg4)
    if event == "CHAT_MSG_SYSTEM" or event == "UI_INFO_MESSAGE" or event == "CHAT_MSG_RAID_BOSS_EMOTE" then
        local msg = arg1
        if msg then
            local lv = msg:match("%(Level (%d+)%)")
                     or msg:match("Level (%d+)%)")
                     or msg:match("Level (%d+) ")
            if lv then lastCompletedLevelRH = tonumber(lv) end
        end

    elseif event == "INSPECT_READY" then
        -- arg1 is the GUID of the inspected unit — cache their spec
        pcall(function()
            local inRaid = IsInRaid and IsInRaid()
            local n = (GetNumGroupMembers and GetNumGroupMembers()) or 0
            for i = 1, n do
                local unit = inRaid and ("raid" .. i) or ("party" .. i)
                if UnitExists(unit) and UnitGUID(unit) == arg1 then
                    local specID = GetInspectSpecialization and GetInspectSpecialization(unit)
                    if specID and specID > 0 then
                        local _, sname = GetSpecializationInfoByID(specID)
                        if sname and sname ~= "" then
                            local name, realm = UnitName(unit)
                            realm = realm or GetRealmName() or ""
                            inspectCache[(name or "?") .. "-" .. realm] = sname
                        end
                    end
                    break
                end
            end
        end)

    elseif event == "CHALLENGE_MODE_START" then
        pendingRH = { level=0, dungeon=nil, affixes={} }
        inspectCache = {}  -- clear old cache

        -- Pre-cache inspect data for all group members so specs are ready at run end
        C_Timer.After(3.0, RequestGroupInspects)
        C_Timer.After(10.0, RequestGroupInspects)  -- retry midway through run

        local function TryCaptureRH()
            if not pendingRH then return end
            -- GetInstanceInfo() is the most reliable source while inside the instance
            pcall(function()
                local iName = GetInstanceInfo and select(1, GetInstanceInfo())
                if iName and iName ~= "" and iName ~= "Unknown" then
                    pendingRH.dungeon = iName
                end
            end)
            -- GetSlottedKeystoneInfo: key currently in Font of Power
            if not pendingRH.level or pendingRH.level == 0 then
                pcall(function()
                    if C_ChallengeMode and C_ChallengeMode.GetSlottedKeystoneInfo then
                        local _, _, lv = C_ChallengeMode.GetSlottedKeystoneInfo()
                        if lv and lv > 0 then pendingRH.level = lv end
                    end
                end)
            end
            -- GetActiveKeystoneInfo fallback
            if not pendingRH.level or pendingRH.level == 0 then
                pcall(function()
                    if C_ChallengeMode and C_ChallengeMode.GetActiveKeystoneInfo then
                        local lv = C_ChallengeMode.GetActiveKeystoneInfo()
                        if lv and lv > 0 then pendingRH.level = lv end
                    end
                end)
            end
            -- Fallback via dungeon name
            if not pendingRH.dungeon or pendingRH.dungeon == "" then
                pcall(function()
                    if C_ChallengeMode and C_ChallengeMode.GetActiveChallengeMapID then
                        local mapID = C_ChallengeMode.GetActiveChallengeMapID()
                        if mapID and mapID > 0 and C_ChallengeMode.GetMapUIInfo then
                            local n = C_ChallengeMode.GetMapUIInfo(mapID)
                            if n and n ~= "" then pendingRH.dungeon = n end
                        end
                    end
                end)
            end
            -- Affixes
            if not pendingRH.affixes or #pendingRH.affixes == 0 then
                pcall(function()
                    if C_MythicPlus and C_MythicPlus.GetCurrentAffixes then
                        local list = C_MythicPlus.GetCurrentAffixes()
                        if list and #list > 0 then
                            pendingRH.affixes = {}
                            for _, a in ipairs(list) do
                                local aname = a.name
                                if not aname and a.id then
                                    pcall(function()
                                        if C_ChallengeMode and C_ChallengeMode.GetAffixInfo then
                                            aname = select(1, C_ChallengeMode.GetAffixInfo(a.id))
                                        end
                                    end)
                                end
                                if aname then table.insert(pendingRH.affixes, aname) end
                            end
                        end
                    end
                end)
            end
        end

        TryCaptureRH()
        C_Timer.After(0.5, TryCaptureRH)
        C_Timer.After(2.0, TryCaptureRH)

    elseif event == "CHALLENGE_MODE_COMPLETED" then
        -- Event fires with NO arguments (confirmed: "None" per WoW API docs)

        -- Dungeon name
        local nameNow = (pendingRH and pendingRH.dungeon)
        if not nameNow or nameNow == "" then
            pcall(function()
                local iName = GetInstanceInfo and select(1, GetInstanceInfo())
                if iName and iName ~= "" and iName ~= "Unknown" then nameNow = iName end
            end)
        end

        -- Level: try GetSlottedKeystoneInfo first (key still in Font at completion)
        local levelToRecord = (pendingRH and pendingRH.level) or 0
        if levelToRecord == 0 then
            pcall(function()
                if C_ChallengeMode and C_ChallengeMode.GetSlottedKeystoneInfo then
                    local _, _, lv = C_ChallengeMode.GetSlottedKeystoneInfo()
                    if lv and lv > 0 then levelToRecord = lv end
                end
            end)
        end
        if levelToRecord == 0 then
            pcall(function()
                if C_ChallengeMode and C_ChallengeMode.GetActiveKeystoneInfo then
                    local lv = C_ChallengeMode.GetActiveKeystoneInfo()
                    if lv and lv > 0 then levelToRecord = lv end
                end
            end)
        end
        if levelToRecord == 0 and lastCompletedLevelRH and lastCompletedLevelRH > 0 then
            levelToRecord = lastCompletedLevelRH
        end

        local snapAffixes = (pendingRH and pendingRH.affixes) or {}
        pendingRH = nil
        lastCompletedLevelRH = nil

        C_Timer.After(1.5, function()
            local onTime = false
            pcall(function()
                if C_MythicPlus and C_MythicPlus.GetRunHistory then
                    local h = C_MythicPlus.GetRunHistory(false, false)
                    if h and h[1] then
                        onTime = h[1].completed or h[1].onTime or false
                        if levelToRecord == 0 and h[1].level and h[1].level > 0 then
                            levelToRecord = h[1].level
                        end
                    end
                end
            end)

            local dungeon = (nameNow or "Unknown"):gsub("|c%x%x%x%x%x%x%x%x",""):gsub("|r","")
            local season  = 1
            pcall(function() if MTS_GetCurrentSeason then season = MTS_GetCurrentSeason() end end)
            AddRun({ ts=time(), dungeon=dungeon, level=levelToRecord, timed=onTime,
                     season=season, affixes=snapAffixes, players=CaptureGroupMembers() })
        end)
    end
end)

-- ─── Main Draw Function ───────────────────────────────────────────────────────

function MTS_DrawRunHistory(sc)
    -- Clear any previous history frames
    MTS_ClearHistory()
    -- Reset scroll child height before drawing
    sc:SetHeight(600)

    local ok, err = pcall(function()

        local history    = GetRunHistory()
        local encounters = BuildEncounterIndex(history)
        local y = -8

        -- ── Filter buttons ───────────────────────────────────────────────
        local filterDefs = {
            { "All",      "all",      12  },
            { "Timed",    "timed",    100 },
            { "Depleted", "depleted", 190 },
        }
        for _, fd in ipairs(filterDefs) do
            local label, fkey, bx = fd[1], fd[2], fd[3]
            local btn = CreateFrame("Button", nil, sc, "UIPanelButtonTemplate")
            btn:SetSize(82, 22)
            btn:SetText(label)
            btn:SetPoint("TOPLEFT", sc, "TOPLEFT", bx, y)
            if historyFilter == fkey then
                btn:SetNormalFontObject(GameFontHighlight)
            end
            local captureKey = fkey
            btn:SetScript("OnClick", function()
                historyFilter = captureKey
                MTS_DrawRunHistory(sc)
            end)
            table.insert(historyFrames, btn)
        end

        -- Run count line
        local total, showing = #history, 0
        for _, r in ipairs(history) do
            if historyFilter == "all"
            or (historyFilter == "timed"    and r.timed)
            or (historyFilter == "depleted" and not r.timed) then
                showing = showing + 1
            end
        end
        HPT(sc, 11, 0.40, 0.40, 0.40,
            string.format("Showing %d of %d  |cff333333(max %d stored)|r",
                showing, total, HISTORY_MAX),
            285, y + 6)

        y = y - 30
        HLine(sc, 0.45, 0.35, 0.08, 0.7, 12, y, 688)
        y = y - 14

        -- ── Empty state ──────────────────────────────────────────────────
        if showing == 0 then
            HPT(sc, 12, 0.45, 0.45, 0.45,
                "No runs recorded.  Complete a Mythic+ key to begin tracking.", 12, y)
            sc:SetHeight(math.abs(y) + 60)
            return
        end

        -- ── Column headers ───────────────────────────────────────────────
        HPT(sc, 10, 0.36, 0.36, 0.36, "Player",       62,  y)
        HPT(sc, 10, 0.36, 0.36, 0.36, "Spec / Class", 262, y)
        HPT(sc, 10, 0.36, 0.36, 0.36, "Role",         432, y)
        HPT(sc, 10, 0.36, 0.36, 0.36, "M+ Score",     512, y)
        y = y - 18

        -- ── Run entries ──────────────────────────────────────────────────
        local runIdx = 0
        for _, run in ipairs(history) do
            local visible = historyFilter == "all"
                or (historyFilter == "timed"    and run.timed)
                or (historyFilter == "depleted" and not run.timed)

            if visible then
                runIdx = runIdx + 1

                -- Header bg stripe
                local hbg = sc:CreateTexture(nil, "BACKGROUND")
                hbg:SetColorTexture(0.12, 0.09, 0.04, 0.70)
                hbg:SetPoint("TOPLEFT",  sc, "TOPLEFT",  12, y)
                hbg:SetPoint("TOPRIGHT", sc, "TOPRIGHT", -12, y)
                hbg:SetHeight(22)
                table.insert(historyFrames, hbg)

                HPT(sc, 10, 0.32, 0.32, 0.32,
                    string.format("#%d", runIdx), 16, y - 5)

                HPT(sc, 13, 1, 0.82, 0,
                    string.format("%s  |cffffff00+%d|r",
                        run.dungeon or "Unknown", run.level or 0),
                    44, y - 4)

                if run.timed then
                    HPT(sc, 11, 0.20, 0.85, 0.30, "TIMED",    410, y - 5)
                else
                    HPT(sc, 11, 0.90, 0.25, 0.25, "DEPLETED", 410, y - 5)
                end

                HPT(sc, 10, 0.38, 0.38, 0.38, FormatDate(run.ts), 480, y - 5)

                y = y - 24

                -- Players
                local players = run.players or {}
                for _, p in ipairs(players) do
                    local cr, cg, cb = ClassColor(p.class)

                    local dname = (p.name or "Unknown")
                    if p.isSelf then dname = dname .. " |cff888888(You)|r" end

                    local nameFS = sc:CreateFontString(nil, "OVERLAY")
                    nameFS:SetFont("Fonts\\FRIZQT__.TTF", 12, "OUTLINE")
                    nameFS:SetTextColor(cr, cg, cb, 1)
                    nameFS:SetJustifyH("LEFT")
                    nameFS:SetPoint("TOPLEFT", sc, "TOPLEFT", 62, y - 2)
                    nameFS:SetText(dname)
                    table.insert(historyFrames, nameFS)

                    -- Spec / Class
                    local specStr = ""
                    if (p.spec or "") ~= "" and (p.className or "") ~= "" then
                        specStr = p.spec .. " " .. p.className
                    elseif (p.className or "") ~= "" then
                        specStr = p.className
                    else
                        specStr = "|cff444444Unknown|r"
                    end
                    HPT(sc, 12, 0.80, 0.80, 0.80, specStr, 262, y - 2)

                    -- Role
                    local rr, rg, rb = 0.40, 0.40, 0.40
                    if     p.role == "Tank"   then rr,rg,rb = 0.30, 0.70, 1.00
                    elseif p.role == "Healer" then rr,rg,rb = 0.20, 0.85, 0.30
                    elseif p.role == "DPS"    then rr,rg,rb = 0.90, 0.25, 0.25
                    end
                    HPT(sc, 12, rr, rg, rb,
                        ((p.role or "") ~= "" and p.role) or "|cff444444-|r",
                        432, y - 2)

                    -- Score
                    local sr, sg, sb = ScoreColor(p.score)
                    local scoreStr = (p.score and p.score > 0)
                        and tostring(p.score) or "|cff404040N/A|r"
                    HPT(sc, 12, sr, sg, sb, scoreStr, 512, y - 2)

                    -- Realm
                    if (p.realm or "") ~= "" then
                        HPT(sc, 9, 0.26, 0.26, 0.26, p.realm, 62, y - 15)
                    end

                    -- Encounter tooltip hover
                    if not p.isSelf then
                        local pKey   = (p.name or "") .. "-" .. (p.realm or "")
                        local enc    = encounters[pKey]
                        local capN   = p.name or "Unknown"
                        local capCls = p.class

                        local hf = CreateFrame("Frame", nil, sc)
                        hf:SetSize(195, 22)
                        hf:SetPoint("TOPLEFT", sc, "TOPLEFT", 62, y - 1)
                        hf:EnableMouse(true)
                        hf:SetScript("OnEnter", function()
                            local tr, tg, tb = ClassColor(capCls)
                            local hex = string.format("|cff%02x%02x%02x",
                                tr*255, tg*255, tb*255)
                            local count = enc and enc.count or 0

                            GameTooltip:SetOwner(hf, "ANCHOR_CURSOR")
                            if count <= 1 then
                                GameTooltip:SetText(string.format(
                                    "You've encountered %s%s|r 1 time",
                                    hex, capN))
                            else
                                GameTooltip:SetText(string.format(
                                    "You've encountered %s%s|r %d times",
                                    hex, capN, count))
                            end

                            if enc and #enc.runs > 0 then
                                GameTooltip:AddLine(" ")
                                GameTooltip:AddLine("|cffffff00Shared runs:|r")
                                local shown = 0
                                for _, er in ipairs(enc.runs) do
                                    if shown >= 10 then
                                        GameTooltip:AddLine(string.format(
                                            "|cff555555... and %d more|r",
                                            count - shown), 1,1,1)
                                        break
                                    end
                                    local res = er.timed
                                        and "|cff33dd55Timed|r"
                                        or  "|cffdd3333Depleted|r"
                                    GameTooltip:AddLine(
                                        string.format("  %s  +%d  %s  %s",
                                            er.dungeon or "?",
                                            er.level   or 0,
                                            res,
                                            FormatDate(er.ts)),
                                        0.88, 0.84, 0.60)
                                    shown = shown + 1
                                end
                            end
                            GameTooltip:Show()
                        end)
                        hf:SetScript("OnLeave", function() GameTooltip:Hide() end)
                        table.insert(historyFrames, hf)
                    end

                    y = y - (((p.realm or "") ~= "") and 22 or 20)
                end

                -- Divider between runs
                y = y - 4
                HLine(sc, 0.15, 0.15, 0.15, 0.6, 22, y, 668)
                y = y - 10
            end
        end

        sc:SetHeight(math.abs(y) + 40)

    end)  -- end pcall

    -- If anything errored, show it in the panel instead of silent failure
    if not ok then
        local errFS = sc:CreateFontString(nil, "OVERLAY")
        errFS:SetFont("Fonts\\FRIZQT__.TTF", 11, "OUTLINE")
        errFS:SetTextColor(1, 0.3, 0.3, 1)
        errFS:SetJustifyH("LEFT")
        errFS:SetPoint("TOPLEFT", sc, "TOPLEFT", 12, -8)
        errFS:SetText("|cffff4444Run History Error:|r  " .. tostring(err))
        errFS:SetWidth(680)
        table.insert(historyFrames, errFS)
        sc:SetHeight(200)
    end
end