local M = {}

function M.create(ctx)
	local mp = ctx.mp
	local utils = ctx.utils
	local opts = ctx.opts
	local state = ctx.state
	local environment = ctx.environment
	local subminer_log = ctx.log.subminer_log

	local function normalize_binary_path_candidate(candidate)
		if type(candidate) ~= "string" then
			return nil
		end
		local trimmed = candidate:match("^%s*(.-)%s*$") or ""
		if trimmed == "" then
			return nil
		end
		if #trimmed >= 2 then
			local first = trimmed:sub(1, 1)
			local last = trimmed:sub(-1)
			if (first == '"' and last == '"') or (first == "'" and last == "'") then
				trimmed = trimmed:sub(2, -2)
			end
		end
		return trimmed ~= "" and trimmed or nil
	end

	local function binary_candidates_from_app_path(app_path)
		if environment.is_windows() then
			return {
				utils.join_path(app_path, "SubMiner.exe"),
				utils.join_path(app_path, "subminer.exe"),
			}
		end

		return {
			utils.join_path(app_path, "Contents", "MacOS", "SubMiner"),
			utils.join_path(app_path, "Contents", "MacOS", "subminer"),
		}
	end

	local function file_exists(path)
		local info = utils.file_info(path)
		if not info then
			return false
		end
		if info.is_dir ~= nil then
			return not info.is_dir
		end
		return true
	end

	local function directory_exists(path)
		local info = utils.file_info(path)
		return info ~= nil and info.is_dir == true
	end

	local function resolve_binary_candidate(candidate)
		local normalized = normalize_binary_path_candidate(candidate)
		if not normalized then
			return nil
		end

		if file_exists(normalized) then
			return normalized
		end

		if environment.is_windows() then
			if not normalized:lower():match("%.exe$") then
				local with_exe = normalized .. ".exe"
				if file_exists(with_exe) then
					return with_exe
				end
			end

			if directory_exists(normalized) then
				for _, path in ipairs(binary_candidates_from_app_path(normalized)) do
					if file_exists(path) then
						return path
					end
				end
			end

			return nil
		end

		if not normalized:lower():find("%.app") then
			return nil
		end

		local app_root = normalized
		if not app_root:lower():match("%.app$") then
			app_root = normalized:match("(.+%.app)")
		end
		if not app_root then
			return nil
		end

		for _, path in ipairs(binary_candidates_from_app_path(app_root)) do
			if file_exists(path) then
				return path
			end
		end

		return nil
	end

	local function find_binary_override()
		for _, env_name in ipairs({ "SUBMINER_APPIMAGE_PATH", "SUBMINER_BINARY_PATH" }) do
			local path = resolve_binary_candidate(os.getenv(env_name))
			if path and path ~= "" then
				return path
			end
		end

		if not environment.is_windows() then
			local appimage_path = resolve_binary_candidate(os.getenv("APPIMAGE"))
			if appimage_path and appimage_path ~= "" then
				return appimage_path
			end
		end

		return nil
	end

	local function add_search_path(search_paths, candidate)
		if type(candidate) == "string" and candidate ~= "" then
			search_paths[#search_paths + 1] = candidate
		end
	end

	local function trim_subprocess_stdout(value)
		if type(value) ~= "string" then
			return nil
		end
		local trimmed = value:match("^%s*(.-)%s*$") or ""
		if trimmed == "" then
			return nil
		end
		return trimmed
	end

	local function find_windows_binary_via_system_lookup()
		if not environment.is_windows() then
			return nil
		end
		if not mp or type(mp.command_native) ~= "function" then
			return nil
		end

		local script = [=[
function Emit-FirstExistingPath {
  param([string[]]$Candidates)

  foreach ($candidate in $Candidates) {
    if ([string]::IsNullOrWhiteSpace($candidate)) {
      continue
    }
    if (Test-Path -LiteralPath $candidate -PathType Leaf) {
      Write-Output $candidate
      exit 0
    }
  }
}

$runningProcess = Get-CimInstance Win32_Process |
  Where-Object { $_.Name -ieq 'SubMiner.exe' -or $_.Name -ieq 'subminer.exe' } |
  Select-Object -First 1 -Property ExecutablePath, CommandLine
if ($null -ne $runningProcess) {
  Emit-FirstExistingPath @($runningProcess.ExecutablePath)
}

$localAppData = [Environment]::GetFolderPath('LocalApplicationData')
$programFiles = [Environment]::GetFolderPath('ProgramFiles')
$programFilesX86 = ${env:ProgramFiles(x86)}

Emit-FirstExistingPath @(
  $(if (-not [string]::IsNullOrWhiteSpace($localAppData)) { Join-Path $localAppData 'Programs\SubMiner\SubMiner.exe' } else { $null }),
  $(if (-not [string]::IsNullOrWhiteSpace($programFiles)) { Join-Path $programFiles 'SubMiner\SubMiner.exe' } else { $null }),
  $(if (-not [string]::IsNullOrWhiteSpace($programFilesX86)) { Join-Path $programFilesX86 'SubMiner\SubMiner.exe' } else { $null }),
  'C:\SubMiner\SubMiner.exe'
)

foreach ($registryPath in @(
  'HKCU:\Software\Microsoft\Windows\CurrentVersion\App Paths\SubMiner.exe',
  'HKLM:\Software\Microsoft\Windows\CurrentVersion\App Paths\SubMiner.exe',
  'HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\SubMiner.exe'
)) {
  try {
    $appPath = (Get-ItemProperty -Path $registryPath -ErrorAction Stop).'(default)'
    Emit-FirstExistingPath @($appPath)
  } catch {
  }
}

try {
  $commandPath = Get-Command SubMiner.exe -ErrorAction Stop | Select-Object -First 1 -ExpandProperty Source
  Emit-FirstExistingPath @($commandPath)
} catch {
}
]=]

		local result = mp.command_native({
			name = "subprocess",
			args = {
				"powershell.exe",
				"-NoProfile",
				"-ExecutionPolicy",
				"Bypass",
				"-Command",
				script,
			},
			playback_only = false,
			capture_stdout = true,
			capture_stderr = false,
		})
		if not result or result.status ~= 0 then
			return nil
		end

		local candidate = trim_subprocess_stdout(result.stdout)
		if not candidate then
			return nil
		end

		return resolve_binary_candidate(candidate)
	end

	local function find_binary()
		local override = find_binary_override()
		if override then
			return override
		end

		local configured = resolve_binary_candidate(opts.binary_path)
		if configured then
			return configured
		end

		local system_lookup_binary = find_windows_binary_via_system_lookup()
		if system_lookup_binary then
			subminer_log("info", "binary", "Found Windows binary via system lookup at: " .. system_lookup_binary)
			return system_lookup_binary
		end

		local home = os.getenv("HOME") or os.getenv("USERPROFILE") or ""
		local app_data = os.getenv("APPDATA") or ""
		local app_data_local = app_data ~= "" and app_data:gsub("[/\\][Rr][Oo][Aa][Mm][Ii][Nn][Gg]$", "\\Local") or ""
		local local_app_data = os.getenv("LOCALAPPDATA") or utils.join_path(home, "AppData", "Local")
		local program_files = os.getenv("ProgramFiles") or "C:\\Program Files"
		local program_files_x86 = os.getenv("ProgramFiles(x86)") or "C:\\Program Files (x86)"
		local search_paths = {}

		if environment.is_windows() then
			add_search_path(search_paths, utils.join_path(app_data_local, "Programs", "SubMiner", "SubMiner.exe"))
			add_search_path(search_paths, utils.join_path(local_app_data, "Programs", "SubMiner", "SubMiner.exe"))
			add_search_path(search_paths, utils.join_path(program_files, "SubMiner", "SubMiner.exe"))
			add_search_path(search_paths, utils.join_path(program_files_x86, "SubMiner", "SubMiner.exe"))
			add_search_path(search_paths, "C:\\SubMiner\\SubMiner.exe")
		else
			add_search_path(search_paths, "/Applications/SubMiner.app/Contents/MacOS/SubMiner")
			add_search_path(search_paths, utils.join_path(home, "Applications", "SubMiner.app", "Contents", "MacOS", "SubMiner"))
			add_search_path(search_paths, utils.join_path(home, ".local", "bin", "SubMiner.AppImage"))
			add_search_path(search_paths, "/opt/SubMiner/SubMiner.AppImage")
			add_search_path(search_paths, "/usr/local/bin/SubMiner")
			add_search_path(search_paths, "/usr/local/bin/subminer")
			add_search_path(search_paths, "/usr/bin/SubMiner")
			add_search_path(search_paths, "/usr/bin/subminer")
		end

		for _, path in ipairs(search_paths) do
			if file_exists(path) then
				subminer_log("info", "binary", "Found binary at: " .. path)
				return path
			end
		end

		return nil
	end

	local function ensure_binary_available()
		if state.binary_available and state.binary_path and file_exists(state.binary_path) then
			return true
		end

		local discovered = find_binary()
		if discovered then
			state.binary_path = discovered
			state.binary_available = true
			return true
		end

		state.binary_path = nil
		state.binary_available = false
		return false
	end

	return {
		normalize_binary_path_candidate = normalize_binary_path_candidate,
		file_exists = file_exists,
		find_binary = find_binary,
		ensure_binary_available = ensure_binary_available,
		is_windows = environment.is_windows,
	}
end

return M
