local ADDON_NAME, ns = ...

ns = ns or {}

local LoadoutData = {}
ns.LoadoutData = LoadoutData

local loadoutCache = {}
local currentSpecID = nil
local suppressEvents = false
local hasCleanedUp = false

local function getSpecID()
    local specIndex = GetSpecialization and GetSpecialization()
    if specIndex then
        return GetSpecializationInfo(specIndex)
    end
    return nil
end

local function getMeta(configID)
    if not ns.db or not ns.db.global or not ns.db.global.loadoutMeta then
        return nil
    end
    return ns.db.global.loadoutMeta[configID]
end

local function setMeta(configID, key, value)
    if not ns.db or not ns.db.global then
        return
    end
    ns.db.global.loadoutMeta = ns.db.global.loadoutMeta or {}
    ns.db.global.loadoutMeta[configID] = ns.db.global.loadoutMeta[configID] or {}
    ns.db.global.loadoutMeta[configID][key] = value
end

local function removeMeta(configID)
    if not ns.db or not ns.db.global or not ns.db.global.loadoutMeta then
        return
    end
    ns.db.global.loadoutMeta[configID] = nil
end

function LoadoutData:ScanLoadouts()
    loadoutCache = {}
    currentSpecID = getSpecID()
    if not currentSpecID then
        return loadoutCache
    end

    local configIDs = C_ClassTalents.GetConfigIDsBySpecID(currentSpecID) or {}
    local activeConfigID = C_ClassTalents.GetActiveConfigID()
    local lastSelected = C_ClassTalents.GetLastSelectedSavedConfigID(currentSpecID)

    for _, configID in ipairs(configIDs) do
        local configInfo = C_Traits.GetConfigInfo(configID)
        if configInfo and configInfo.type == 1 then
            local meta = getMeta(configID)
            local entry = {
                configID = configID,
                name = configInfo.name or ("Loadout " .. configID),
                usesSharedActionBars = configInfo.usesSharedActionBars,
                isActive = (configID == lastSelected),
                createdAt = meta and meta.createdAt or nil,
                notes = meta and meta.notes or "",
            }
            loadoutCache[#loadoutCache + 1] = entry
        end
    end

    if not hasCleanedUp and currentSpecID and #loadoutCache > 0 then
        self:CleanupOrphanedMeta()
        hasCleanedUp = true
    end

    return loadoutCache
end

function LoadoutData:GetCachedLoadouts()
    return loadoutCache
end

function LoadoutData:GetCurrentSpecID()
    return currentSpecID
end

function LoadoutData:GetSortedLoadouts(sortMode)
    local list = {}
    for i, entry in ipairs(loadoutCache) do
        list[i] = entry
    end

    sortMode = sortMode or (ns.db and ns.db.sortMode) or "name_asc"

    if sortMode == "name_asc" then
        table.sort(list, function(a, b)
            return (a.name or ""):lower() < (b.name or ""):lower()
        end)
    elseif sortMode == "name_desc" then
        table.sort(list, function(a, b)
            return (a.name or ""):lower() > (b.name or ""):lower()
        end)
    elseif sortMode == "date_asc" then
        table.sort(list, function(a, b)
            local aHasDate = (a.createdAt and a.createdAt > 0)
            local bHasDate = (b.createdAt and b.createdAt > 0)
            if aHasDate and not bHasDate then return true end
            if not aHasDate and bHasDate then return false end
            if not aHasDate and not bHasDate then
                return (a.name or ""):lower() < (b.name or ""):lower()
            end
            if a.createdAt == b.createdAt then
                return (a.name or ""):lower() < (b.name or ""):lower()
            end
            return a.createdAt < b.createdAt
        end)
    elseif sortMode == "date_desc" then
        table.sort(list, function(a, b)
            local aHasDate = (a.createdAt and a.createdAt > 0)
            local bHasDate = (b.createdAt and b.createdAt > 0)
            if aHasDate and not bHasDate then return true end
            if not aHasDate and bHasDate then return false end
            if not aHasDate and not bHasDate then
                return (a.name or ""):lower() < (b.name or ""):lower()
            end
            if a.createdAt == b.createdAt then
                return (a.name or ""):lower() < (b.name or ""):lower()
            end
            return a.createdAt > b.createdAt
        end)
    end

    return list
end

function LoadoutData:DeleteLoadout(configID)
    if not configID then
        return false, "No config ID."
    end
    local success = C_ClassTalents.DeleteConfig(configID)
    if success then
        removeMeta(configID)
    end
    return success
end

function LoadoutData:BulkDelete(configIDs, onProgress, onComplete)
    if not configIDs or #configIDs == 0 then
        if onComplete then
            onComplete(0, 0)
        end
        return
    end

    suppressEvents = true
    local deleted = 0
    local failed = 0
    local index = 0
    local total = #configIDs
    local waitingForEvent = false
    local timeoutTimer = nil

    local eventFrame = CreateFrame("Frame")

    local function finish()
        eventFrame:UnregisterAllEvents()
        eventFrame:SetScript("OnEvent", nil)
        suppressEvents = false
        LoadoutData:ScanLoadouts()
        if ns.RefreshLoadoutPanel then
            ns.RefreshLoadoutPanel()
        end
        if onComplete then
            onComplete(deleted, failed)
        end
    end

    local function deleteNext()
        index = index + 1
        if index > total then
            finish()
            return
        end

        if onProgress then
            onProgress(index, total)
        end

        local configID = configIDs[index]
        local success = C_ClassTalents.DeleteConfig(configID)

        if success then
            removeMeta(configID)
            deleted = deleted + 1
            waitingForEvent = true
            timeoutTimer = C_Timer.NewTimer(2.0, function()
                waitingForEvent = false
                deleteNext()
            end)
        else
            failed = failed + 1
            C_Timer.After(0.05, deleteNext)
        end
    end

    eventFrame:RegisterEvent("TRAIT_CONFIG_DELETED")
    eventFrame:SetScript("OnEvent", function(_, event)
        if not waitingForEvent then
            return
        end
        if event == "TRAIT_CONFIG_DELETED" then
            waitingForEvent = false
            if timeoutTimer then
                timeoutTimer:Cancel()
                timeoutTimer = nil
            end
            C_Timer.After(0.3, deleteNext)
        end
    end)

    deleteNext()
end

function LoadoutData:RenameLoadout(configID, newName)
    if not configID or not newName or newName == "" then
        return false
    end
    return C_ClassTalents.RenameConfig(configID, newName)
end

function LoadoutData:LoadLoadout(configID)
    if not configID then
        return false
    end
    if InCombatLockdown() then
        ns:Print("Cannot switch loadouts during combat.")
        return false
    end
    local configInfo = C_Traits.GetConfigInfo(configID)
    local loadoutName = configInfo and configInfo.name or "loadout"
    local result = C_ClassTalents.LoadConfig(configID, true)
    if result == 2 then
        C_ClassTalents.UpdateLastSelectedSavedConfigID(currentSpecID, configID)
    end
    if result ~= 0 then
        ns:Print("|cff4ECC4E" .. loadoutName .. "|r applied successfully.")
        return true
    end
    ns:Print("Failed to apply |cffFFFF00" .. loadoutName .. "|r.")
    return false
end

function LoadoutData:GetExportString(configID)
    if not configID then
        return nil
    end
    return C_Traits.GenerateImportString(configID)
end

function LoadoutData:SetNote(configID, note)
    if not configID then
        return
    end
    setMeta(configID, "notes", note or "")
end

function LoadoutData:GetNote(configID)
    local meta = getMeta(configID)
    return meta and meta.notes or ""
end

function LoadoutData:RecordCreation(configID)
    if not configID then
        return
    end
    local timestamp = GetServerTime and GetServerTime() or time()
    setMeta(configID, "createdAt", timestamp)
end

function LoadoutData:CleanupOrphanedMeta()
    if not ns.db or not ns.db.global or not ns.db.global.loadoutMeta then
        return
    end

    local validIDs = {}
    local specID = getSpecID()
    if specID then
        local configIDs = C_ClassTalents.GetConfigIDsBySpecID(specID) or {}
        for _, id in ipairs(configIDs) do
            validIDs[id] = true
        end
    end

    for configID in pairs(ns.db.global.loadoutMeta) do
        if not validIDs[configID] then
            ns.db.global.loadoutMeta[configID] = nil
        end
    end
end

function LoadoutData:SaveCurrentLoadout(name)
    if not name or name == "" then
        return false, "Name cannot be empty."
    end
    if InCombatLockdown() then
        return false, "Cannot save during combat."
    end
    local success = C_ClassTalents.RequestNewConfig(name)
    if success then
        return true
    end
    return false, "Failed to create loadout. The talent frame may need to be open."
end

function LoadoutData:ImportLoadout(importString)
    if not importString or importString == "" then
        return false, "No import string provided."
    end
    if InCombatLockdown() then
        return false, "Cannot import during combat."
    end

    local talentsTab = ClassTalentFrame and ClassTalentFrame.TalentsTab
        or (PlayerSpellsFrame and PlayerSpellsFrame.TalentsFrame)

    if not talentsTab then
        if not C_AddOns.IsAddOnLoaded("Blizzard_PlayerSpells") then
            C_AddOns.LoadAddOn("Blizzard_PlayerSpells")
        elseif not C_AddOns.IsAddOnLoaded("Blizzard_ClassTalentUI") then
            C_AddOns.LoadAddOn("Blizzard_ClassTalentUI")
        end
        talentsTab = ClassTalentFrame and ClassTalentFrame.TalentsTab
            or (PlayerSpellsFrame and PlayerSpellsFrame.TalentsFrame)
    end

    if not talentsTab then
        return false, "Please open the Talents window first (press N)."
    end

    if talentsTab.ImportLoadout then
        talentsTab:ImportLoadout(importString)
        return true
    end

    local importDialog = ClassTalentLoadoutImportDialog
    if importDialog and importDialog.ShowDialog then
        importDialog:ShowDialog()
        local eb = importDialog.ImportControl
            and importDialog.ImportControl.InputContainer
            and importDialog.ImportControl.InputContainer.EditBox
        if eb then
            eb:SetText(importString)
        end
        return true
    end

    return false, "Import dialog not available. Open the Talents window and use Blizzard's import."
end

local function refreshUI()
    if suppressEvents then
        return
    end
    LoadoutData:ScanLoadouts()
    if ns.RefreshLoadoutPanel then
        ns.RefreshLoadoutPanel()
    end
end

local function onTraitConfigCreated(configInfo)
    if not configInfo or not configInfo.ID then
        return
    end
    if configInfo.type ~= 1 then
        return
    end
    LoadoutData:RecordCreation(configInfo.ID)
    refreshUI()
end

local function onTraitConfigDeleted(configID)
    if not suppressEvents then
        removeMeta(configID)
    end
    refreshUI()
end

local function onTraitConfigListUpdated()
    refreshUI()
end

local function onSpecChanged()
    refreshUI()
end

function LoadoutData:Initialize()
    self:ScanLoadouts()

    local eventFrame = CreateFrame("Frame")
    eventFrame:RegisterEvent("TRAIT_CONFIG_CREATED")
    eventFrame:RegisterEvent("TRAIT_CONFIG_DELETED")
    eventFrame:RegisterEvent("TRAIT_CONFIG_LIST_UPDATED")
    eventFrame:RegisterEvent("TRAIT_CONFIG_UPDATED")
    eventFrame:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
    eventFrame:RegisterEvent("ACTIVE_COMBAT_CONFIG_CHANGED")

    eventFrame:SetScript("OnEvent", function(_, event, ...)
        if event == "TRAIT_CONFIG_CREATED" then
            onTraitConfigCreated(...)
        elseif event == "TRAIT_CONFIG_DELETED" then
            onTraitConfigDeleted(...)
        elseif event == "TRAIT_CONFIG_LIST_UPDATED" then
            onTraitConfigListUpdated()
        elseif event == "TRAIT_CONFIG_UPDATED" then
            refreshUI()
        elseif event == "PLAYER_SPECIALIZATION_CHANGED" then
            onSpecChanged()
        elseif event == "ACTIVE_COMBAT_CONFIG_CHANGED" then
            refreshUI()
        end
    end)
end
