EbonholdHub.Talents = {}

local T = EbonholdHub.Talents

T.MAX_POINTS = 93

function T.MakeKey(tab, talentIndex)
    return tab .. ":" .. talentIndex
end

function T.ParseKey(key)
    local tab, idx = tostring(key):match("^(%d+):(%d+)$")
    return tonumber(tab), tonumber(idx)
end

function T.ClonePlan(talents)
    local out = {}
    if talents then
        for k, v in pairs(talents) do
            if type(v) == "number" and v > 0 then
                out[k] = v
            end
        end
    end
    return out
end

function T.CloneOrder(order)
    local out = {}
    if order then
        for i, step in ipairs(order) do
            out[i] = {
                tab    = step.tab,
                talent = step.talent,
                rank   = step.rank,
                name   = step.name,
            }
        end
    end
    return out
end

function T.GetRank(plan, tab, talentIndex)
    if not plan then return 0 end
    return plan[T.MakeKey(tab, talentIndex)] or 0
end

function T.SetRank(plan, tab, talentIndex, rank)
    local key = T.MakeKey(tab, talentIndex)
    if rank and rank > 0 then
        plan[key] = rank
    else
        plan[key] = nil
    end
end

function T.TotalPoints(plan)
    local total = 0
    if not plan then return 0 end
    for _, rank in pairs(plan) do
        if type(rank) == "number" then
            total = total + rank
        end
    end
    return total
end

function T.PointsInTab(plan, tab)
    local total = 0
    if not plan then return 0 end
    local prefix = tab .. ":"
    for key, rank in pairs(plan) do
        if type(key) == "string" and key:sub(1, #prefix) == prefix and type(rank) == "number" then
            total = total + rank
        end
    end
    return total
end

function T.EnsureBuild(build)
    build.talents = build.talents or {}
    build.talentOrder = build.talentOrder or {}
end

function T.ScanTrees()
    if not GetNumTalentTabs or not GetNumTalents or not GetTalentInfo then
        return nil
    end

    local tabs = {}
    local numTabs = GetNumTalentTabs()
    for tab = 1, numTabs do
        local name, icon, pointsSpent, background = GetTalentTabInfo(tab)
        local talents = {}
        local maxTier, maxCol = 0, 0
        local numTalents = GetNumTalents(tab)
        for ti = 1, numTalents do
            local tName, tIcon, tier, column, rank, maxRank =
                GetTalentInfo(tab, ti)
            tier = tier or 1
            column = column or 1
            maxRank = maxRank or 1
            if tier > maxTier then maxTier = tier end
            if column > maxCol then maxCol = column end
            talents[ti] = {
                tab     = tab,
                index   = ti,
                name    = tName or ("Talent " .. ti),
                icon    = tIcon,
                tier    = tier,
                column  = column,
                maxRank = maxRank,
                playerRank = rank or 0,
            }
        end
        tabs[tab] = {
            index       = tab,
            name        = name or ("Tree " .. tab),
            icon        = icon,
            background  = background,
            pointsSpent = pointsSpent or 0,
            talents     = talents,
            maxTier     = math.max(maxTier, 1),
            maxCol      = math.max(maxCol, 1),
        }
    end
    return tabs
end

function T.GetTabCount(trees)
    if not trees then return 0 end
    local n = 0
    while trees[n + 1] do n = n + 1 end
    return n
end

function T.ForEachTab(trees, fn)
    if not trees or not fn then return end
    for tab = 1, T.GetTabCount(trees) do
        fn(tab, trees[tab])
    end
end

function T.ReadPlayerPlan(trees)
    trees = trees or T.ScanTrees()
    if not trees then return {}, {} end

    local plan = {}
    local order = {}
    T.ForEachTab(trees, function(tab, tree)
        for ti, talent in pairs(tree.talents) do
            local rank = talent.playerRank or 0
            if rank > 0 then
                T.SetRank(plan, tab, ti, rank)
                for r = 1, rank do
                    order[#order + 1] = {
                        tab = tab, talent = ti, rank = r, name = talent.name,
                    }
                end
            end
        end
    end)
    return plan, order
end

function T.RefreshPlayerRanks(trees)
    if not trees then return end
    T.ForEachTab(trees, function(tab, tree)
        for ti in pairs(tree.talents) do
            local _, _, _, _, rank = GetTalentInfo(tab, ti)
            tree.talents[ti].playerRank = rank or 0
        end
    end)
end

function T.AddPoint(plan, order, tab, talentIndex, talentInfo)
    if not plan or not talentInfo then return false end
    local current = T.GetRank(plan, tab, talentIndex)
    if current >= (talentInfo.maxRank or 1) then return false end
    if T.TotalPoints(plan) >= T.MAX_POINTS then return false end

    T.SetRank(plan, tab, talentIndex, current + 1)
    if order then
        order[#order + 1] = {
            tab = tab, talent = talentIndex, rank = current + 1, name = talentInfo.name,
        }
    end
    return true
end

function T.RemovePoint(plan, order, tab, talentIndex)
    if not plan then return false end
    local current = T.GetRank(plan, tab, talentIndex)
    if current <= 0 then return false end

    T.SetRank(plan, tab, talentIndex, current - 1)
    if order then
        for i = #order, 1, -1 do
            local step = order[i]
            if step.tab == tab and step.talent == talentIndex and step.rank == current then
                table.remove(order, i)
                break
            end
        end
    end
    return true
end

function T.ClearPlan(plan, order)
    if plan then
        for k in pairs(plan) do plan[k] = nil end
    end
    if order then
        for i = #order, 1, -1 do order[i] = nil end
    end
end

function T.FindTalent(trees, tab, talentIndex)
    local tree = trees and trees[tab]
    return tree and tree.talents and tree.talents[talentIndex]
end

function T.OrderSummary(order, trees)
    local lines = {}
    for i, step in ipairs(order or {}) do
        local info = T.FindTalent(trees, step.tab, step.talent)
        local maxRank = info and info.maxRank or step.rank or 1
        local name = step.name or (info and info.name) or "?"
        lines[#lines + 1] = string.format("%d. %s (%d/%d)", i, name, step.rank or 0, maxRank)
    end
    return lines
end
