EbonholdHub.EquipmentLoadout = {}



local AD = EbonholdHub.AffixData



local DEFAULT_SET_NAME = "Main"

local MAX_SETS = 8



local function CloneEntry(entry)

    if not entry then return nil end

    return {

        itemId  = entry.itemId,

        name    = entry.name,

        link    = entry.link,

        quality = entry.quality,

        texture = entry.texture,

    }

end



function EbonholdHub.EquipmentLoadout.NewSetId()

    return string.format("%08x%04x", time(), math.random(0, 65535))

end



function EbonholdHub.EquipmentLoadout.Clone(loadout)

    local copy = {}

    if type(loadout) ~= "table" then return copy end

    for key, entry in pairs(loadout) do

        copy[key] = CloneEntry(entry)

    end

    return copy

end



function EbonholdHub.EquipmentLoadout.CloneSet(set)

    if type(set) ~= "table" then return nil end

    return {

        id      = set.id or EbonholdHub.EquipmentLoadout.NewSetId(),

        name    = set.name or DEFAULT_SET_NAME,

        loadout = EbonholdHub.EquipmentLoadout.Clone(set.loadout),

    }

end



function EbonholdHub.EquipmentLoadout.CloneSets(sets)

    local copy = {}

    if type(sets) ~= "table" then return copy end

    for i, set in ipairs(sets) do

        copy[i] = EbonholdHub.EquipmentLoadout.CloneSet(set)

    end

    return copy

end



