local _AddonName, _EchoRaidTools = ...;


local module = _EchoRaidTools:GetModule("todo-list")

local AbbreviateNumbers = module.AbbreviateNumbers
local minWithNilCheck = module.MinWithNilCheck

--[[
one big flaw of this system is that we cannot (in a neat way) change events on demand. does this matter?

another one is that it's kinda awkward to show alt info about these 'containers'. we could cache min/max/current/progresstext
you don't really want to see the account bound stuff when browsing alts though
nor titles/dividers

]] --

---@alias importType "quest" | "achievement" | "currency" | "custom"  | "divider" | "title" | "reputation" | "vault"


local function processCustomImport(state)
    if not state.loadstring then return end
    local events = state.events
    local func, error = loadstring(state.loadstring)

    if func then
        return 0, func, events
    end
end



local function processQuestImport(state)
    if not state.questIds or #state.questIds == 0 then return end

    local events = { "QUEST_COMPLETE", "QUEST_DATA_LOAD_RESULT", "QUEST_LOG_UPDATE" }
    local totalQuests = #state.questIds
    state.total = minWithNilCheck(state.total, totalQuests)
    local questCheckFunc;

    state.unregisterOnCompletion = true
    if state.questState == "warbound" then
        questCheckFunc = C_QuestLog.IsQuestFlaggedCompletedOnAccount
    elseif state.questState == "done" then
        questCheckFunc = function(questId)
            return C_QuestLog.IsComplete(questId) or C_QuestLog.IsQuestFlaggedCompleted(questId)
        end
        state.unregisterOnCompletion = false
    elseif state.questState == "pickup" then
        questCheckFunc = function(questId)
            return C_QuestLog.GetLogIndexForQuestID(questId) or C_QuestLog.IsQuestFlaggedCompleted(questId)
        end
        state.unregisterOnCompletion = false
    elseif state.questState == "hasRepBonus" then
        questCheckFunc = function(questId)
            return not C_QuestLog.QuestContainsFirstTimeRepBonusForPlayer(questId)
        end
    else
        questCheckFunc = C_QuestLog.IsQuestFlaggedCompleted
    end
    for i, questId in pairs(state.questIds) do
        C_QuestLog.RequestLoadQuestByID(questId)
    end

    local function func(event, ...)
        if event == "QUEST_DATA_LOAD_RESULT" then
            local questID, success = ...
            if not success then return end
            if not tContains(state.questIds, questID) then return end
        end

        if not state.text then
            for i, questId in ipairs(state.questIds) do
                local text = C_QuestLog.GetTitleForQuestID(questId)
                if text then
                    state.text = text
                    break
                end
            end
        end
        if not state.text then return end
        state.icon = state.icon or module.GetIconForQuest(state.questIds[1])
        local questsCompleted = 0

        for i, questId in ipairs(state.questIds) do
            if questCheckFunc(questId) then
                questsCompleted = questsCompleted + 1
            end
        end
        state.value = questsCompleted;

        -- if state.value == state.total then
        --state.progressText = "DONE"
        --state.progressTextColour = { 0, 255, 0, 1 }
        --  else
        state.progressText = string.format("%s/%s", AbbreviateNumbers(state.value), AbbreviateNumbers(state.total))
        -- end
        return state
    end




    return 0, func, events
end

