EbonholdHub.Session = {}

local POLL_INTERVAL = 2  -- seconds between level checks for reset detection

local maxLevel    = 0   -- highest level seen in the active session
local pollFrame   = nil
local pollElapsed = 0


local function GetRunSoulAshes()
    local rd = EbonholdPlayerRunData
    if not rd and ProjectEbonhold and ProjectEbonhold.PlayerRunService then
        local get = ProjectEbonhold.PlayerRunService.GetCurrentData
        if get then rd = get() end
    end
    return (rd and rd.soulPoints) or 0
end

local function GetClassName()
    local _, class = UnitClass("player")
    return class  -- English token (WARRIOR, MAGE, etc.)
end

local function GetActiveBuildTitle()
    local build = EbonholdHub.Build.GetActive()
    return build and build.title or "No Build"
end

local function CreateSession()
    local sessions = EbonholdHubDB.sessions
    local id = tostring(time()) .. "-" .. tostring(#sessions + 1)

    local session = {
        id            = id,
        characterName = UnitName("player"),
        className     = GetClassName(),
        startTime     = time(),
        endTime       = nil,
        soulAshes     = 0,
        buildTitle    = GetActiveBuildTitle(),
        logs          = {},
    }

    table.insert(sessions, 1, session)
    EbonholdHubDB.currentSessionIndex = 1
    maxLevel = UnitLevel("player")

    if EbonholdHub.Scoring and EbonholdHub.Scoring.ResetRunEchoes then
        EbonholdHub.Scoring.ResetRunEchoes()
    end
    if EbonholdHub.Automation and EbonholdHub.Automation.ResetPeakCache then
        EbonholdHub.Automation.ResetPeakCache()
    end
    -- Discard any in-flight pick state (pending choices, pending selection, timer)
    -- so stale data from the dead run cannot bleed into the new run.
    if EbonholdHub.Automation and EbonholdHub.Automation.ResetPickState then
        EbonholdHub.Automation.ResetPickState()
    end

    -- Refresh the log UI so it immediately shows the fresh empty session
    -- instead of retaining the previous run's entries.
    if EbonholdHub.SessionHistory and EbonholdHub.SessionHistory.RefreshLogViewIfVisible then
        EbonholdHub.SessionHistory.RefreshLogViewIfVisible()
    end

    return session
end


local function OnPlayerEnteringWorld()
    local level = UnitLevel("player")

    -- No active session: start one at the current level
    if not EbonholdHubDB.currentSessionIndex then
        CreateSession()
        return
    end

    -- Active session exists, but player is now level 1 after being higher:
    -- the run ended (death / reset to level 1).
    if level == 1 and maxLevel > 1 then
        EbonholdHub.Session.EndCurrentSession()
        CreateSession()
        return
    end

    -- Update max level if player leveled up while offline / zoning
    if level > maxLevel then
        maxLevel = level
    end
end

local function OnPlayerLevelUp(newLevel)
    if not EbonholdHubDB.currentSessionIndex then
        -- No session yet: start one at the current level
        CreateSession()
        return
    end

    if newLevel > maxLevel then
        maxLevel = newLevel
    end
end

local function OnPollUpdate(self, dt)
    pollElapsed = pollElapsed + dt
    if pollElapsed < POLL_INTERVAL then return end
    pollElapsed = 0

    local level = UnitLevel("player")

    -- Level reset detection: player went from >1 back to 1 (run ended).
    if EbonholdHubDB.currentSessionIndex and level == 1 and maxLevel > 1 then
        EbonholdHub.Session.EndCurrentSession()
        CreateSession()
        return
    end

    if level > maxLevel then
        maxLevel = level
    end
end


function EbonholdHub.Session.EndCurrentSession()
    local idx = EbonholdHubDB.currentSessionIndex
    if not idx then return end

    local session = EbonholdHubDB.sessions[idx]
    if not session then
        EbonholdHubDB.currentSessionIndex = nil
        return
    end

    session.endTime   = time()
    session.soulAshes = GetRunSoulAshes()
    session.maxLevel  = maxLevel
    EbonholdHubDB.currentSessionIndex = nil
    maxLevel = 0
    if EbonholdHub.Scoring and EbonholdHub.Scoring.ResetRunEchoes then
        EbonholdHub.Scoring.ResetRunEchoes()
    end
    if EbonholdHub.Automation and EbonholdHub.Automation.ResetPeakCache then
        EbonholdHub.Automation.ResetPeakCache()
    end
end

function EbonholdHub.Session.LogAction(scored, action, targetIndex)
    -- Detect run reset: player is level 1 but we tracked a higher peak.
    -- This catches resets that happen without a loading screen where
    -- PLAYER_ENTERING_WORLD never fires.
    local level = UnitLevel("player")
    if EbonholdHubDB.currentSessionIndex and level == 1 and maxLevel > 1 then
        EbonholdHub.Session.EndCurrentSession()
        CreateSession()
    end

    local idx = EbonholdHubDB.currentSessionIndex
    if not idx then
        -- No active session yet: create one on the fly so logs are never lost
        CreateSession()
        idx = EbonholdHubDB.currentSessionIndex
        if not idx then return end
    end

    local session = EbonholdHubDB.sessions[idx]
    if not session then return end

    local choices = {}
    for _, s in ipairs(scored) do
        choices[#choices + 1] = {
            name         = s.name,
            score        = s.selectScore or s.score,
            selectScore  = s.selectScore,
            quality      = s.quality,
            spellId      = s.spellId,
            tier         = s.tier,
            baseTier     = s.baseTier,
            isTierF      = s.isTierF,
            isZeroWeight = s.isZeroWeight,
            isRepeat     = s.isRepeat,
            isDuplicate  = s.isDuplicate,
            isBanned     = s.isBanned,
        }
    end

    local rd = EbonholdPlayerRunData
    if not rd and ProjectEbonhold and ProjectEbonhold.PlayerRunService then
        local get = ProjectEbonhold.PlayerRunService.GetCurrentData
        if get then rd = get() end
    end

    local charges = {
        ban    = (rd and rd.remainingBanishes) or 0,
        reroll = (rd and ((rd.totalRerolls or 0) - (rd.usedRerolls or 0))) or 0,
        freeze = (rd and ((rd.totalFreezes or 0) - (rd.usedFreezes or 0))) or 0,
    }

    local entry = {
        timestamp   = time(),
        level       = level,
        action      = action,
        choices     = choices,
        targetIndex = targetIndex,
        charges     = charges,
    }

    session.logs[#session.logs + 1] = entry

    if EbonholdHub.SessionHistory and EbonholdHub.SessionHistory.RefreshLogViewIfVisible then
        EbonholdHub.SessionHistory.RefreshLogViewIfVisible()
    end
end

function EbonholdHub.Session.AggregateStats(logs)
    local stats = {
        picks         = 0,
        banishesUsed  = 0,
        rerollsUsed   = 0,
        freezesUsed   = 0,
        qualityPicks  = { [0] = 0, [1] = 0, [2] = 0, [3] = 0, [4] = 0 },
        mostPicked    = {},
        mostBanned    = {},
    }

    for _, entry in ipairs(logs or {}) do
        local action = entry.action or ""
        local choice = entry.choices and entry.choices[entry.targetIndex]

        if action:match("^Select") then
            stats.picks = stats.picks + 1
            if choice and choice.name then
                local q = choice.quality or 0
                stats.qualityPicks[q] = (stats.qualityPicks[q] or 0) + 1
                stats.mostPicked[choice.name] = (stats.mostPicked[choice.name] or 0) + 1
            end
        elseif action:match("^Banish") then
            stats.banishesUsed = stats.banishesUsed + 1
            if choice and choice.name then
                stats.mostBanned[choice.name] = (stats.mostBanned[choice.name] or 0) + 1
            end
        elseif action:match("^Reroll") then
            stats.rerollsUsed = stats.rerollsUsed + 1
        elseif action:match("^Freeze") then
            stats.freezesUsed = stats.freezesUsed + 1
        end
    end

    return stats
end

function EbonholdHub.Session.FormatTopEntry(counts)
    local bestName, bestCount = nil, 0
    for name, count in pairs(counts or {}) do
        if count > bestCount then
            bestName, bestCount = name, count
        end
    end
    if not bestName then return "-" end
    return string.format("%s (%d)", bestName, bestCount)
end

function EbonholdHub.Session.GetSessions()
    return EbonholdHubDB.sessions or {}
end

function EbonholdHub.Session.GetActiveSession()
    local idx = EbonholdHubDB.currentSessionIndex
    if not idx then return nil end
    return EbonholdHubDB.sessions[idx]
end

function EbonholdHub.Session.DeleteSession(id)
    -- Refuse to delete the active session individually
    if EbonholdHubDB.currentSessionIndex then
        local active = EbonholdHubDB.sessions[EbonholdHubDB.currentSessionIndex]
        if active and active.id == id then
            return false
        end
    end

    local sessions = EbonholdHubDB.sessions
    for i, s in ipairs(sessions) do
        if s.id == id then
            if EbonholdHubDB.currentSessionIndex and i < EbonholdHubDB.currentSessionIndex then
                EbonholdHubDB.currentSessionIndex = EbonholdHubDB.currentSessionIndex - 1
            end
            table.remove(sessions, i)
            return true
        end
    end
    return false
end

function EbonholdHub.Session.ClearAllSessions()
    EbonholdHubDB.sessions = {}
    EbonholdHubDB.currentSessionIndex = nil
    maxLevel = 0
    if EbonholdHub.Scoring and EbonholdHub.Scoring.ResetRunEchoes then
        EbonholdHub.Scoring.ResetRunEchoes()
    end
    if EbonholdHub.Automation and EbonholdHub.Automation.ResetPeakCache then
        EbonholdHub.Automation.ResetPeakCache()
    end
    -- Immediately create a fresh session at the current player level
    CreateSession()
end

function EbonholdHub.Session.DeleteLogEntry(sessionId, logIndex)
    local sessions = EbonholdHubDB.sessions
    for _, s in ipairs(sessions) do
        if s.id == sessionId then
            if s.logs and logIndex >= 1 and logIndex <= #s.logs then
                table.remove(s.logs, logIndex)
                return true
            end
            return false
        end
    end
    return false
end


function EbonholdHub.Session.Init()
    EbonholdHubDB.sessions = EbonholdHubDB.sessions or {}
    if EbonholdHubDB.currentSessionIndex == nil then
        EbonholdHubDB.currentSessionIndex = nil  -- normalize falsey
    end

    -- Death / run-end is detected via PLAYER_ENTERING_WORLD (loading screen)
    -- and the polling frame (level reset to 1 mid-session without loading screen).
    -- We intentionally do NOT hook ProjectEbonhold.PlayerRunService.AcceptDeath
    -- because modifying a native ProjectEbonhold API during death processing can
    -- interfere with the server's run-end flow and cause disconnects.

    -- Event frame for lifecycle detection
    local ef = CreateFrame("Frame", nil, UIParent)
    ef:RegisterEvent("PLAYER_ENTERING_WORLD")
    ef:RegisterEvent("PLAYER_LEVEL_UP")
    ef:SetScript("OnEvent", function(self, event, ...)
        if event == "PLAYER_ENTERING_WORLD" then
            OnPlayerEnteringWorld()
        elseif event == "PLAYER_LEVEL_UP" then
            OnPlayerLevelUp(...)
        end
    end)

    -- Polling frame for level reset detection without loading screen
    pollFrame = CreateFrame("Frame", nil, UIParent)
    pollFrame:SetScript("OnUpdate", OnPollUpdate)

    if EbonholdHubDB.currentSessionIndex and EbonholdHub.Scoring and EbonholdHub.Scoring.RebuildRunEchoesFromSession then
        EbonholdHub.Scoring.RebuildRunEchoesFromSession()
    end
end
