local TXUI, F, E, I, V, P, G = unpack(select(2, ...))

local _G = _G
local EnableAddOn = EnableAddOn
local format = string.format
local gsub = string.gsub
local ipairs = ipairs
local IsAddOnLoaded = IsAddOnLoaded
local LoadAddOn = LoadAddOn
local match = string.match
local next = next
local pairs = pairs
local ReloadUI = ReloadUI
local sort = table.sort
local strrep = strrep
local strsplit = strsplit
local tconcat = table.concat
local tinsert = table.insert
local tostring = tostring
local tremove = table.remove
local type = type

local function createExportFrame(profileExport, needsReload, btnFunc)
    if not IsAddOnLoaded("ElvUI_OptionsUI") then
        EnableAddOn("ElvUI_OptionsUI")
        LoadAddOn("ElvUI_OptionsUI")
    end

    if not E.Libs.AceGUI then E:AddLib("AceGUI", "AceGUI-3.0") end

    local frame = E.Libs.AceGUI:Create("Frame")
    frame:SetTitle("Export")
    frame:EnableResize(false)
    frame:SetWidth(800)
    frame:SetHeight(600)
    frame:SetLayout("flow")
    frame.frame:SetFrameStrata("FULLSCREEN_DIALOG")
    frame.frame:SetScript("OnHide", function()
        if needsReload then ReloadUI() end
    end)

    local box = E.Libs.AceGUI:Create("MultiLineEditBox-ElvUI")
    box:SetNumLines(36)
    box:DisableButton(true)
    box:SetWidth(1000)
    box:SetLabel("Export")
    frame:AddChild(box)

    box.editBox:SetScript("OnCursorChanged", nil)
    box.scrollFrame:UpdateScrollChildRect()
    box:SetText(profileExport)
    box.editBox:HighlightText()
    box:SetFocus()

    frame.box = box

    if btnFunc then
        local importButton = E.Libs.AceGUI:Create("Button-ElvUI") --This version changes text color on SetDisabled
        importButton:SetText("Decode")
        importButton:SetAutoWidth(true)
        importButton:SetCallback("OnClick", btnFunc)
        frame:AddChild(importButton)
    end

    return frame
end

local function removeEmptySubTables(tbl)
    if type(tbl) ~= "table" then return end

    if next(tbl) == nil then
        tbl = nil
        return tbl
    end

    for _, v in pairs(tbl) do
        if type(v) == "table" then
            if next(v) ~= nil then removeEmptySubTables(v) end
        end
    end

    for k, v in pairs(tbl) do
        if type(v) == "table" then
            if next(v) == nil then tbl[k] = nil end
        end
    end
end

local function generatePluginTable(profile, databaseName)
    do
        local sameLine = false
        local lineStructureTable = {}

        local function keySort(a, b)
            local A, B = type(a), type(b)

            if A == B then
                if A == "number" or A == "string" then
                    return a < b
                elseif A == "boolean" then
                    return (a and 1 or 0) > (b and 1 or 0)
                end
            end

            return A < B
        end

        local function buildLineStructure(str)
            for _, v in ipairs(lineStructureTable) do
                if type(v) == "string" then
                    str = str .. "[\"" .. v .. "\"]"
                else
                    str = str .. "[" .. v .. "]"
                end
            end

            return str
        end

        local function buildTableStructure(tbl, ret, db, lineStructureTableCreated)
            local tkeys = {}

            for i in pairs(tbl) do
                tinsert(tkeys, i)
            end

            sort(tkeys, keySort)

            local lineStructure = buildLineStructure(db)
            for _, k in ipairs(tkeys) do
                local v = tbl[k]

                if not sameLine then ret = ret .. lineStructure end

                ret = ret .. "["

                if type(k) == "string" then
                    ret = ret .. "\"" .. k .. "\""
                else
                    ret = ret .. k
                end

                if type(v) == "table" then
                    tinsert(lineStructureTable, k)
                    sameLine = true
                    ret = ret .. "]"

                    local newLine = match(ret, "([^\n]*)$")
                    if not lineStructureTableCreated[newLine] then
                        ret = ret .. " = {}\n"
                        ret = ret .. newLine
                        lineStructureTableCreated[newLine] = true
                    end

                    ret = buildTableStructure(v, ret, db, lineStructureTableCreated)
                else
                    sameLine = false
                    ret = ret .. "] = "

                    if type(v) == "number" then
                        ret = ret .. v .. "\n"
                    elseif type(v) == "string" then
                        ret = ret .. "\"" .. v:gsub("\\", "\\\\"):gsub("\n", "\\n"):gsub("\"", "\\\""):gsub("\124", "\124\124") .. "\"\n"
                    elseif type(v) == "boolean" then
                        if v then
                            ret = ret .. "true\n"
                        else
                            ret = ret .. "false\n"
                        end
                    else
                        ret = ret .. "\"" .. tostring(v) .. "\"\n"
                    end
                end
            end

            tremove(lineStructureTable)

            return ret
        end

        return buildTableStructure(profile, "", databaseName, {})
    end