local function processCurrencyImport(state)
    if not state then return end
    if not state.currencyId then return end
    local events = { "ACCOUNT_CHARACTER_CURRENCY_DATA_RECEIVED", "CURRENCY_TRANSFER_SUCCESS", "CURRENCY_DISPLAY_UPDATE",
        "CHAT_MSG_CURRENCY" }
    local currencyInfo = C_CurrencyInfo.GetCurrencyInfo(state.currencyId)
    if not currencyInfo then return end
    state.text = state.text or currencyInfo.name
    state.icon = state.icon or currencyInfo.iconFileID
    state.total = state.total or math.huge
    if currencyInfo.maxQuantity > 0 then
        state.oldTotal = state.total
        state.total = minWithNilCheck(state.oldTotal, currencyInfo.maxQuantity)
    end
    local function func(event, ...)
        local id, amount = ...
        if id and id ~= state.currencyId then return end
        if id == state.currencyId and amount then
            state.value = amount
        else
            currencyInfo = C_CurrencyInfo.GetCurrencyInfo(state.currencyId)
            if not currencyInfo then return end
            state.value = currencyInfo.quantity
            if currencyInfo.maxQuantity > 0 then
                state.total = minWithNilCheck(state.oldTotal, currencyInfo.maxQuantity)
            end
        end
        state.progressText = string.format("%s/%s", AbbreviateNumbers(state.value), AbbreviateNumbers(state.total))
        return state
    end

    return 0, func, events
end

local function processAchievementImport(state)
    if not state then return end
    if not state.achievementId then return end
    local events = {}
    local id, name, points, completed, month, day, year, description, flags,
    icon, rewardText, isGuild, wasEarnedByMe, earnedBy, isStatistic = GetAchievementInfo(state.achievementId)
    state.text = state.text or name
    state.icon = state.icon or icon



    local func
    local criterias = GetAchievementNumCriteria(state.achievementId)

    if criterias == 0 then
        state.total = 1
        state.value = 0
        function func(event, ...)
            local criteriaString, criteriaType, isCompleted, quantity, reqQuantity, charName, flags, assetID, quantityString, criteriaID, eligible, duration, elapsed =
                GetAchievementCriteriaInfoByID(state.achievementId, 0)
            if isCompleted then
                state.value = 1
            end

            if not state.text then
                id, name, points, completed, month, day, year, description, flags,
                icon, rewardText, isGuild, wasEarnedByMe, earnedBy, isStatistic = GetAchievementInfo(state.achievementId)
                state.text = name
            end

            state.progressText = string.format("%s/%s", AbbreviateNumbers(state.value), AbbreviateNumbers(state.total))
            return state
        end
    elseif criterias == 1 then
        function func(event, ...)
            local criteriaString, criteriaType, isCompleted, quantity, reqQuantity, charName, flags, assetID, quantityString, criteriaID, eligible, duration, elapsed =
                GetAchievementCriteriaInfo(state.achievementId, 1)

            state.value = quantity
            state.total = state.total or reqQuantity
            state.progressText = string.format("%s/%s", AbbreviateNumbers(state.value), AbbreviateNumbers(state.total))
            if not state.text then
                id, name, points, completed, month, day, year, description, flags,
                icon, rewardText, isGuild, wasEarnedByMe, earnedBy, isStatistic = GetAchievementInfo(state.achievementId)
                state.text = name
            end
            return state
        end
    else
        state.total = state.total or criterias
        function func(event, ...)
            local completes = 0
            for i = 1, criterias do
                local criteriaString, criteriaType, isCompleted, quantity, reqQuantity, charName, flags, assetID, quantityString, criteriaID, eligible, duration, elapsed =
                    GetAchievementCriteriaInfo(state.achievementId, i)
                if isCompleted then
                    completes = completes + 1
                end
            end

            state.value = completes
            state.progressText = string.format("%s/%s", AbbreviateNumbers(state.value), AbbreviateNumbers(state.total))
            if not state.text then
                id, name, points, completed, month, day, year, description, flags,
                icon, rewardText, isGuild, wasEarnedByMe, earnedBy, isStatistic = GetAchievementInfo(state.achievementId)
                state.text = name
            end

            return state
        end
    end


    return 0, func, events
end


local function processTitleImport(state)
    state.value = state.filled and 1 or 0
    state.total = 1
    state.min = 0
    state.text = state.text or ""
    state.progressText = state.progressText or ""
    state.hideOnComplete = false
    local function func()
        return state
    end

    return 0, func, {}
end