function EbonholdHub.EquipmentLoadout.MigrateBuild(build)

    if not build then return end

    if build.equipmentSets and #build.equipmentSets > 0 then

        for _, set in ipairs(build.equipmentSets) do

            set.loadout = set.loadout or {}

            set.name = (set.name and set.name ~= "") and set.name or DEFAULT_SET_NAME

            set.id = set.id or EbonholdHub.EquipmentLoadout.NewSetId()

        end

        local idx = build.equipmentSetIndex or 1

        build.equipmentSetIndex = math.min(math.max(1, idx), #build.equipmentSets)

    else

        build.equipmentSets = {

            {

                id = EbonholdHub.EquipmentLoadout.NewSetId(),

                name = DEFAULT_SET_NAME,

                loadout = EbonholdHub.EquipmentLoadout.Clone(build.equipmentLoadout),

            },

        }

        build.equipmentSetIndex = 1

    end

    build.equipmentLoadout = build.equipmentSets[1].loadout

end



function EbonholdHub.EquipmentLoadout.Ensure(build)

    EbonholdHub.EquipmentLoadout.MigrateBuild(build)

    return build.equipmentSets[build.equipmentSetIndex or 1].loadout

end



local function PendingSets()

    EbonholdHubDB.pendingEquipmentSets = EbonholdHubDB.pendingEquipmentSets or {

        {

            id = EbonholdHub.EquipmentLoadout.NewSetId(),

            name = DEFAULT_SET_NAME,

            loadout = EbonholdHub.EquipmentLoadout.Clone(EbonholdHubDB.pendingEquipmentLoadout),

        },

    }

    EbonholdHubDB.pendingEquipmentSetIndex = EbonholdHubDB.pendingEquipmentSetIndex or 1

    return EbonholdHubDB.pendingEquipmentSets, EbonholdHubDB.pendingEquipmentSetIndex

end



function EbonholdHub.EquipmentLoadout.GetSetsForBuild(build)
    if not build then return {}, 1 end
    EbonholdHub.EquipmentLoadout.MigrateBuild(build)
    return build.equipmentSets, build.equipmentSetIndex or 1
end

function EbonholdHub.EquipmentLoadout.SetActiveSetIndexForBuild(build, index)
    if not build then return end
    EbonholdHub.EquipmentLoadout.MigrateBuild(build)
    index = math.min(math.max(1, index or 1), #build.equipmentSets)
    build.equipmentSetIndex = index
end

function EbonholdHub.EquipmentLoadout.GetLoadoutForBuild(build, index)
    if not build then return {} end
    EbonholdHub.EquipmentLoadout.MigrateBuild(build)
    index = index or build.equipmentSetIndex or 1
    local set = build.equipmentSets[index]
    return set and set.loadout or {}
end

function EbonholdHub.EquipmentLoadout.GetSets()

    if EbonholdHubDB._isEditingBuild then

        return PendingSets()

    end

    local build = EbonholdHub.Build.GetActive()

    if build then

        EbonholdHub.EquipmentLoadout.MigrateBuild(build)

        return build.equipmentSets, build.equipmentSetIndex or 1

    end

    return PendingSets()

end



function EbonholdHub.EquipmentLoadout.GetActiveSetIndex()

    local _, idx = EbonholdHub.EquipmentLoadout.GetSets()

    return idx or 1

end



function EbonholdHub.EquipmentLoadout.SetActiveSetIndex(index)

    local sets = EbonholdHub.EquipmentLoadout.GetSets()

    index = math.min(math.max(1, index or 1), #sets)

    if EbonholdHubDB._isEditingBuild then

        EbonholdHubDB.pendingEquipmentSetIndex = index

        return

    end

    local build = EbonholdHub.Build.GetActive()

    if build then

        EbonholdHub.EquipmentLoadout.MigrateBuild(build)

        build.equipmentSetIndex = index

    end

end



function EbonholdHub.EquipmentLoadout.GetActiveSet()

    local sets, idx = EbonholdHub.EquipmentLoadout.GetSets()

    return sets[idx]

end



function EbonholdHub.EquipmentLoadout.GetActive()

    local set = EbonholdHub.EquipmentLoadout.GetActiveSet()

    if not set then return {} end

    set.loadout = set.loadout or {}

    return set.loadout

end



function EbonholdHub.EquipmentLoadout.NextSetName(sets)

    sets = sets or EbonholdHub.EquipmentLoadout.GetSets()

    local n = #sets + 1

    local base = "Set " .. n

    local used = {}

    for _, set in ipairs(sets) do

        used[set.name or ""] = true

    end

    if not used[base] then return base end

    for i = 2, MAX_SETS + 5 do

        local candidate = "Set " .. i

        if not used[candidate] then return candidate end

    end

    return base

end



function EbonholdHub.EquipmentLoadout.AddSet(name, cloneCurrent)

    local sets = EbonholdHub.EquipmentLoadout.GetSets()

    if #sets >= MAX_SETS then return nil end

    local loadout = {}

    if cloneCurrent ~= false then

        loadout = EbonholdHub.EquipmentLoadout.Clone(EbonholdHub.EquipmentLoadout.GetActive())

    end

    local set = {

        id = EbonholdHub.EquipmentLoadout.NewSetId(),

        name = name or EbonholdHub.EquipmentLoadout.NextSetName(sets),

        loadout = loadout,

    }

    sets[#sets + 1] = set

    EbonholdHub.EquipmentLoadout.SetActiveSetIndex(#sets)

    return set

end



function EbonholdHub.EquipmentLoadout.RemoveSet(index)

    local sets = EbonholdHub.EquipmentLoadout.GetSets()

    if #sets <= 1 then return false end

    index = index or EbonholdHub.EquipmentLoadout.GetActiveSetIndex()

    if index < 1 or index > #sets then return false end

    table.remove(sets, index)

    local active = EbonholdHub.EquipmentLoadout.GetActiveSetIndex()

    if active > #sets then active = #sets end

    EbonholdHub.EquipmentLoadout.SetActiveSetIndex(active)

    if not EbonholdHubDB._isEditingBuild then

        local build = EbonholdHub.Build.GetActive()

        if build then build.equipmentLoadout = sets[1].loadout end

    end

    return true

end



function EbonholdHub.EquipmentLoadout.RenameSet(index, name)
    local sets = EbonholdHub.EquipmentLoadout.GetSets()
    return EbonholdHub.EquipmentLoadout.RenameSetIn(sets, index or EbonholdHub.EquipmentLoadout.GetActiveSetIndex(), name)
end

function EbonholdHub.EquipmentLoadout.RenameSetIn(sets, index, name)
    if strtrim then
        name = strtrim(name or "")
    else
        name = (name or ""):match("^%s*(.-)%s*$") or ""
    end
    if name == "" or type(sets) ~= "table" then return false end
    index = index or 1
    local set = sets[index]
    if not set then return false end
    set.name = name
    return true
end

function EbonholdHub.EquipmentLoadout.RemoveSetFrom(sets, index, getActiveIndex, setActiveIndex)
    if type(sets) ~= "table" or #sets <= 1 then return false end
    index = index or 1
    if index < 1 or index > #sets then return false end
    local active = getActiveIndex and getActiveIndex() or EbonholdHub.EquipmentLoadout.GetActiveSetIndex()
    table.remove(sets, index)
    if active > #sets then
        active = #sets
    elseif active > index then
        active = active - 1
    end
    if setActiveIndex then
        setActiveIndex(active)
    else
        EbonholdHub.EquipmentLoadout.SetActiveSetIndex(active)
    end
    if not EbonholdHubDB._isEditingBuild then
        local build = EbonholdHub.Build.GetActive()
        if build then build.equipmentLoadout = sets[1] and sets[1].loadout end
    end
    return true
end

function EbonholdHub.EquipmentLoadout.DuplicateSet(index)

    local sets = EbonholdHub.EquipmentLoadout.GetSets()

    if #sets >= MAX_SETS then return nil end

    index = index or EbonholdHub.EquipmentLoadout.GetActiveSetIndex()

    local src = sets[index]

    if not src then return nil end

    local set = {

        id = EbonholdHub.EquipmentLoadout.NewSetId(),

        name = (src.name or DEFAULT_SET_NAME) .. " copy",

        loadout = EbonholdHub.EquipmentLoadout.Clone(src.loadout),

    }

    sets[#sets + 1] = set

    EbonholdHub.EquipmentLoadout.SetActiveSetIndex(#sets)

    return set

end



function EbonholdHub.EquipmentLoadout.ApplySetsToBuild(build, sets, activeIndex)

    if not build then return end

    build.equipmentSets = EbonholdHub.EquipmentLoadout.CloneSets(sets or {})

    if #build.equipmentSets == 0 then

        build.equipmentSets = {

            {

                id = EbonholdHub.EquipmentLoadout.NewSetId(),

                name = DEFAULT_SET_NAME,

                loadout = {},

            },

        }

    end

    build.equipmentSetIndex = math.min(math.max(1, activeIndex or 1), #build.equipmentSets)

    build.equipmentLoadout = build.equipmentSets[1].loadout

end



function EbonholdHub.EquipmentLoadout.InitPendingFromBuild(build)

    EbonholdHub.EquipmentLoadout.MigrateBuild(build)

    EbonholdHubDB.pendingEquipmentSets = EbonholdHub.EquipmentLoadout.CloneSets(build.equipmentSets)

    EbonholdHubDB.pendingEquipmentSetIndex = build.equipmentSetIndex or 1

    EbonholdHubDB.pendingEquipmentLoadout = nil

end



function EbonholdHub.EquipmentLoadout.InitPendingEmpty()

    EbonholdHubDB.pendingEquipmentSets = {

        {

            id = EbonholdHub.EquipmentLoadout.NewSetId(),

            name = DEFAULT_SET_NAME,

            loadout = {},

        },

    }

    EbonholdHubDB.pendingEquipmentSetIndex = 1

    EbonholdHubDB.pendingEquipmentLoadout = nil

end



function EbonholdHub.EquipmentLoadout.ClearPending()

    EbonholdHubDB.pendingEquipmentSets = nil

    EbonholdHubDB.pendingEquipmentSetIndex = nil

    EbonholdHubDB.pendingEquipmentLoadout = nil

end



function EbonholdHub.EquipmentLoadout.PendingPayload()

    local sets, idx = PendingSets()

    return {

        equipmentSets = EbonholdHub.EquipmentLoadout.CloneSets(sets),

        equipmentSetIndex = idx,

        equipmentLoadout = EbonholdHub.EquipmentLoadout.Clone(sets[idx] and sets[idx].loadout or {}),

    }

end



function EbonholdHub.EquipmentLoadout.GetSlot(slotKey)

    return EbonholdHub.EquipmentLoadout.GetActive()[slotKey]

end



function EbonholdHub.EquipmentLoadout.SetSlot(slotKey, entry)

    local loadout = EbonholdHub.EquipmentLoadout.GetActive()

    if entry then

        if EbonholdHub.ItemCatalog and EbonholdHub.ItemCatalog.EnrichEntry then

            EbonholdHub.ItemCatalog.EnrichEntry(entry)

        end

        loadout[slotKey] = CloneEntry(entry)

    else

        loadout[slotKey] = nil

    end

end



function EbonholdHub.EquipmentLoadout.ParseItemLink(link)

    if not link or link == "" then return nil end

    local itemId = tonumber(link:match("item:(%d+)"))

    if not itemId then return nil end

    local name, linkRet, quality, _, _, _, _, _, _, texture = GetItemInfo(itemId)

    if not name then

        name = link:match("%[(.-)%]") or ("Item " .. itemId)

    end

    return {

        itemId  = itemId,

        name    = name,

        link    = linkRet or link,

        quality = quality or 0,

        texture = texture,

    }

end



function EbonholdHub.EquipmentLoadout.ReadUnitSlot(slotDef, unit)

    if not slotDef or not slotDef.invSlot then return nil end

    unit = unit or "player"

    local link = GetInventoryItemLink(unit, slotDef.invSlot)

    return EbonholdHub.EquipmentLoadout.ParseItemLink(link)

end



function EbonholdHub.EquipmentLoadout.ReadPlayerSlot(slotDef)

    return EbonholdHub.EquipmentLoadout.ReadUnitSlot(slotDef, "player")

end



function EbonholdHub.EquipmentLoadout.CopyFromUnitTo(unit, loadout)

    loadout = loadout or EbonholdHub.EquipmentLoadout.GetActive()

    unit = unit or "player"

    for _, slot in ipairs(AD.SLOTS) do

        local entry = EbonholdHub.EquipmentLoadout.ReadUnitSlot(slot, unit)

        if entry then

            if EbonholdHub.ItemCatalog and EbonholdHub.ItemCatalog.EnrichEntry then

                EbonholdHub.ItemCatalog.EnrichEntry(entry)

            end

            loadout[slot.key] = CloneEntry(entry)

        end

    end

    return EbonholdHub.EquipmentLoadout.CountFilled(loadout)

end



function EbonholdHub.EquipmentLoadout.CopyFromPlayerTo(loadout)

    return EbonholdHub.EquipmentLoadout.CopyFromUnitTo("player", loadout)

end



function EbonholdHub.EquipmentLoadout.CopyFromPlayer()

    EbonholdHub.EquipmentLoadout.CopyFromPlayerTo(EbonholdHub.EquipmentLoadout.GetActive())

end



function EbonholdHub.EquipmentLoadout.ClearAll()

    local loadout = EbonholdHub.EquipmentLoadout.GetActive()

    for key in pairs(loadout) do

        loadout[key] = nil

    end

end



function EbonholdHub.EquipmentLoadout.GetItemIcon(entry)

    if not entry then return nil end

    if EbonholdHub.ItemCatalog and EbonholdHub.ItemCatalog.EnrichEntry then

        EbonholdHub.ItemCatalog.EnrichEntry(entry)

    end

    if entry.texture then return entry.texture end

    if entry.itemId then

        local _, _, _, _, _, _, _, _, _, texture = GetItemInfo(entry.itemId)

        if texture then

            entry.texture = texture

            return texture

        end

        if EbonholdHub.ItemCatalog and EbonholdHub.ItemCatalog.PrimeItemId then

            EbonholdHub.ItemCatalog.PrimeItemId(entry.itemId)

        end

    end

    return nil

end



function EbonholdHub.EquipmentLoadout.CountFilled(loadout)

    loadout = loadout or EbonholdHub.EquipmentLoadout.GetActive()

    local n = 0

    for _, entry in pairs(loadout) do

        if entry and entry.name then n = n + 1 end

    end

    return n

end



function EbonholdHub.EquipmentLoadout.ListFilled(loadout)

    loadout = loadout or EbonholdHub.EquipmentLoadout.GetActive()

    local rows = {}

    for _, slot in ipairs(AD.SLOTS) do

        local entry = loadout[slot.key]

        if entry and entry.name then

            rows[#rows + 1] = {

                slotKey   = slot.key,

                slotLabel = slot.label,

                itemId    = entry.itemId,

                name      = entry.name,

                link      = entry.link,

                quality   = entry.quality or 0,

            }

        end

    end

    return rows

end



function EbonholdHub.EquipmentLoadout.ChecksumPart(loadout)

    if type(loadout) ~= "table" then return "" end

    local keys = {}

    for key in pairs(loadout) do keys[#keys + 1] = key end

    table.sort(keys)

    local parts = {}

    for _, key in ipairs(keys) do

        local e = loadout[key]

        if e and e.name then

            parts[#parts + 1] = key .. "=" .. tostring(e.itemId or e.name)

        end

    end

    return table.concat(parts, ";")

end



function EbonholdHub.EquipmentLoadout.ChecksumPartForBuild(build)

    if not build then return "" end

    EbonholdHub.EquipmentLoadout.MigrateBuild(build)

    local parts = {}

    for i, set in ipairs(build.equipmentSets) do

        parts[#parts + 1] = tostring(set.id or i) .. ":" .. (set.name or "") .. ":"

            .. EbonholdHub.EquipmentLoadout.ChecksumPart(set.loadout)

    end

    return table.concat(parts, "|")

end


