-- lx-bundle:true

-- This file is part of Lx, created by Liqube <www.liqube.com>
-- Copyright Liqube Audio. All rights reserved.
-- Lx Interim License. See LICENSE for details.

---@diagnostic disable: param-type-mismatch
-- lx bootstrapper

local function setup_paths()
	PATHSEP = package.config:sub(1, 1)
	EXE_DIR = EXE_DIR or "."
	CORE_DIR = false
	PLUGINS_DIR = false
	FORMATS_DIR = false
	TESTS_DIR = false

	assert(type(EXE_FILE) == "string" and EXE_FILE ~= "", "EXE_FILE must be set")

	DATA_DIR = system.join_path(EXE_DIR, "data", true)
	USER_DIR = system.join_path(EXE_DIR, "user", true)
	HAVE_LOCAL_DATA = system.is_dir(DATA_DIR)

	CACHE_DIR = system.join_path(USER_DIR, "cache", true)
	STYLES_DIR = system.join_path(DATA_DIR, "styles", true)
	USER_SETTINGS_DIR = system.join_path(USER_DIR, "settings", true)
	USER_INIT_FILE = system.join_path(USER_DIR, "init.lua")

	if HAVE_LOCAL_DATA then
		local d = nil

		d = system.join_path(DATA_DIR, "core", true)
		if system.is_file(system.join_path(d, "init.lua")) then
			CORE_DIR = d
		end
		d = system.join_path(EXE_DIR, "cache", true)
		if system.is_dir(d) then
			CACHE_DIR = d
		end
		d = system.join_path(DATA_DIR, "formats", true)
		if system.is_dir(d) then
			FORMATS_DIR = d
		end
		d = system.join_path(DATA_DIR, "plugins", true)
		if system.is_dir(d) then
			PLUGINS_DIR = d
		end
		d = system.join_path(DATA_DIR, "tests", true)
		if system.is_dir(d) then
			TESTS_DIR = d
		end
		-- User and style paths remain stable regardless of bundled or local data mode
	end

	FONTS_DIR = system.join_path(DATA_DIR, "fonts", true)
	OS_FONTS_DIR = system.join_path(
		(os.getenv("SystemRoot") or os.getenv("WINDIR") or "C:/Windows"), "Fonts", true)
end

local function setup_local_override_searcher()
	if not HAVE_LOCAL_DATA then
		return
	end

	local cache = {}
	local function path_exists(path)
		local hit = cache[path]
		if hit ~= nil then
			return hit
		end
		local ok = system.is_file(path)
		cache[path] = ok
		return ok
	end

	local function module_path(module)
		local rel = module:gsub("%.", "/")
		if module == "core" then
			return CORE_DIR and system.join_path(CORE_DIR, "init.lua") or nil
		end
		if module:match("^core%.") then
			return CORE_DIR and system.join_path(DATA_DIR, rel .. ".lua") or nil
		end
		if module:match("^format%.") then
			local format_rel = module:sub(8):gsub("%.", "/")

			-- Map singular 'format.*' module ids onto the plural 'data/formats' directory
			return FORMATS_DIR and system.join_path(FORMATS_DIR, format_rel .. ".lua") or nil
		end
		if module:match("^plugins%.") then
			if not PLUGINS_DIR then
				return nil
			end

			local flat_path = system.join_path(DATA_DIR, rel .. ".lua")
			if path_exists(flat_path) then
				return flat_path
			end

			local base = module:match("^plugins%.([%w_]+)$")
			if not base then
				return flat_path
			end

			-- Keep canonical plugin ids stable when the entry file lives in 'name/name.lua'
			local dir_path = system.join_path(PLUGINS_DIR, base)
			local nested_path = system.join_path(dir_path, base .. ".lua")
			if path_exists(nested_path) then
				return nested_path
			end

			return flat_path
		end
		if module:match("^tests%.") then
			return TESTS_DIR and system.join_path(DATA_DIR, rel .. ".lua") or nil
		end
		if module == "user" then
			local p1 = system.join_path(DATA_DIR, "user.lua")
			if path_exists(p1) then
				return p1
			end
			local p2 = USER_DIR and (USER_DIR .. "init.lua") or nil
			if path_exists(p2) then
				return p2
			end
		end
		return nil
	end

	local function local_override_searcher(module)
		local p = module_path(module)
		if not p or not path_exists(p) then
			return nil
		end
		local loader, err = loadfile(p)
		if not loader then
			return "\n\tlocal override load failed: " .. tostring(err)
		end
		return loader
	end

	table.insert(package.searchers, 2, local_override_searcher)
end

local function setup_external_package_path()
	if not HAVE_LOCAL_DATA then
		return
	end
	package.path = DATA_DIR .. "?.lua;" .. package.path
	package.path = DATA_DIR .. "?/init.lua;" .. package.path
end

local MAX_STARTUP_SCALE = 20

--- Parse and normalize one startup scale from an environment string
-- @param raw string|nil raw environment value
-- @return number|nil scale
local function parse_scale(raw)
	if type(raw) ~= "string" or raw == "" then
		return nil
	end

	-- Pattern breakdown:
	-- '^%s+' removes leading whitespace and '%s+$' removes trailing whitespace
	raw = raw:gsub("^%s+", ""):gsub("%s+$", ""):gsub(",", ".")
	local scale = tonumber(raw)
	if not scale or scale <= 0 or scale ~= scale or scale == math.huge then
		return nil
	end

	if scale > 100 then
		scale = scale * 0.01
	end

	return math.min(scale, MAX_STARTUP_SCALE)
end

local boot = {}

--- Return pending.json from an early updater command line
-- @return string|nil path
local function get_pending_update_file()
	for index, argument in ipairs(ARGS or {}) do
		if argument == "--apply-update" then
			return ARGS[index + 1]
		end
	end

	return nil
end

--- Start the application core after applying environment scale overrides
function boot.run()
	SCALE = parse_scale(os.getenv("LX_SCALE"))
		or parse_scale(os.getenv("GDK_SCALE"))
		or parse_scale(os.getenv("QT_SCALE_FACTOR"))
		or SCALE
	setup_paths()
	setup_local_override_searcher()
	setup_external_package_path()

	local pending_info_file = get_pending_update_file()
	if pending_info_file then
		local applied, apply_error = require("core.update.apply").run(pending_info_file)
		if not applied then
			system.show_confirm_dialog(
				"Lx update failed",
				tostring(apply_error or "The pending update could not be installed")
			)
		end

		os.exit(applied and 0 or 1)
	end

	local core = require("core")
	core.init()

	-- Close the native boot log before the ordinary application loop begins
	if type(BOOT_COMPLETE) == "function" then
		BOOT_COMPLETE()
	end

	core.run()
	return core
end

return boot
