if string.match(debug.getinfo(1).source, '=%[SMODS %w+ ".+"]') then
    error("Please update your steamodded thanks")
end

local util = require("debugplus.util")
local loggerSucc, logger = pcall(require, "debugplus.logger")
local global = {}

if not loggerSucc then -- To handle older lovely versions, where I can't properly load my deps.
    return {
        getValue = function() -- Can't error myself because it's not propagated, so I error in the first function that is called.
            error("DebugPlus couldn't load a required component. Please make sure your lovely is up to date.\nYou can grab the latest lovely at: https://github.com/ethangreen-dev/lovely-injector/releases\n\n".. (logger or "No further info"))
        end
    }
end


local configDefinition = {
    debugMode = {
        label = "Debug Mode",
        type = "toggle",
        default = true,
        info = {"Toggles everything in DebugPlus except the console."},
        onUpdate = function(v) _RELEASE_MODE = not v end
    },
    ctrlKeybinds = {
        label = util.ctrlText .. " for Keybinds",
        type = "toggle",
        default = true,
        info = {"Requires you hold ".. util.ctrlText .. " when pressing the built in keybinds."}
    },
    logLevel = {
        label = "Log Level",
        type = "select",
        default = "INFO",
        values = {"ERROR", "WARN", "INFO", "DEBUG"},
        info = {
			"Only shows you logs of a certain level. This setting ignore command logs.",
			"Will show all logs for the selected level and higher."
		},
        -- Most of the time I wouldn't define onUpdate here for something in another module, 
        -- but I need to avoid circular dependencies and want the logger here.
        onUpdate = function(v) 
            for k, v in pairs(logger.levelMeta) do
                v.shouldShow = false
            end
            
            logger.levelMeta.ERROR.shouldShow = true
            if v == "ERROR" then return logger.handleLogsChange() end
            logger.levelMeta.WARN.shouldShow = true
            if v == "WARN" then return logger.handleLogsChange() end
            logger.levelMeta.INFO.shouldShow = true
            if v == "INFO" then return logger.handleLogsChange() end
            logger.levelMeta.DEBUG.shouldShow = true
            logger.handleLogsChange() 
        end
    },
    showNewLogs = {
        label = "Show New Logs",
        type = "toggle",
        default = true,
        info = {
            "Show a message when something is logged. Can also press shift + / to temporarily toggle."
        } 
    },
    onlyCommands = {
        label = "Only Show Commands",
        type = "toggle",
        default = false,
        info = {"Do not show any logs, other than ones from commands or from you pressing debug keybinds."}
    },
    showHUD = {
        label = "Show Debug HUD",
        type = "toggle",
        default = true,
        info = {"Shows some debug information on the top left of the screen."}
    },
    -- Hidden config for rn. Even though they are hidden at the default logging level, 
    -- they are so frequent is starts clearing normal logs if left run for a bit.
    -- This option was added so if someone does want them, they can be reenabled. 
    enableLongDT = { 
        label = "Enable Long DT Messages",
        type = "toggle",
        default = false,
        info = {}
    },
	processTables = {
		label = "Automatically Expand Printed Tables",
		type = "toggle",
		default = true,
		info = {"When a table is printed, expand it's contents (like in the eval command) instead of just strigifying it."}
	},
	stringifyPrint = {
		label = "Process Arguments Before Logging",
		type = "toggle",
		default = true,
		info = {
			"When this is enabled and something is printed to the lovely console/log DebugPlus will handle the processing of the args before logging them.",
			"This allows the 'Automatically Expand Printed Tables' option to also show up in those logs."
		}
	},
	hyjackErrorHandler = {
		label = "Console In Crash Handler",
		type = "toggle",
		default = true,
		info = {
			"When this is toggled, DebugPlus's console will be accessible in the error handler.",
			"Requires Steamodded (or another tool to replace the error handler) to function",
			"Requires a restart for the toggle to take effect"
		}
	}
}

global.configDefinition = configDefinition

local configPages = { -- TODO: implement paging, maybe only when I need to
    {
		name = "Console",
        "showNewLogs",
        "onlyCommands",
        "logLevel",
		"processTables",
		"stringifyPrint",
		"hyjackErrorHandler",
    },
	{
		name = "Misc",
        "debugMode",
        "ctrlKeybinds",
        "showHUD",
	}
}

for k,v in pairs(configDefinition) do
    v.key = k
end

local testValues = {}
local configTypes
local configMemory

local function parseConfigValue(val)
    val = util.trim(val)
    if val == "true" then
        return true
    end
    if val == "false" then
        return false
    end
    if val:sub(1, 1) == '"' and val:sub(#val) == '"' then
        return val:sub(2, #val - 1):gsub("\\(.?)", {
            ["\\"] = "\\",
            n = "\n",
            r = "\r"
        })
    end
    if tonumber(val) then
        return tonumber(val)
    end
    return {
        type = "raw",
        val = val
    }
end

local function stringifyConfigValue(val)
    if val == true then
        return "true"
    end
    if val == false then
        return "false"
    end
    if type(val) == "string" then
        return '"' .. val:gsub("\\", "\\\\"):gsub("\n", "\\n"):gsub("\r", "\\r") .. '"'
    end
    if type(val) == "number" then
        return string.format("%g", val)
    end
    if val.type == "raw" then
        return val.val
    end
end

local function parseConfigFile(data)
    local t = {}
    for str in string.gmatch(data, "([^\n\r]+)") do
        local name, val = str:match("(%w+)%s*=%s*(.+)")
        if not name then
            logger.errorLog("Failed to parse line:", str)
        else
            t[name] = parseConfigValue(val)
        end
    end
    return t
