--- Defold Text Proto format encoder/decoder to lua table

local config = require("druid.editor_scripts.defold_parser.system.config")
local system = require("druid.editor_scripts.defold_parser.system.system")

local M = {}


--- Decode a Defold object from a string
---@param text string
---@return table
function M.decode_defold_object(text)
	-- Create a root object, which will contain all the file data
	local root = {}
	-- Stack to keep track of nested objects. Always insert data to the last object in the stack
	local stack = { root }

	-- For each line in the text, we go through the following steps:
	for raw_line in text:gmatch("[^\r\n]+") do
		system.parse_line(raw_line, stack)
	end

	return root
end


-- Encoding Functions
function M.encode_defold_object(obj, spaces, data_level, extension)
	spaces = spaces or 0
	data_level = data_level or 0
	local key_order = extension and config.KEY_ORDER[extension] or {}

	local result = ''
	local tabString = string.rep(' ', spaces)

	local keys = {}
	for key in pairs(obj) do
		table.insert(keys, key)
	end

	table.sort(keys, function(a, b)
		local index_a = system.contains(key_order, a) or 0
		local index_b = system.contains(key_order, b) or 0
		return index_a < index_b
	end)

	-- Iterate over the sorted keys
	for _, key in ipairs(keys) do
		local value = obj[key]
		local value_type = type(value)

		-- Handle different types of values
		if value_type == "table" then
			-- Check if it's an array-like table
			if #value > 0 then
				-- It's an array-like table, process each element
				for _, array_item in ipairs(value) do
					local item_type = type(array_item)

					if key == "data" and item_type == "table" then
						-- Handle nested data
						local encodedChild = M.encode_defold_object(array_item, spaces + 2, data_level + 1, extension)
						result = result .. tabString .. key .. ': "' .. encodedChild .. '"\n'
					elseif item_type == "number" or item_type == "boolean" then
						local is_contains_dot = string.find(key, "%.")
						if item_type == "number" and (system.contains(config.with_dot_params, key) and not is_contains_dot) then
							result = result .. tabString .. key .. ': ' .. string.format("%.1f", array_item) .. '\n'
						else
							result = result .. tabString .. key .. ': ' .. tostring(array_item) .. '\n'
						end
					elseif item_type == "string" then
						-- Handle multiline text
						if key == "text" then
							result = result .. tabString .. key .. ': "' .. array_item:gsub("\n", '\\n"\n' .. tabString .. '"') .. '"\n'
						else
							-- Check if the key should not have quotes
							local is_uppercase = (array_item == string.upper(array_item))
							local is_boolean = (array_item == "true" or array_item == "false")
							if (is_uppercase and not config.string_keys[key]) or is_boolean then
								result = result .. tabString .. key .. ': ' .. array_item .. '\n'
							else
								result = result .. tabString .. key .. ': "' .. array_item .. '"\n'
							end
						end
					elseif item_type == "table" then
						result = result .. tabString .. key .. ' {\n' .. M.encode_defold_object(array_item, spaces + 2, data_level, extension) .. tabString .. '}\n'
					end
				end
			else
				-- It's a dictionary-like table
				result = result .. tabString .. key .. ' {\n' .. M.encode_defold_object(value, spaces + 2, data_level, extension) .. tabString .. '}\n'
			end
		else
			-- Handle scalar values (string, number, boolean)
			if value_type == "number" or value_type == "boolean" then
				local is_contains_dot = string.find(key, "%.")
				if value_type == "number" and (system.contains(config.with_dot_params, key) and not is_contains_dot) then
					result = result .. tabString .. key .. ': ' .. string.format("%.1f", value) .. '\n'
				else
					result = result .. tabString .. key .. ': ' .. tostring(value) .. '\n'
				end
			elseif value_type == "string" then
				-- Handle multiline text
				if key == "text" then
					result = result .. tabString .. key .. ': "' .. value:gsub("\n", '\\n"\n' .. tabString .. '"') .. '"\n'
				else
					-- Check if the key should not have quotes
					local is_uppercase = (value == string.upper(value))
					local is_boolean = (value == "true" or value == "false")
					if (is_uppercase and not config.string_keys[key]) or is_boolean then
						result = result .. tabString .. key .. ': ' .. value .. '\n'
					else
						result = result .. tabString .. key .. ': "' .. value .. '"\n'
					end
				end
			end
		end
	end

	return result
end


---Load lua table from file in Defold Text Proto format
---@param file_path string
---@return table|nil, string|nil
function M.load_from_file(file_path)
	local content, reason = system.read_file(file_path)
	if not content then
		return nil, reason
	end

	return M.decode_defold_object(content), nil
end


---Write lua table to file in Defold Text Proto format
---The path file extension will be used to determine the Defold format (*.atlas, *.gui, *.font, etc)
---@param file_path string
---@param lua_table table
---@return boolean, string|nil
function M.save_to_file(file_path, lua_table)
	-- Get extension without the dot
	local defold_format_name = file_path:match("^.+%.(.+)$")
	print("File extension:", defold_format_name)

	local encoded_object = M.encode_defold_object(lua_table, nil, nil, defold_format_name)

	return system.write_file(file_path, encoded_object)
end


return M