local function processReputationImport(state)
    if not state.reputationId then return end
    local repData = C_Reputation.GetFactionDataByID(state.reputationId)
    if not repData then return end
    state.text = state.text or repData.name
    state.icon = state.icon or module.GetIconForReputation(state.reputationId)
    state.unregisterOnCompletion = true
    local majorFactionData = C_MajorFactions.GetMajorFactionData(state.reputationId)
    local friendshipInfo = C_GossipInfo.GetFriendshipReputation(state.reputationId)
    local func
    local events = { "UPDATE_FACTION" } -- TODO check what args are provided
    if majorFactionData then
        local renownLevels = #C_MajorFactions.GetRenownLevels(state.reputationId)

        state.oldTotal = minWithNilCheck(state.total, renownLevels)

        state.total = renownLevels * majorFactionData.renownLevelThreshold
        function func(event, ...)
            local factionData = C_MajorFactions.GetMajorFactionData(state.reputationId)
            if not factionData then return end
            state.value = (factionData.renownLevelThreshold * factionData.renownLevel) +
                factionData.renownReputationEarned
            if renownLevels == factionData.renownLevel then
                state.progressText = string.format("Renown %s/%s",
                    factionData.renownLevel,
                    renownLevels)
            else
                state.progressText = string.format("%s/%s Renown %s/%s", factionData.renownReputationEarned,
                    factionData.renownLevelThreshold,
                    factionData.renownLevel,
                    state.oldTotal)
            end

            return state
        end
    elseif friendshipInfo and friendshipInfo.friendshipFactionID > 0 then
        state.min = 1
        function func(event, ...)
            local rankInfo = C_GossipInfo.GetFriendshipReputationRanks(state.reputationId)
            if not rankInfo then return end
            local maxRank = rankInfo.maxLevel
            local currentLevel = rankInfo.currentLevel

            local repInfo = C_GossipInfo.GetFriendshipReputation(state.reputationId)
            local xpMax = repInfo.nextThreshold - repInfo.reactionThreshold
            local xpCurrent = repInfo.standing - repInfo.reactionThreshold
            state.total = minWithNilCheck(state.total, maxRank)
            state.value = currentLevel + (xpCurrent / xpMax)

            state.progressText = string.format("%s/%s - %s/%s", AbbreviateNumbers(xpCurrent), AbbreviateNumbers(xpMax),
                currentLevel, state.total)
            return state
        end
    else
        state.total = state.total or 42000
        state.min = 3000
        function func(event, ...)
            local reputationData = C_Reputation.GetFactionDataByID(state.reputationId)
            if not reputationData then return end
            state.value = reputationData.currentStanding
            local reaction = reputationData.reaction and tonumber(reputationData.reaction)
            local reactionText
            if reaction then
                reactionText = GetText("FACTION_STANDING_LABEL" .. reputationData.reaction, UnitSex("player") or 0, nil)
            end


            state.progressText = reactionText and
                string.format("%s - %s/%s", reactionText, AbbreviateNumbers(state.value), AbbreviateNumbers(state.total))
                or string.format("%s/%s", state.value, state.total)
            return state
        end
    end


    return 0, func, events
end




local function getVaultDisplayString(activityType, activity)
    if activityType == Enum.WeeklyRewardChestThresholdType.Activities then
        local difficultyId = C_WeeklyRewards.GetDifficultyIDForActivityTier(activity.activityTierID)
        if true then
            return DifficultyUtil.GetDifficultyName(difficultyId)
        else
            return "" --TODO M+ level (need to do m+ to get this one )

            --C_MythicPlus.GetRunHistory([includePreviousWeeks, includeIncompleteRuns])
        end
    elseif activityType == Enum.WeeklyRewardChestThresholdType.Raid then
        return DifficultyUtil.GetDifficultyName(activity.level)
    elseif activityType == Enum.WeeklyRewardChestThresholdType.World then
        return string.format(GREAT_VAULT_WORLD_TIER, activity.level)
    end
end