end

local function exportNames()
    local elvDB = _G.ElvDB
    if not elvDB then return TXUI:DebugPrint("ElvDB could not be found, this is impossible") end

    local exportTable = {}
    local printNames = {}

    for namerealm, _ in pairs(elvDB.profileKeys) do
        local name, realm = strsplit(" - ", namerealm, 2)
        if name and realm then
            local exportName = format("%s-%s", name, E:ShortenRealm(realm))
            exportTable[exportName] = true
            tinsert(printNames, exportName)
        else
            TXUI:DebugPrint("Could not parse name and realm", namerealm)
        end
    end

    local distributor = E:GetModule("Distributor")
    local libCompress = E.Libs.Compress
    local libBase64 = E.Libs.Base64

    local serialData = distributor:Serialize(exportTable)
    local compressedData = libCompress:Compress(serialData)
    local encodedData = libBase64:Encode(compressedData)

    -- Set export to window
    createExportFrame(encodedData)

    -- Print Names
    TXUI:Print("Exported Names include: ", F.String.Good(tconcat(printNames, ", ")))
end

local function generateLuaTable(tbl, level, ret)
    ret = ret or ""
    level = level or 0

    local tkeys = {}

    for i in pairs(tbl) do
        tinsert(tkeys, i)
    end

    sort(tkeys, function(a, b)
        local A, B = type(a), type(b)

        if A == B then
            if A == "string" then
                local _, serverA = strsplit("-", a)
                local _, serverB = strsplit("-", b)
                if serverA and serverB then return serverA < serverB end

                return a < b
            end
        end

        return A < B
    end)

    local lastServer

    for _, i in ipairs(tkeys) do
        local v = tbl[i]
        local isStr = type(i) == "string"

        if isStr then
            local _, serverStr = strsplit("-", i)
            if serverStr then
                if lastServer ~= serverStr then
                    if lastServer ~= nil then ret = ret .. "\n" end
                    ret = ret .. strrep("    ", level) .. "-- " .. serverStr .. "\n"
                end
                lastServer = serverStr
            end
        end

        ret = ret .. strrep("    ", level) .. "["
        if isStr then
            if i == "__FLAVOR__" then
                ret = ret .. "I.Enum.Flavor.RETAIL"
            else
                ret = ret .. "\"" .. i .. "\""
            end
        else
            ret = ret .. i
        end
        ret = ret .. "] = "

        if type(v) == "number" then
            ret = ret .. v .. ",\n"
        elseif type(v) == "string" then
            ret = ret .. "\"" .. v:gsub("\\", "\\\\"):gsub("\n", "\\n"):gsub("\"", "\\\""):gsub("\124", "\124\124") .. "\",\n"
        elseif type(v) == "boolean" then
            if v then
                ret = ret .. "true,\n"
            else
                ret = ret .. "false,\n"
            end
        elseif type(v) == "table" then
            ret = ret .. "{\n"
            ret = generateLuaTable(v, level + 1, ret)
            ret = ret .. strrep("    ", level) .. "},\n"
        else
            ret = ret .. "\"" .. tostring(v) .. "\",\n"
        end
    end

    return ret
end

local function exportImportNames()
    local distributor = E:GetModule("Distributor")
    local libCompress = E.Libs.Compress
    local libBase64 = E.Libs.Base64

    local frame
    frame = createExportFrame("", false, function()
        local dataString = frame.box:GetText()

        local function showError(msg, ...)
            TXUI:Print(msg, ...)
            frame:SetTitle(F.String.Error(msg))
        end

        if not libBase64:IsBase64(dataString) then return showError("Invalid import string provided") end

        local decodedData = libBase64:Decode(dataString)
        local decompressedData, decompressedMessage = libCompress:Decompress(decodedData)
        if not decompressedData then return showError("Error decompressing data", decompressedMessage) end

        local deserializedData = E:SplitString(decompressedData, "^^::")
        deserializedData = format("%s%s", deserializedData, "^^")

        local success, nameData = distributor:Deserialize(deserializedData)

        if not success or not nameData then
            showError("Error deserializing", nameData, decompressedData)
            return
        end

        -- Generate Table
        frame.box:SetText(generateLuaTable {
            ["USERNAME"] = {
                ["__FLAVOR__"] = nameData,
            },
        })
    end)