end

local function stringifyConfigFile(data)
    local str = ""
    for k, v in pairs(data) do
        local val = stringifyConfigValue(v)
        if val then
            str = str .. k .. "=" .. val .. "\n"
        end
    end
    return str
end

local function loadSaveFromFile()
    local content = love.filesystem.read("config/DebugPlus.jkr")
    if not content then
        return {}
    end
    local success, res = pcall(parseConfigFile, content)
    if success and type(res) == "table" then
        return res
    end
    logger.errorLog("Loading save err", res)
    return {}
end


local function generateSaveFileTable()
    if not configMemory then return loadSaveFromFile() end
    local fin = {}
    for k, v in pairs(configMemory) do
        fin[k] = v.store
    end
    return fin
end

local function updateSaveFile()
    local conf = generateSaveFileTable()
    love.filesystem.createDirectory("config")
    local success, res = pcall(stringifyConfigFile, conf)
    if success then
        love.filesystem.write("config/DebugPlus.jkr", res)
    else
        logger.errorLog("Failure saving config", res)
    end
end


function global.setValue(key, value) 
    local def = configDefinition[key]
    if not def then return end
    if configTypes[def.type] and configTypes[def.type].validate then
        if not configTypes[def.type].validate(value, def) then
            logger.errorLog('Value for saving key ' .. key .. ' failed to validate')
            return
        end
    end
    local mem = configMemory[key]
    mem.store = value
    mem.value = value
    if def.onUpdate then
        def.onUpdate(value)
    end
    updateSaveFile()
end

function global.clearValue(key)
    local def = configDefinition[key]
    if not def then return end
    local mem = configMemory[key]
    mem.store = nil
    mem.value = def.default
    if def.onUpdate then
        def.onUpdate(value)
    end
    updateSaveFile()
end

function global.getValue(key)
    local def = configDefinition[key]
    if not def then return end
    return configMemory[key].value
end

configTypes = {
    toggle = {
        validate = function(data, def)
            return type(data) == "boolean"
        end,
        render = function(def)
            return create_toggle({
                label = def.label,
                ref_table = configMemory[def.key],
                ref_value = "value",
                callback = function(v) global.setValue(def.key, v) end,
                info = def.info
            })
        end
    },
    select = {
        validate = function(data, def)
            return util.hasValue(def.values, data)
        end,
        render = function(def)
            local curr = util.hasValue(def.values, configMemory[def.key].value) or 1
            return create_option_cycle({
                options = def.values,
                current_option = curr,
                scale = 0.8,
                opt_callback = "DP_conf_select_callback",
                label = def.label,
                info = def.info,
                dp_key = def.key
            })
        end
    },
}

local function getDefaultsObject()
    local config = {}
    for k, v in pairs(configDefinition) do
        config[k] = v.default
    end
    return config
end


local function generateMemory()
    local defaults = getDefaultsObject()
    local loaded = loadSaveFromFile()

    configMemory = {}

    for k, v in pairs(loaded) do
        local store = v
        local value = nil
        local def = configDefinition[k]
        if def then
            if configTypes[def.type] and configTypes[def.type].validate then
                if configTypes[def.type].validate(v, def) then
                    value = v
                else
                    logger.errorLog('Value for saved key ' .. k .. ' failed to validate')
                    value = def.default
                end
            else
                value = v
            end
            if def.onUpdate then
                def.onUpdate(value)
            end
        end
        configMemory[k] = {
            store = store,
            value = value,
        }
    end

    for k, v in pairs(defaults) do
        if configMemory[k] then
            goto continue
        end
        configMemory[k] = {
            store = nil,
            value = v,
        }
        ::continue::
    end
end

function global.generateConfigTab(arg)
	local index = arg.index or 1
    function G.FUNCS.DP_conf_select_callback(e)
        global.setValue(e.cycle_config.dp_key, e.to_val)
    end
    local nodes = {}
    for k,v in ipairs(configPages[index]) do
        local def = configDefinition[v]
        table.insert(nodes, configTypes[def.type].render(def))
    end
    return {
        -- ROOT NODE
        n = G.UIT.ROOT,
        config = {r = 0.1, minw = 7, minh = 5, align = "cm", padding = arg.source == "lovely" and .05 or .5, colour = arg.source == "lovely" and G.C.CLEAR or G.C.BLACK},
        nodes = {
            {
                -- COLUMN NODE TO ALIGN EVERYTHING INSIDE VERTICALLY
                n = G.UIT.C,
                config = {align = "tm", padding = 0.1},
                nodes = nodes
            }
        }
    }
end

function global.generateConfigTabs(source)
	local tab = {}
	for i,v in ipairs(configPages) do
		table.insert(tab, {
			label = v.name,
			tab_definition_function = global.generateConfigTab,
			tab_definition_function_args = {source = source, index = i }
		})
	end
	return tab
end

function global.fakeConfigTab()
	local tabs = global.generateConfigTabs("lovely")
	tabs[1].chosen = true
	G.FUNCS.overlay_menu({
		definition = create_UIBox_generic_options({
			back_func = "settings",
			contents = {create_tabs({
				snap_to_nav = true,
				-- colour = {.65, .36, 1, 1},
				tabs = tabs,
				tab_h = 7.05,
				tab_alignment = 'tm',
			})}
		})
	})
	return {}
end

generateMemory()

-- if debug.getinfo(1).source:match("@.*") then -- For when running under watch
--     logger.log("DebugPlus config in watch")
--     return global.generateConfigTab() -- For watch config_tab
-- end

return global