local function processVaultImport(state)
    --C_WeeklyRewards.CanClaimRewards()

    local events = { "WEEKLY_REWARDS_ITEM_CHANGED", "WEEKLY_REWARDS_UPDATE" }
    if not state.activtyID then return end
    state.slots = minWithNilCheck(state.total, 3)
    state.icon = state.icon or 4203076



    if not state.text then
        for activityName, id in pairs(Enum.WeeklyRewardChestThresholdType) do
            if id == state.activtyID then
                state.text = activityName
                break
            end
        end
    end

    local function func(event, ...)
        local activities = C_WeeklyRewards.GetActivities(state.activtyID)
        if not activities then return end
        if #activities == 0 then return end
        local progress = 0
        local displayString = ""
        for i, activity in ipairs(activities) do
            if activity.progress > progress then
                progress = activity.progress
            end
            if activity.index <= state.slots then
                if activity.level > 0 then
                    local s = getVaultDisplayString(state.activtyID, activity)
                    if s then
                        displayString = displayString .. s
                    end
                else
                    displayString = displayString .. activity.progress .. "/" .. activity.threshold
                end
                if activity.index < state.slots then
                    displayString = displayString .. ", "
                end
            end

            if not state.total then
                if activity.index == state.slots then
                    state.total = activity.threshold
                end
            end
        end
        state.value = progress
        state.progressText = displayString


        --TODO is it possible to check level of incomplete vaultbox?
        return state
    end


    return 0, func, events
end

local function processMoneyImport(state)
    local events = { "PLAYER_MONEY" }
    state.total = minWithNilCheck(state.total, 9999999.99999) * 10000
    state.text = state.text or "Gold"
    state.icon = state.icon or 133784 --generic gold icon
    local function func(event, ...)
        state.value = GetMoney()
        state.progressText = string.format("%s/%s", AbbreviateNumbers(math.ceil(state.value / 10000)),
            AbbreviateNumbers(math.ceil(state.total / 10000))) --TODO replace this with abbreviatelargenumbers from WA
        return state
    end

    return 0, func, events
end

local function processItemImport(state)
    if not state.itemId then return end
    local events = { "BAG_UPDATE", "ITEM_DATA_LOAD_RESULT" } --yuh, should be good
    state.total = state.total or 1
    C_Item.RequestLoadItemDataByID(state.itemId)
    local function func(event, ...)
        if event == "ITEM_DATA_LOAD_RESULT" then
            local itemId, success = ...
            if not success then return end
            if itemId ~= state.itemId then return end
        end
        state.icon = state.icon or C_Item.GetItemIconByID(state.itemId)
        state.value = C_Item.GetItemCount(state.itemId)
        state.text = state.text or C_Item.GetItemNameByID(state.itemId)
        if not state.text then
            C_Item.RequestLoadItemDataByID(state.itemId)
        end
        ---TODO add support for bank/ warbank
        state.progressText = string.format("%s/%s", AbbreviateNumbers(state.value), AbbreviateNumbers(state.total))
        return state
    end

    return 0, func, events
end

local function processItemlevelImport(state)
    ---both single slot and total
    local events = { "PLAYER_AVG_ITEM_LEVEL_UPDATE" }
    state.total = state.total or 999

    local func

    if state.useItemslot and state.itemslot then
        local itemlocation = ItemLocation:CreateFromEquipmentSlot(state.itemslot)
        local slotName = module.GetItemSlotName(state.itemslot)
        state.text = state.text or slotName and slotName .. " Item Level"

        function func(event, ...)
            if not itemlocation or not itemlocation:IsValid() then
                return
            end
            state.icon = state.icon or C_Item.GetItemIcon(itemlocation)
            state.value = C_Item.GetCurrentItemLevel(itemlocation)
            state.progressText = string.format("%s/%s", AbbreviateNumbers(state.value),
                AbbreviateNumbers(state.total))
            return state
        end
    else
        state.icon = state.icon or 1561252
        state.text = state.text or "Item Level"
        function func(event, ...)
            local avg, equipped, pvp = GetAverageItemLevel()
            if not equipped then return end
            state.value = equipped
            state.progressText = string.format("%s/%s", AbbreviateNumbers(state.value),
                AbbreviateNumbers(state.total))
            return state
        end
    end

    return 0, func, events