end

local function exportDBM(profile)
    -- Check for DBM
    if not _G.DBM_AllSavedOptions then
        TXUI:Print("DBM not found or options wasn't opened at least once")
        return
    end

    local profileExport
    local transformProfileName = gsub(profile, "%-", "%%-") -- Excape "-"" inside profile names

    --- DBM_AllSavedOptions
    do
        -- Copy DBM table
        local TXUI_DBM = E:CopyTable({}, _G.DBM_AllSavedOptions)

        -- Remove other profiles
        for k, _ in next, TXUI_DBM do
            if k ~= profile then TXUI_DBM[k] = nil end
        end

        -- Cleanup
        removeEmptySubTables(TXUI_DBM)

        -- Generate export
        profileExport = generatePluginTable(TXUI_DBM, "TXUI_DBM_AllSavedOptions")

        -- Replace hardcoded profile with dynamic one
        profileExport = gsub(profileExport, "\"" .. transformProfileName .. "\"", "profileName")
    end

    -- DBT_AllPersistentOptions
    do
        -- Copy DBM table
        local TXUI_DBM = E:CopyTable({}, _G.DBT_AllPersistentOptions)

        -- Remove other profiles
        for k, _ in next, TXUI_DBM do
            if k ~= profile then TXUI_DBM[k] = nil end
        end

        -- Cleanup
        removeEmptySubTables(TXUI_DBM)

        -- Generate export
        profileExport = profileExport .. "\n" .. generatePluginTable(TXUI_DBM, "TXUI_DBT_AllPersistentOptions")

        -- Replace hardcoded profile with dynamic one
        profileExport = gsub(profileExport, "\"" .. transformProfileName .. "\"", "profileName")
    end

    -- Set export to window
    createExportFrame(profileExport)
end

local function exportBigWigs(profile)
    -- Check for BW
    if not _G.BigWigs or not _G.BigWigs.db then
        TXUI:Print("BW not found or options wasn't opened at least once")
        return
    end

    -- BOOM
    _G.BigWigs.db:RegisterDefaults(nil)

    -- BABY BOOM
    if _G.BigWigs.db.children then
        for _, db in pairs(_G.BigWigs.db.children) do
            db.RegisterDefaults(db, nil)
        end
    end

    -- Copy BigWigs table
    local TXUI_BigWigs3DB = E:CopyTable({}, _G.BigWigs3DB)

    -- Remove unused stuff
    TXUI_BigWigs3DB.profileKeys = nil
    TXUI_BigWigs3DB.profiles = nil
    TXUI_BigWigs3DB.wipe80 = nil
    TXUI_BigWigs3DB.discord = nil
    TXUI_BigWigs3DB.namespaces["LibDualSpec-1.0"] = nil

    -- Cleanup export
    if TXUI_BigWigs3DB.namespaces then
        -- Remove all existing boss modules from the export
        for k, _ in next, TXUI_BigWigs3DB.namespaces do
            if k:find("BigWigs_Bosses_", nil, true) or k == "BigWigs_Plugins_Statistics" then TXUI_BigWigs3DB.namespaces[k] = nil end
        end

        -- Remove other profiles
        for k, _ in next, TXUI_BigWigs3DB.namespaces do
            if TXUI_BigWigs3DB.namespaces[k] and TXUI_BigWigs3DB.namespaces[k]["profiles"] then
                for pk, _ in next, TXUI_BigWigs3DB.namespaces[k]["profiles"] do
                    if pk ~= profile then TXUI_BigWigs3DB.namespaces[k]["profiles"][pk] = nil end
                end
            end
        end

        -- Cleanup namespaces
        removeEmptySubTables(TXUI_BigWigs3DB.namespaces)
    end

    -- Cleanup rest
    removeEmptySubTables(TXUI_BigWigs3DB)

    -- Generate export
    local profileExport = generatePluginTable(TXUI_BigWigs3DB, "TXUI_BigWigs3DB")

    -- Excape "-"" inside profile names
    local transformProfileName = gsub(profile, "%-", "%%-")

    -- Replace hardcoded profile with dynamic one
    profileExport = gsub(profileExport, "\"" .. transformProfileName .. "\"", "profileName")

    -- Set export to window
    createExportFrame(profileExport, true)
end

function TXUI:ExportProfile(addon, profile)
    if addon == "bw" then
        exportBigWigs(profile)
    elseif addon == "dbm" then
        exportDBM(profile)
    elseif addon == "names" then
        exportNames()
    elseif addon == "import_names" then
        exportImportNames()
    end
end
