-- lx-bundle:true

--- Temporary file and filesystem helper operations
-- Keep temp-file session state outside 'core.init'
-- @module core.fileops

local common = require("core.common")
local config = require("core.config")

local fileops = {}

local temp_uid = math.floor((system.get_time() * 1000) % 0xffffffff)
local temp_file_prefix = string.format(".lxtemp%08x", temp_uid)
local temp_file_counter = 0

--- Delete temporary files created for this process session
function fileops.delete_temp_files()
	local entries = system.list_dir(EXE_DIR) or {}

	for _, filename in ipairs(entries) do
		if filename:find(temp_file_prefix, 1, true) == 1 then
			os.remove(EXE_DIR .. PATHSEP .. filename)
		end
	end
end

--- Generate a unique temporary filename with an optional extension
-- @param ext string|nil filename extension
-- @return string path
function fileops.temp_filename(ext)
	temp_file_counter = temp_file_counter + 1

	return EXE_DIR .. PATHSEP .. temp_file_prefix .. string.format("%06x", temp_file_counter) .. (ext or "")
end

--- Check whether a path is a filesystem root
-- @param path string|nil path to check
-- @return boolean is_root
local function is_root_directory(path)
	local normalized_path = (system.absolute_path(path) or path or ""):gsub("\\", "/")

	return normalized_path == "/" or normalized_path:match("^%a:[/\\]?$") ~= nil
end

--- Sort project file entries by filename
-- @param first_entry table file entry
-- @param second_entry table file entry
-- @return boolean ordered
local function compare_file(first_entry, second_entry)
	return first_entry.filename < second_entry.filename
end

--- Build one cached project file entry
-- @param path string file path
-- @param info table filesystem metadata
-- @return table entry
local function make_file_entry(path, info)
	return {
		filename = path,
		modified = info.modified,
		size = info.size,
		type = info.type,
	}
end

--- Collect project files under one path with configured limits
-- @param path string directory path
-- @param files table|nil collected file list
-- @param state table|nil scan state
-- @return table files
function fileops.get_files(path, files, state)
	coroutine.yield()
	files = files or {}
	state = state or { file_count = 0, root_is_root = nil }

	if state.root_is_root == nil then
		state.root_is_root = is_root_directory(path)
	end

	local size_limit = config.file_size_limit * 10e5
	local file_limit = math.max(1, tonumber(config.project_scan_limit_files) or 2000)

	if state.file_count >= file_limit then
		return files
	end

	local all_entries = system.list_dir(path) or {}
	local directories = {}
	local file_entries = {}

	for _, name in ipairs(all_entries) do
		if not common.match_pattern(name, config.ignore_files) then
			local entry_path = (path ~= "." and path .. PATHSEP or "") .. name
			local info = system.get_file_info(entry_path)

			if info and info.size < size_limit then
				local entry = make_file_entry(entry_path, info)
				table.insert(info.type == "dir" and directories or file_entries, entry)
			end
		end
	end

	if not state.root_is_root then
		table.sort(directories, compare_file)

		for _, entry in ipairs(directories) do
			if state.file_count >= file_limit then
				break
			end

			fileops.get_files(entry.filename, files, state)
		end
	end

	table.sort(file_entries, compare_file)

	for _, entry in ipairs(file_entries) do
		if state.file_count >= file_limit then
			break
		end

		table.insert(files, entry)
		state.file_count = state.file_count + 1
	end

	return files
end

return fileops