end

local function GetItemSplit(itemLink)
    local itemString = string.match(itemLink, "item:([%-?%d:]+)")
    local itemSplit = {}

    -- Split data into a table
    for _, v in ipairs({ strsplit(":", itemString) }) do
        if v == "" then
            itemSplit[#itemSplit + 1] = 0
        else
            itemSplit[#itemSplit + 1] = tonumber(v)
        end
    end

    return itemSplit
end

local function processBonusIdImport(state)
    if not state.itemslot then return end
    if not state.bonusType then return end
    local events = { "PLAYER_EQUIPMENT_CHANGED" }
    local itemlocation = ItemLocation:CreateFromEquipmentSlot(state.itemslot)
    local textString = module.GetItemSlotName(state.itemslot)
    local offset
    state.total = state.total or 1


    if state.bonusType == "gem" then
        textString = textString .. " Gem"
        offset = {
            module.Constants.gemSlot1ID,
            module.Constants.gemSlot2ID,
            module.Constants.gemSlot3ID,
            module.Constants.gemSlot4ID,
        }
    elseif state.bonusType == "enchant" then
        state.total = 1
        textString = textString .. " Enchant"
        offset = { module.Constants.enchantSlotID }
    end
    state.text = state.text or textString
    if not offset then return end


    local function func(event, ...)
        if event == "PLAYER_EQUIPMENT_CHANGED" then
            local equipmentSlot, hasCurrent = ...
            if equipmentSlot and equipmentSlot ~= state.itemslot then
                return
            end
        end
        local found = 0
        if itemlocation:IsValid() then
            state.icon = state.icon or C_Item.GetItemIcon(itemlocation)
            local itemLink = C_Item.GetItemLink(itemlocation)
            local split = GetItemSplit(itemLink)

            for i, id in ipairs(offset) do
                local bonusId = split[id]
                if bonusId > 0 and (not state.ids or #state.ids == 0 or tContains(state.ids, bonusId)) then
                    found = found + 1
                end
            end
        end
        state.value = found

        state.progressText = string.format("%s/%s", AbbreviateNumbers(state.value), AbbreviateNumbers(state.total))

        return state
    end


    return 0, func, events

    --enchants n stuff
end

local function processLockoutImport(state)
    if not state.instanceId then return end

    local instanceInfo = module:GetInstanceInfoByInstanceId(state.instanceId)
    if instanceInfo then
        local text = instanceInfo.name
        if state.difficultyId then
            local difficultyName = GetDifficultyInfo(state.difficultyId) or ""
            text = text .. " " .. difficultyName
        end
        state.text = state.text or text
        state.value = 0
        state.oldTotal = state.total
        state.total = minWithNilCheck(state.oldTotal, instanceInfo.bossCount)
        state.icon = state.icon or instanceInfo.icon
    end


    local events = { "UPDATE_INSTANCE_INFO" }
    local function func(event, ...)
        local kills = {}
        local bosses = 0
        for index = 1, GetNumSavedInstances() do
            local name, lockoutId, reset, difficultyId, locked, extended, instanceIDMostSig, isRaid, maxPlayers, difficultyName, numEncounters, encounterProgress, extendDisabled, instanceId =
                GetSavedInstanceInfo(index)
            if instanceId == state.instanceId and (locked or extended) then
                if not state.difficultyId or difficultyId == state.difficultyId then
                    for encounterIndex = 1, numEncounters do
                        local bossName, fileDataID, isKilled, unknown4 = GetSavedInstanceEncounterInfo(index,
                            encounterIndex)
                        if isKilled then
                            kills[bossName] = true
                        end
                    end

                    bosses = numEncounters > bosses and numEncounters or bosses
                    if not state.text then
                        local str = name
                        if state.difficultyId then
                            str = str .. " " .. difficultyName
                            state.text = str
                        end
                    end
                end
            end
        end
        if bosses > 0 then
            state.total = minWithNilCheck(state.oldTotal, bosses)
        end

        local i = 0
        for bossName, isKilled in pairs(kills) do
            i = i + 1
        end

        state.value = i
        state.progressText = string.format("%s/%s", AbbreviateNumbers(state.value), AbbreviateNumbers(state.total))
        return state
    end
    return 0, func, events
end



---TODO request if info isn't available
local function processEventImport(state)
    if not state.eventId then return end

    local eventInfo = C_AreaPoiInfo.GetAreaPOIInfo(nil, state.eventId)

    if not eventInfo then return end
    state.setText = state.text
    state.text = state.setText or eventInfo.name
    state.icon = state.icon or eventInfo.atlasName
    state.total = 1 --don't think this can be anything else?¨
    state.timer = {}
    local events = {
        "EVENT_SCHEDULER_UPDATE",
        "AREA_POIS_UPDATED", -- idk if we need this
        "CVAR_UPDATE",       --also questionable
    }
    local validIds = module.GetRelatedEventIds(state.eventId)
    local function func(event, ...)
        if event == "CVAR_UPDATE" then
            local cvar, timestamp = ...
            if cvar ~= "eventSchedulerLastUpdate" then return end
        end
        local eventList = C_EventScheduler.GetScheduledEvents()
        if not eventList then return end
        local firstEventStart
        for i, eventStatus in ipairs(eventList) do
            if validIds[eventStatus.areaPoiID] then
                if not firstEventStart then
                    --surely this only shows up if there's time left :)
                    state.value = eventStatus.rewardsClaimed and 1 or 0
                    state.timer.duration = module.GetEventDurationOverride(eventStatus.areaPoiID) or
                        eventStatus.duration

                    state.text = state.setText or C_AreaPoiInfo.GetAreaPOIInfo(nil, eventStatus.areaPoiID).name
                    firstEventStart = eventStatus
                        .startTime --TODO offset with with region thingy so that it even outs in backend
                elseif firstEventStart ~= eventStatus.startTime then
                    state.timer.interval = (eventStatus.startTime - firstEventStart)

                    return state
                end
            end
        end

        if firstEventStart then
            state.timer.offset = GetServerTime() - C_DateAndTime.GetWeeklyResetStartTime() + firstEventStart
            return state
        end
    end



    return 0, func, events
end

local function processLevelImport(state)
    local events = { "PLAYER_LEVEL_UP" }
    state.oldTotal = state.total
    state.icon = state.icon or 397907
    state.text = state.text or "Level"
    local function func()
        state.value = UnitLevel("player")
        state.total = minWithNilCheck(state.oldTotal, GetMaxPlayerLevel())
        state.progressText = string.format("%s/%s", AbbreviateNumbers(state.value), AbbreviateNumbers(state.total))
        return state
    end
    return 0, func, events
end

---@param state table
---@return string|nil identifier
function module:AddMod(state)
    if state.disabled then return nil end
    if module:GetUserMod(state.identifier) and state.isDefaultMod then return nil end
    state.identifier = state.identifier or module.GenerateUid()

    state = module:DeepCopy(state)
    if not module:ShouldModBeLoaded(state) then return end
    local identifier = state.identifier
    local type = state.type
    local status, func, events;
    if type == "quest" then
        status, func, events = processQuestImport(state)
    elseif type == "custom" then
        status, func, events = processCustomImport(state)
    elseif type == "achievement" then
        status, func, events = processAchievementImport(state)
    elseif type == "currency" then
        status, func, events = processCurrencyImport(state)
    elseif type == "title" then
        status, func, events = processTitleImport(state)
    elseif type == "vault" then
        status, func, events = processVaultImport(state)
    elseif type == "reputation" then
        status, func, events = processReputationImport(state)
    elseif type == "money" then
        status, func, events = processMoneyImport(state)
    elseif type == "item" then
        status, func, events = processItemImport(state)
    elseif type == "itemlevel" then
        status, func, events = processItemlevelImport(state)
    elseif type == "bonusID" then
        status, func, events = processBonusIdImport(state)
    elseif type == "lockout" then
        status, func, events = processLockoutImport(state)
    elseif type == "event" then
        status, func, events = processEventImport(state)
    elseif type == "level" then
        status, func, events = processLevelImport(state)
    end
    if status == 0 then --change this to something that makes more sense. also, should probably provide a error message
        module.eventHandler:RegisterFunction(state.identifier, func, events)
        return identifier
    end
end

---@param identifier string
function module:RemoveMod(identifier)
    if not identifier then return end
    module.eventHandler:UnregisterFunction(identifier)
    module:ProcessValueUpdate(
        {
            identifier = identifier,
            disabled = true,
        }
    )
    --tell display to remove
end

local function applyAdminOverridesOnState(oldState)
    local state = module:DeepCopy(oldState)
    local overrides = module:GetAdminOverridesForMod(state.identifier)
    if overrides then
        for i, override in ipairs(overrides) do
            module:SetField(state, override.fieldTable, override.value)
        end
    end
    return state
end

function module:AddAdminMod(state)
    if not state then return end
    local copiedState = applyAdminOverridesOnState(state)
    --check if these are user disabled, check if user made style overrides and add those to state

    module:AddMod(copiedState)
end

local defaultModsprepared = 0
local function prepareDefaultMods()
    local state = module:GetDefaultMods(defaultModsprepared + 1)
    if not state then
        --we done here, go back
        module:ImportDefaults()
        return
    end

    state.isDefaultMod = true
    state.index = state.index or (defaultModsprepared + 1)
    state.identifier = state.identifier or module:GenerateUidFromStateStructure(state)
    defaultModsprepared = defaultModsprepared + 1

    C_Timer.After(0, function()
        prepareDefaultMods()
    end)
end



function module:GetDefaultMods(index)
    local defaultMods = module.DefaultMods
    if not defaultMods then return {} end

    if not index then
        return defaultMods
    end
    return defaultMods[index]
end

function module:GetDefaultModsWithOverrides()
    local mods = module:GetDefaultMods()
    local ret = {}
    for i, mod in ipairs(mods) do
        ret[i] = applyAdminOverridesOnState(mod)
    end

    table.sort(ret, function(a, b)
        local aIndex = a.index or math.huge
        local bIndex = b.index or math.huge

        return aIndex < bIndex or aIndex == bIndex and a.identifier < b.identifier
    end)
    return ret
end

function module:ImportDefaults()
    if type(module.DefaultMods) == "string" then
        module.DefaultMods = module:Decompress(module.DefaultMods)
        defaultModsprepared = 0
    end
    if module.DefaultMods and defaultModsprepared < #module.DefaultMods then
        prepareDefaultMods()
        return
    end
    local mods = module:GetDefaultMods()


    for i, modTable in ipairs(mods) do
        module:AddAdminMod(modTable)
    end
end

function module:UpdateModState(state)
    if not state then
        return
    end
    if state.disabled then
        module:RemoveMod(state.identifier)
        return
    end
    module:AddMod(state)
end

function module:ReloadModFromStorage(identifier)
    if not identifier then return end
    module:RemoveMod(identifier)
    for i, mods in ipairs({ module:GetUserMods(), module:GetDefaultMods() }) do
        for j, state in ipairs(mods) do
            if state.identifier == identifier then
                if i == 1 then
                    module:AddMod(state)
                else
                    module:AddAdminMod(state)
                end
            end
        end
    end
end

function module:ReloadAllModsFromStorage()
    for i, mods in ipairs({ module:GetUserMods(), module:GetDefaultMods() }) do
        for j, state in ipairs(mods) do
            module:RemoveMod(state.identifier)
            if i == 1 then
                module:AddMod(state)
            else
                module:AddAdminMod(state)
            end
        end
    end
end

function module:ImportUserMods()
    local mods = module:GetUserMods()
    for i, mod in ipairs(mods) do
        module:AddMod(mod)
    end
end
