local M = {}
local unpack_fn = table.unpack or unpack

function M.create(ctx)
	local mp = ctx.mp
	local utils = ctx.utils

	local detected_backend = nil
	local app_running_cache_value = nil
	local app_running_cache_time = nil
	local app_running_check_inflight = false
	local app_running_waiters = {}
	local APP_RUNNING_CACHE_TTL_SECONDS = 2

	local function is_windows()
		local platform = mp.get_property("platform") or ""
		if platform ~= "" then
			local normalized = platform:lower()
			if normalized == "windows" or normalized == "win32" then
				return true
			end
			if normalized == "macos" or normalized == "darwin" or normalized == "osx" or normalized == "linux" then
				return false
			end
		end
		return package.config:sub(1, 1) == "\\"
	end

	local function is_macos()
		local platform = mp.get_property("platform") or ""
		if platform ~= "" then
			local normalized = platform:lower()
			if normalized == "macos" or normalized == "darwin" or normalized == "osx" then
				return true
			end
			if normalized == "windows" or normalized == "win32" or normalized == "linux" then
				return false
			end
		end
		local ostype = os.getenv("OSTYPE") or ""
		return ostype:find("darwin") ~= nil
	end

	local function default_socket_path()
		if is_windows() then
			return "\\\\.\\pipe\\subminer-socket"
		end
		return "/tmp/subminer-socket"
	end

	local function path_separator()
		return is_windows() and "\\" or "/"
	end

	local function join_path(...)
		local parts = { ... }
		if utils and type(utils.join_path) == "function" then
			return utils.join_path(unpack_fn(parts))
		end
		return table.concat(parts, path_separator())
	end

	local function file_exists(path)
		if not utils or type(utils.file_info) ~= "function" then
			return false
		end
		return utils.file_info(path) ~= nil
	end

	local function resolve_subminer_config_dir()
		local home = os.getenv("HOME") or os.getenv("USERPROFILE") or ""
		local candidates = {}
		if is_windows() then
			local app_data = os.getenv("APPDATA") or join_path(home, "AppData", "Roaming")
			candidates = {
				join_path(app_data, "SubMiner"),
			}
		else
			local xdg_config_home = os.getenv("XDG_CONFIG_HOME")
			local primary_base = (type(xdg_config_home) == "string" and xdg_config_home ~= "")
					and xdg_config_home
				or join_path(home, ".config")
			candidates = {
				join_path(primary_base, "SubMiner"),
				join_path(home, ".config", "SubMiner"),
			}
		end

		for _, dir in ipairs(candidates) do
			if file_exists(join_path(dir, "config.jsonc")) or file_exists(join_path(dir, "config.json")) then
				return dir
			end
		end

		for _, dir in ipairs(candidates) do
			if file_exists(dir) then
				return dir
			end
		end

		return candidates[1]
	end

	local function resolve_session_bindings_artifact_path()
		return join_path(resolve_subminer_config_dir(), "session-bindings.json")
	end

	local function is_linux()
		return not is_windows() and not is_macos()
	end

	local function now_seconds()
		if type(mp.get_time) == "function" then
			local value = tonumber(mp.get_time())
			if value then
				return value
			end
		end
		return os.time()
	end

	local function process_list_has_subminer(raw_process_list)
		if type(raw_process_list) ~= "string" then
			return false
		end
		local process_list = raw_process_list:lower()
		for line in process_list:gmatch("[^\n]+") do
			if is_windows() then
				local image = line:match('^"([^"]+)","')
				if not image then
					image = line:match('^"([^"]+)"')
				end
				if image then
					if image == "subminer" or image == "subminer.exe" or image == "subminer.appimage" or image == "subminer.app" then
						return true
					end
					if image:find("subminer", 1, true) and not image:find(".lua", 1, true) then
						return true
					end
				end
			else
				local argv0 = line:match('^"([^"]+)"') or line:match("^%s*([^%s]+)")
				if argv0 and not argv0:find("subminer.lua", 1, true) and not argv0:find("subminer.conf", 1, true) then
					local exe = argv0:match("([^/\\]+)$") or argv0
					if exe == "SubMiner" or exe == "SubMiner.AppImage" or exe == "SubMiner.exe" or exe == "subminer" or exe == "subminer.appimage" or exe == "subminer.exe" then
						return true
					end
					if exe:find("subminer", 1, true) and exe:find("%.lua", 1, true) == nil and exe:find("%.app", 1, true) == nil then
						return true
					end
				end
			end
		end
		return false
	end

	local function process_scan_command()
		if is_windows() then
			return { "tasklist", "/FO", "CSV", "/NH" }
		end
		return { "ps", "-A", "-o", "args=" }
	end

	local function is_subminer_process_running()
		local result = mp.command_native({
			name = "subprocess",
			args = process_scan_command(),
			playback_only = false,
			capture_stdout = true,
			capture_stderr = false,
		})
		if not result or result.status ~= 0 then
			return false
		end
		return process_list_has_subminer(result.stdout)
	end

	local function flush_app_running_waiters(value)
		local waiters = app_running_waiters
		app_running_waiters = {}
		for _, waiter in ipairs(waiters) do
			waiter(value)
		end
	end

	local function is_subminer_app_running_async(callback, opts)
		opts = opts or {}
		local force_refresh = opts.force_refresh == true
		local now = now_seconds()
		if not force_refresh and app_running_cache_value ~= nil and app_running_cache_time ~= nil then
			if (now - app_running_cache_time) <= APP_RUNNING_CACHE_TTL_SECONDS then
				callback(app_running_cache_value)
				return
			end
		end

		app_running_waiters[#app_running_waiters + 1] = callback
		if app_running_check_inflight then
			return
		end
		app_running_check_inflight = true

		mp.command_native_async({
			name = "subprocess",
			args = process_scan_command(),
			playback_only = false,
			capture_stdout = true,
			capture_stderr = false,
		}, function(success, result)
			app_running_check_inflight = false
			local running = false
			if success and result and result.status == 0 then
				running = process_list_has_subminer(result.stdout)
			end
			app_running_cache_value = running
			app_running_cache_time = now_seconds()
			flush_app_running_waiters(running)
		end)
	end

	local function is_subminer_app_running()
		local running = is_subminer_process_running()
		app_running_cache_value = running
		app_running_cache_time = now_seconds()
		return running
	end

	local function set_subminer_app_running_cache(running)
		app_running_cache_value = running == true
		app_running_cache_time = now_seconds()
	end

	local function detect_backend()
		if detected_backend then
			return detected_backend
		end

		local backend = nil
		local subminer_log = ctx.log and ctx.log.subminer_log or function() end

		if is_macos() then
			backend = "macos"
		elseif is_windows() then
			backend = nil
		elseif os.getenv("HYPRLAND_INSTANCE_SIGNATURE") then
			backend = "hyprland"
		elseif os.getenv("SWAYSOCK") then
			backend = "sway"
		elseif os.getenv("XDG_SESSION_TYPE") == "x11" or os.getenv("DISPLAY") then
			backend = "x11"
		else
			subminer_log("warn", "backend", "Could not detect window manager, falling back to x11")
			backend = "x11"
		end

		detected_backend = backend
		if backend then
			subminer_log("info", "backend", "Detected backend: " .. backend)
		else
			subminer_log("info", "backend", "No backend detected")
		end
		return backend
	end

	return {
		is_windows = is_windows,
		is_macos = is_macos,
		is_linux = is_linux,
		join_path = join_path,
		default_socket_path = default_socket_path,
		resolve_subminer_config_dir = resolve_subminer_config_dir,
		resolve_session_bindings_artifact_path = resolve_session_bindings_artifact_path,
		is_subminer_process_running = is_subminer_process_running,
		is_subminer_app_running = is_subminer_app_running,
		is_subminer_app_running_async = is_subminer_app_running_async,
		set_subminer_app_running_cache = set_subminer_app_running_cache,
		detect_backend = detect_backend,
	}
end

return M
