<?php
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class ASA_Local_Inventory {

	const MAX_ROUTE_COUNT    = 1000;
	const MAX_ACTION_COUNT   = 500;
	const MAX_MANIFEST_FILES = 200;
	const SEARCH_MAX_DEPTH   = 6;
	const EXECUTION_SUMMARY_LIMIT = 240;

	public static function generate( array $args = array() ) {
		if ( ! function_exists( 'get_plugins' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		$args = wp_parse_args(
			$args,
			array(
				'include_dependency_audits' => true,
			)
		);
		$include_dependency_audits = ! empty( $args['include_dependency_audits'] );

		$tooling_capabilities = self::get_tooling_capabilities();

		$inventory = array(
			'wordpress_core'    => self::collect_wordpress_core(),
			'plugins'           => self::collect_plugins(),
			'themes'            => self::collect_themes(),
			'mu_plugins'        => self::collect_mu_plugins(),
			'roles'             => self::collect_roles(),
			'runtime'           => self::collect_runtime(),
			'db_posture'        => self::collect_db_posture(),
			'db_privileges'     => self::collect_db_privileges(),
			'site_health'       => self::collect_site_health_snapshot(),
			'rest_routes'       => self::collect_rest_routes(),
			'admin_ajax_actions'=> self::collect_admin_ajax_actions(),
			'composer_packages' => array(),
			'python_packages'   => array(),
			'js_packages'       => array(),
			'node_packages'     => array(),
			'npm_packages'      => array(),
			'yarn_packages'     => array(),
			'pnpm_packages'     => array(),
			'tooling_capabilities' => $tooling_capabilities,
			'dependency_audits' => array(
				'composer' => array(
					'status'  => 'skipped',
					'error'   => '',
					'records' => array(),
				),
				'pip'      => array(
					'status'  => 'skipped',
					'error'   => '',
					'records' => array(),
				),
				'npm'      => array(
					'status'  => 'skipped',
					'error'   => '',
					'records' => array(),
				),
				'yarn'     => array(
					'status'  => 'skipped',
					'error'   => '',
					'records' => array(),
				),
				'pnpm'     => array(
					'status'  => 'skipped',
					'error'   => '',
					'records' => array(),
				),
			),
		);

		$manifests = self::collect_manifest_inventory();
		$inventory['composer_packages'] = $manifests['composer_packages'];
		$inventory['python_packages']   = $manifests['python_packages'];
		$inventory['js_packages']       = $manifests['js_packages'];
		$inventory['node_packages']     = $manifests['node_packages'];
		$inventory['npm_packages']      = $manifests['npm_packages'];
		$inventory['yarn_packages']     = $manifests['yarn_packages'];
		$inventory['pnpm_packages']     = $manifests['pnpm_packages'];
		if ( $include_dependency_audits ) {
			$inventory['dependency_audits'] = self::run_dependency_audits( $manifests );
		}

		return array(
			'inventory_version' => '1.0.0',
			'generated_at_gmt'  => gmdate( 'Y-m-d H:i:s' ),
			'privacy'           => array(
				'mode'        => 'metadata_only',
				'description' => 'Inventory and dependency enrichment data excludes customer content, post bodies, comments, order contents, usernames, emails, secrets, tokens, API keys, and database row content.',
			),
			'inventory'         => $inventory,
			'summary'           => array(
				'active_plugins'        => count( $inventory['plugins']['active'] ),
				'inactive_plugins'      => count( $inventory['plugins']['inactive'] ),
				'mu_plugins'            => count( $inventory['mu_plugins'] ),
				'roles'                 => count( $inventory['roles'] ),
				'privileged_roles'      => count( array_filter( (array) $inventory['roles'], static function( $role ) { return ! empty( $role['privileged'] ); } ) ),
				'active_themes'         => count( $inventory['themes']['active'] ),
				'inactive_themes'       => count( $inventory['themes']['inactive'] ),
				'rest_routes'           => count( $inventory['rest_routes'] ),
				'public_rest_routes'    => count( array_filter( (array) $inventory['rest_routes'], static function( $route ) { return ! empty( $route['public'] ); } ) ),
				'admin_ajax_actions'    => count( $inventory['admin_ajax_actions'] ),
				'public_ajax_actions'   => count( array_filter( (array) $inventory['admin_ajax_actions'], static function( $action ) { return ! empty( $action['public'] ); } ) ),
				'composer_packages'     => count( $inventory['composer_packages'] ),
				'python_packages'       => count( $inventory['python_packages'] ),
				'js_packages'           => count( $inventory['js_packages'] ),
				'npm_packages'          => count( $inventory['npm_packages'] ),
				'yarn_packages'         => count( $inventory['yarn_packages'] ),
				'pnpm_packages'         => count( $inventory['pnpm_packages'] ),
				'execution_allowed'      => ! empty( $tooling_capabilities['execution_allowed'] ),
				'subprocess_available'   => ! empty( $tooling_capabilities['subprocess_execution_available'] ),
			),
		);
	}

	public static function get_tooling_capabilities() {
		$subprocess_available = function_exists( 'proc_open' );
		$temp_dir             = get_temp_dir();
		$temp_dir_writable    = '' !== $temp_dir && is_dir( $temp_dir ) && wp_is_writable( $temp_dir );
		$python               = self::detect_python_binary();

		return array(
			'execution_allowed'              => $subprocess_available,
			'subprocess_execution_available' => $subprocess_available,
			'temp_dir_writable'             => $temp_dir_writable,
			'timeout_support'               => array(
				'subprocess_hard_timeout'   => false,
				'phpcs_runtime_available'   => self::has_phpcs_runtime_support(),
				'max_runtime_hint_seconds'  => 20,
			),
			'phpcs'     => self::detect_bundled_phpcs_capability(),
			'wpcs'      => self::detect_wpcs_capability(),
			'composer'  => self::detect_binary_capability( 'composer', array( '--version' ) ),
			'npm'       => self::detect_binary_capability( 'npm', array( '--version' ) ),
			'yarn'      => self::detect_binary_capability( 'yarn', array( '--version' ) ),
			'pnpm'      => self::detect_binary_capability( 'pnpm', array( '--version' ) ),
			'pip_audit'   => self::detect_binary_capability( 'pip-audit', array( '--version' ) ),
			'osv_scanner' => self::detect_binary_capability( 'osv-scanner', array( '--version' ) ),
			'python'      => $python,
		);
	}


	private static function detect_bundled_phpcs_capability() {
		$binary = wp_normalize_path( ASA_DIR . 'vendor/bin/phpcs' );
		$present = file_exists( $binary ) && is_readable( $binary );
		$runtime = self::has_phpcs_runtime_support();
		$version = '';
		if ( $present && $runtime && function_exists( 'proc_open' ) ) {
			$run = self::run_command( array( PHP_BINARY, $binary, '--version' ), ABSPATH );
			$output = ! empty( $run['stdout'] ) ? (string) $run['stdout'] : ( ! empty( $run['stderr'] ) ? (string) $run['stderr'] : '' );
			$version = self::sanitize_execution_fragment( $output );
		}
		return array(
			'binary'            => $present ? $binary : '',
			'present'           => $present,
			'version'           => $version,
			'php_runtime_ready' => $runtime,
			'status'            => ( $present && $runtime ) ? 'available' : ( $present ? 'runtime_missing' : 'unavailable' ),
		);
	}

	private static function detect_wpcs_capability() {
		$path = wp_normalize_path( ASA_DIR . 'vendor/wp-coding-standards/wpcs/WordPress' );
		$present = is_dir( $path );
		return array(
			'path'    => $present ? $path : '',
			'present' => $present,
			'version' => $present ? 'bundled_with_agent' : '',
			'status'  => $present ? 'available' : 'unavailable',
		);
	}

	private static function has_phpcs_runtime_support() {
		return extension_loaded( 'tokenizer' ) && extension_loaded( 'xmlwriter' ) && extension_loaded( 'SimpleXML' );
	}

	private static function detect_python_binary() {
		foreach ( array( 'python3', 'python' ) as $binary ) {
			$capability = self::detect_binary_capability( $binary, array( '--version' ) );
			if ( ! empty( $capability['present'] ) ) {
				$capability['binary'] = $binary;
				return $capability;
			}
		}

		return array(
			'binary'  => '',
			'present' => false,
			'version' => '',
			'status'  => 'unavailable',
		);
	}

	private static function detect_binary_capability( $binary, array $version_args = array( '--version' ) ) {
		$binary = sanitize_text_field( (string) $binary );
		if ( '' === $binary ) {
			return array(
				'binary'  => '',
				'present' => false,
				'version' => '',
				'status'  => 'unavailable',
			);
		}

		$present = self::binary_exists( $binary );
		$version = '';
		if ( $present && function_exists( 'proc_open' ) ) {
			$run = self::run_command( array_merge( array( $binary ), $version_args ), ABSPATH );
			$output = '';
			if ( ! empty( $run['stdout'] ) ) {
				$output = (string) $run['stdout'];
			} elseif ( ! empty( $run['stderr'] ) ) {
				$output = (string) $run['stderr'];
			}
			$version = self::sanitize_execution_fragment( $output );
		}

		return array(
			'binary'  => $binary,
			'present' => $present,
			'version' => $version,
			'status'  => $present ? 'available' : 'unavailable',
		);
	}

	public static function build_execution_summary( $engine_key, $status, $reason, array $run = array(), array $extra = array() ) {
		$summary = array(
			'engine'              => sanitize_key( (string) $engine_key ),
			'status'              => sanitize_key( (string) $status ),
			'reason'              => sanitize_text_field( (string) $reason ),
			'exit_code'           => isset( $run['exit_code'] ) ? (int) $run['exit_code'] : null,
			'timed_out'           => ! empty( $run['timed_out'] ),
			'binary_missing'      => ! empty( $run['binary_missing'] ),
			'invalid_environment' => ! empty( $run['invalid_environment'] ),
			'stdout_summary'      => self::sanitize_execution_fragment( isset( $run['stdout'] ) ? (string) $run['stdout'] : '' ),
			'stderr_summary'      => self::sanitize_execution_fragment( isset( $run['stderr'] ) ? (string) $run['stderr'] : '' ),
		);
		foreach ( $extra as $key => $value ) {
			if ( is_scalar( $value ) || null === $value || is_array( $value ) ) {
				$summary[ sanitize_key( (string) $key ) ] = $value;
			}
		}
		return $summary;
	}

	private static function sanitize_execution_fragment( $text ) {
		$text = trim( preg_replace( '/\s+/', ' ', wp_strip_all_tags( (string) $text ) ) );
		if ( '' === $text ) {
			return '';
		}
		$text = preg_replace( '/[A-Za-z0-9_\-]{20,}/', '[redacted]', $text );
		return function_exists( 'mb_substr' ) ? mb_substr( $text, 0, self::EXECUTION_SUMMARY_LIMIT ) : substr( $text, 0, self::EXECUTION_SUMMARY_LIMIT );
	}


	private static function collect_wordpress_core() {
		global $wp_version;

		$core_updates   = function_exists( 'get_core_updates' ) ? get_core_updates() : array();
		$current_locale = function_exists( 'get_locale' ) ? get_locale() : '';
		$latest_version = '';
		$update_available = false;

		if ( ! empty( $core_updates ) && is_array( $core_updates ) ) {
			foreach ( $core_updates as $update ) {
				if ( empty( $update ) || ! is_object( $update ) ) {
					continue;
				}
				if ( ! empty( $update->current ) ) {
					$latest_version = sanitize_text_field( (string) $update->current );
				}
				if ( ! empty( $update->response ) && 'latest' !== $update->response ) {
					$update_available = true;
					break;
				}
			}
		}

		return array(
			'version'          => is_string( $wp_version ) ? sanitize_text_field( $wp_version ) : '',
			'latest_version'   => $latest_version,
			'update_available' => $update_available,
			'locale'           => sanitize_text_field( (string) $current_locale ),
			'multisite'        => is_multisite(),
		);
	}

	private static function collect_plugins() {
		$plugins        = get_plugins();
		$active_plugins = (array) get_option( 'active_plugins', array() );
		$network_active = is_multisite() ? (array) get_site_option( 'active_sitewide_plugins', array() ) : array();
		$auto_updates   = (array) get_site_option( 'auto_update_' . 'plugins', array() );
		$updates        = get_site_transient( 'update_plugins' );
		$lists          = array(
			'active'   => array(),
			'inactive' => array(),
		);

		foreach ( $plugins as $plugin_file => $plugin_data ) {
			$is_active = in_array( $plugin_file, $active_plugins, true ) || isset( $network_active[ $plugin_file ] );
			$slug      = self::plugin_slug_from_file( $plugin_file, isset( $plugin_data['TextDomain'] ) ? $plugin_data['TextDomain'] : '' );
			$new_version = '';
			if ( ! empty( $updates->response[ $plugin_file ] ) && is_object( $updates->response[ $plugin_file ] ) && ! empty( $updates->response[ $plugin_file ]->new_version ) ) {
				$new_version = sanitize_text_field( (string) $updates->response[ $plugin_file ]->new_version );
			}
			$entry = array(
				'slug'             => $slug,
				'name'             => isset( $plugin_data['Name'] ) ? sanitize_text_field( (string) $plugin_data['Name'] ) : $slug,
				'version'          => isset( $plugin_data['Version'] ) ? sanitize_text_field( (string) $plugin_data['Version'] ) : '',
				'latest_version'   => $new_version,
				'file'             => sanitize_text_field( (string) $plugin_file ),
				'textdomain'       => isset( $plugin_data['TextDomain'] ) ? sanitize_key( (string) $plugin_data['TextDomain'] ) : '',
				'active'           => $is_active,
				'network_active'   => isset( $network_active[ $plugin_file ] ),
				'auto_update'      => in_array( $plugin_file, $auto_updates, true ),
				'update_available' => ! empty( $updates->response[ $plugin_file ] ),
			);
			$lists[ $is_active ? 'active' : 'inactive' ][] = $entry;
		}

		return $lists;
	}

	private static function collect_themes() {
		$themes        = wp_get_themes();
		$current_theme = wp_get_theme();
		$updates       = get_site_transient( 'update_themes' );
		$lists         = array(
			'active'   => array(),
			'inactive' => array(),
		);

		foreach ( $themes as $stylesheet => $theme ) {
			$is_active   = $current_theme->get_stylesheet() === $stylesheet;
			$new_version = '';
			if ( ! empty( $updates->response[ $stylesheet ] ) && is_array( $updates->response[ $stylesheet ] ) && ! empty( $updates->response[ $stylesheet ]['new_version'] ) ) {
				$new_version = sanitize_text_field( (string) $updates->response[ $stylesheet ]['new_version'] );
			}
			$lists[ $is_active ? 'active' : 'inactive' ][] = array(
				'slug'             => sanitize_key( (string) $stylesheet ),
				'stylesheet'       => sanitize_text_field( (string) $stylesheet ),
				'name'             => sanitize_text_field( (string) $theme->get( 'Name' ) ),
				'version'          => sanitize_text_field( (string) $theme->get( 'Version' ) ),
				'latest_version'   => $new_version,
				'parent'           => $theme->parent() ? sanitize_key( (string) $theme->parent()->get_stylesheet() ) : '',
				'active'           => $is_active,
				'update_available' => ! empty( $updates->response[ $stylesheet ] ),
			);
		}

		return $lists;
	}

	private static function collect_mu_plugins() {
		$mu_plugins = function_exists( 'get_mu_plugins' ) ? get_mu_plugins() : array();
		$results    = array();
		foreach ( $mu_plugins as $plugin_file => $plugin_data ) {
			$results[] = array(
				'slug'             => self::plugin_slug_from_file( $plugin_file, isset( $plugin_data['TextDomain'] ) ? $plugin_data['TextDomain'] : '' ),
				'name'             => isset( $plugin_data['Name'] ) ? sanitize_text_field( (string) $plugin_data['Name'] ) : sanitize_text_field( basename( (string) $plugin_file ) ),
				'version'          => isset( $plugin_data['Version'] ) ? sanitize_text_field( (string) $plugin_data['Version'] ) : '',
				'file'             => sanitize_text_field( (string) basename( $plugin_file ) ),
				'textdomain'       => isset( $plugin_data['TextDomain'] ) ? sanitize_key( (string) $plugin_data['TextDomain'] ) : '',
				'active'           => true,
				'update_available' => false,
			);
		}
		return $results;
	}


	private static function collect_roles() {
		if ( ! function_exists( 'wp_roles' ) ) {
			return array();
		}
		$roles_object = wp_roles();
		if ( ! $roles_object || empty( $roles_object->roles ) || ! is_array( $roles_object->roles ) ) {
			return array();
		}
		$privileged_caps = array(
			'manage_options',
			'manage_network',
			'activate_plugins',
			'install_plugins',
			'update_plugins',
			'edit_plugins',
			'edit_themes',
			'update_themes',
			'install_themes',
			'manage_woocommerce',
			'promote_users',
			'create_users',
			'delete_users',
		);
		$roles = array();
		foreach ( $roles_object->roles as $role_key => $role_data ) {
			$capabilities = ! empty( $role_data['capabilities'] ) && is_array( $role_data['capabilities'] ) ? $role_data['capabilities'] : array();
			$notable_caps = array();
			$privileged   = false;
			foreach ( $privileged_caps as $capability ) {
				if ( ! empty( $capabilities[ $capability ] ) ) {
					$notable_caps[] = $capability;
					$privileged = true;
				}
			}
			$roles[] = array(
				'role'             => sanitize_key( (string) $role_key ),
				'name'             => ! empty( $role_data['name'] ) ? sanitize_text_field( (string) $role_data['name'] ) : sanitize_text_field( (string) $role_key ),
				'capability_count' => count( array_filter( $capabilities ) ),
				'privileged'       => $privileged,
				'notable_caps'     => array_values( array_map( 'sanitize_key', $notable_caps ) ),
			);
		}
		usort(
			$roles,
			static function( $a, $b ) {
				return strcmp( $a['role'], $b['role'] );
			}
		);
		return $roles;
	}

	private static function collect_db_posture() {
		global $wpdb;
		$variable_names = array(
			'sql_mode',
			'lower_case_table_names',
			'local_infile',
			'secure_file_priv',
			'read_only',
			'super_read_only',
			'have_ssl',
			'require_secure_transport',
		);
		$cache_key = 'asa_db_posture_' . md5( wp_json_encode( $variable_names ) );
		$rows = wp_cache_get( $cache_key, 'aegisify_audit_agent' );
		if ( false === $rows ) {
			$placeholders = implode( ',', array_fill( 0, count( $variable_names ), '%s' ) );
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Cached read-only inventory query.
			$rows = $wpdb->get_results( $wpdb->prepare( "SHOW VARIABLES WHERE Variable_name IN ($placeholders)", $variable_names ), ARRAY_A );
			wp_cache_set( $cache_key, $rows, 'aegisify_audit_agent', MINUTE_IN_SECONDS * 10 );
		}
		if ( ! is_array( $rows ) ) {
			return array();
		}
		$posture = array();
		foreach ( $rows as $row ) {
			if ( empty( $row['Variable_name'] ) ) {
				continue;
			}
			$key = sanitize_key( strtolower( (string) $row['Variable_name'] ) );
			$posture[ $key ] = sanitize_text_field( (string) $row['Value'] );
		}
		if ( isset( $posture['secure_file_priv'] ) && '' !== $posture['secure_file_priv'] ) {
			$posture['secure_file_priv'] = 'restricted';
		}
		return $posture;
	}

	private static function collect_db_privileges() {
		global $wpdb;
		$result = array(
			'status'               => 'unavailable',
			'grant_count'          => 0,
			'has_all_privileges'   => false,
			'has_file_priv'        => false,
			'has_super_priv'       => false,
			'has_grant_option'     => false,
			'has_create_user_priv' => false,
			'has_process_priv'     => false,
		);
		$cache_key = 'asa_db_grants_current_user';
		$grants = wp_cache_get( $cache_key, 'aegisify_audit_agent' );
		if ( false === $grants ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only grant inspection query with no user input.
			$grants = $wpdb->get_col( 'SHOW GRANTS FOR CURRENT_USER' );
			wp_cache_set( $cache_key, $grants, 'aegisify_audit_agent', MINUTE_IN_SECONDS * 10 );
		}
		if ( empty( $grants ) || ! is_array( $grants ) ) {
			return $result;
		}
		$result['status']      = 'available';
		$result['grant_count'] = count( $grants );
		foreach ( $grants as $grant ) {
			$grant = strtoupper( sanitize_text_field( (string) $grant ) );
			if ( false !== strpos( $grant, 'ALL PRIVILEGES' ) ) {
				$result['has_all_privileges'] = true;
			}
			if ( false !== strpos( $grant, ' FILE' ) || 0 === strpos( $grant, 'FILE' ) ) {
				$result['has_file_priv'] = true;
			}
			if ( false !== strpos( $grant, ' SUPER' ) || 0 === strpos( $grant, 'SUPER' ) ) {
				$result['has_super_priv'] = true;
			}
			if ( false !== strpos( $grant, ' GRANT OPTION' ) || 0 === strpos( $grant, 'GRANT OPTION' ) ) {
				$result['has_grant_option'] = true;
			}
			if ( false !== strpos( $grant, ' CREATE USER' ) || 0 === strpos( $grant, 'CREATE USER' ) ) {
				$result['has_create_user_priv'] = true;
			}
			if ( false !== strpos( $grant, ' PROCESS' ) || 0 === strpos( $grant, 'PROCESS' ) ) {
				$result['has_process_priv'] = true;
			}
		}
		return $result;
	}

	private static function collect_runtime() {
		global $wpdb;

		$php_version = PHP_VERSION;
		$db_version  = method_exists( $wpdb, 'db_version' ) ? (string) $wpdb->db_version() : '';
		$db_server   = method_exists( $wpdb, 'db_server_info' ) ? (string) $wpdb->db_server_info() : '';
		$db_engine   = '';
		if ( '' !== $db_server ) {
			if ( false !== stripos( $db_server, 'mariadb' ) ) {
				$db_engine = 'MariaDB';
			} elseif ( false !== stripos( $db_server, 'mysql' ) ) {
				$db_engine = 'MySQL';
			}
		}
		if ( '' === $db_engine ) {
			$db_engine = 'MariaDB' === self::db_engine_from_version_string( $db_version ) ? 'MariaDB' : 'MySQL';
		}

		$loopback        = self::test_loopback();
		$curl_data       = function_exists( 'curl_version' ) ? curl_version() : array();
		$curl_version    = is_array( $curl_data ) && ! empty( $curl_data['version'] ) ? (string) $curl_data['version'] : '';
		$server_software = isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( (string) $_SERVER['SERVER_SOFTWARE'] ) ) : '';
		$imagick_version = '';
		if ( class_exists( 'Imagick' ) ) {
			$imagick_version_data = Imagick::getVersion();
			if ( is_array( $imagick_version_data ) && ! empty( $imagick_version_data['versionString'] ) ) {
				if ( preg_match( '/ImageMagick\s+([0-9][0-9A-Za-z\.\-_]+)/i', (string) $imagick_version_data['versionString'], $imagick_matches ) ) {
					$imagick_version = (string) $imagick_matches[1];
				}
			}
		}
		$sqlite_version = '';
		if ( class_exists( 'SQLite3' ) && method_exists( 'SQLite3', 'version' ) ) {
			$sqlite_data = SQLite3::version();
			if ( is_array( $sqlite_data ) && ! empty( $sqlite_data['versionString'] ) ) {
				$sqlite_version = (string) $sqlite_data['versionString'];
			}
		} elseif ( defined( 'SQLITE3_VERSION' ) ) {
			$sqlite_version = (string) SQLITE3_VERSION;
		}
		$cache_runtime_versions = self::detect_object_cache_runtime_versions();

		return array(
			'php_version'            => sanitize_text_field( (string) $php_version ),
			'db_engine'              => sanitize_text_field( (string) $db_engine ),
			'db_version'             => sanitize_text_field( (string) $db_version ),
			'db_server_info'         => sanitize_text_field( (string) $db_server ),
			'server_software'        => $server_software,
			'curl_version'           => sanitize_text_field( (string) $curl_version ),
			'imagemagick_version'    => sanitize_text_field( (string) $imagick_version ),
			'sqlite_version'         => sanitize_text_field( (string) $sqlite_version ),
			'memcached_version'      => sanitize_text_field( (string) $cache_runtime_versions['memcached'] ),
			'redis_version'          => sanitize_text_field( (string) $cache_runtime_versions['redis'] ),
			'wp_cron'                => ! defined( 'DISABLE_WP_CRON' ) || ! DISABLE_WP_CRON,
			'loopback_status'        => $loopback['status'],
			'loopback_code'          => $loopback['code'],
			'curl_enabled'           => function_exists( 'curl_version' ),
			'http_api'               => function_exists( 'wp_remote_get' ),
			'file_editors_disabled'  => ( defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT ) || ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ),
			'file_mods_allowed'      => ! ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ),
			'xmlrpc_enabled'         => apply_filters( 'xmlrpc_enabled', true ),
			'is_ssl'                 => is_ssl(),
		);
	}


	private static function detect_object_cache_runtime_versions() {
		$versions = array(
			'memcached' => '',
			'redis'     => '',
		);

		if ( function_exists( 'wp_using_ext_object_cache' ) && ! wp_using_ext_object_cache() ) {
			return $versions;
		}

		global $wp_object_cache;

		$candidates = array();
		if ( is_object( $wp_object_cache ) ) {
			$candidates[] = $wp_object_cache;
			foreach ( array( 'redis', 'redis_instance', 'redis_client', 'client', 'master', 'slave', 'm', 'memcached', 'mc', 'cache' ) as $property ) {
				if ( isset( $wp_object_cache->$property ) ) {
					$candidates[] = $wp_object_cache->$property;
				}
			}
			foreach ( array( 'redis_instance', 'redis_server', 'redis', 'client', 'get_redis_client', 'get_redis', 'memcached' ) as $method ) {
				if ( method_exists( $wp_object_cache, $method ) ) {
					try {
						$candidates[] = $wp_object_cache->$method();
					} catch ( Throwable $e ) {
						// Ignore object cache client introspection failures and continue.
					}
				}
			}
		}

		foreach ( $candidates as $candidate ) {
			if ( '' === $versions['memcached'] ) {
				$versions['memcached'] = self::extract_memcached_runtime_version( $candidate );
			}
			if ( '' === $versions['redis'] ) {
				$versions['redis'] = self::extract_redis_runtime_version( $candidate );
			}
			if ( '' !== $versions['memcached'] && '' !== $versions['redis'] ) {
				break;
			}
		}

		return $versions;
	}

	private static function extract_memcached_runtime_version( $candidate ) {
		if ( $candidate instanceof Memcached ) {
			try {
				$versions = $candidate->getVersion();
			} catch ( Throwable $e ) {
				$versions = array();
			}
			if ( is_array( $versions ) ) {
				foreach ( $versions as $version ) {
					$normalized = self::normalize_cache_runtime_version( $version );
					if ( '' !== $normalized ) {
						return $normalized;
					}
				}
			}
		}

		if ( is_array( $candidate ) ) {
			foreach ( $candidate as $value ) {
				$normalized = self::extract_memcached_runtime_version( $value );
				if ( '' !== $normalized ) {
					return $normalized;
				}
			}
		}

		if ( is_object( $candidate ) ) {
			foreach ( array( 'm', 'memcached', 'client', 'cache' ) as $property ) {
				if ( isset( $candidate->$property ) ) {
					$normalized = self::extract_memcached_runtime_version( $candidate->$property );
					if ( '' !== $normalized ) {
						return $normalized;
					}
				}
			}
			if ( method_exists( $candidate, 'getVersion' ) ) {
				try {
					$versions = $candidate->getVersion();
				} catch ( Throwable $e ) {
					$versions = array();
				}
				if ( is_array( $versions ) ) {
					foreach ( $versions as $version ) {
						$normalized = self::normalize_cache_runtime_version( $version );
						if ( '' !== $normalized ) {
							return $normalized;
						}
					}
				}
			}
		}

		return '';
	}

	private static function extract_redis_runtime_version( $candidate ) {
		if ( $candidate instanceof Redis ) {
			$version = self::extract_redis_runtime_version_from_info_call( $candidate );
			if ( '' !== $version ) {
				return $version;
			}
		}

		if ( is_array( $candidate ) ) {
			if ( ! empty( $candidate['redis_version'] ) ) {
				$normalized = self::normalize_cache_runtime_version( $candidate['redis_version'] );
				if ( '' !== $normalized ) {
					return $normalized;
				}
			}
			foreach ( $candidate as $value ) {
				$normalized = self::extract_redis_runtime_version( $value );
				if ( '' !== $normalized ) {
					return $normalized;
				}
			}
		}

		if ( is_object( $candidate ) ) {
			foreach ( array( 'redis', 'redis_instance', 'redis_client', 'client', 'master', 'slave', 'cache' ) as $property ) {
				if ( isset( $candidate->$property ) ) {
					$normalized = self::extract_redis_runtime_version( $candidate->$property );
					if ( '' !== $normalized ) {
						return $normalized;
					}
				}
			}
			foreach ( array( 'info', 'redis_status', 'diagnostics' ) as $method ) {
				if ( method_exists( $candidate, $method ) ) {
					try {
						$data = 'info' === $method ? $candidate->$method( 'server' ) : $candidate->$method();
					} catch ( Throwable $e ) {
						$data = array();
					}
					$normalized = self::extract_redis_runtime_version( $data );
					if ( '' !== $normalized ) {
						return $normalized;
					}
				}
			}
		}

		return '';
	}

	private static function extract_redis_runtime_version_from_info_call( $client ) {
		$info = array();
		try {
			$info = $client->info( 'server' );
		} catch ( Throwable $e ) {
			$info = array();
		}
		if ( empty( $info ) ) {
			try {
				$info = $client->info();
			} catch ( Throwable $e ) {
				$info = array();
			}
		}
		if ( is_array( $info ) && ! empty( $info['redis_version'] ) ) {
			return self::normalize_cache_runtime_version( $info['redis_version'] );
		}
		return '';
	}

	private static function normalize_cache_runtime_version( $value ) {
		$value = sanitize_text_field( (string) $value );
		if ( '' === $value ) {
			return '';
		}
		if ( preg_match( '/([0-9]+(?:\.[0-9]+){1,3})/', $value, $matches ) ) {
			return sanitize_text_field( (string) $matches[1] );
		}
		return '';
	}

	private static function collect_site_health_snapshot() {
		$debug_enabled = ( defined( 'WP_DEBUG' ) && WP_DEBUG );
		$auto_updates  = get_site_option( 'auto_update_' . 'plugins', array() );
		$core_updates  = function_exists( 'get_core_updates' ) ? get_core_updates() : array();
		$plugin_updates = get_site_transient( 'update_plugins' );
		$theme_updates  = get_site_transient( 'update_themes' );

		return array(
			'is_ssl'                => is_ssl(),
			'blog_public'           => (int) get_option( 'blog_public', 1 ),
			'debug_enabled'         => $debug_enabled,
			'automatic_updates'     => ! empty( $auto_updates ),
			'plugin_updates'        => ! empty( $plugin_updates->response ) ? count( (array) $plugin_updates->response ) : 0,
			'theme_updates'         => ! empty( $theme_updates->response ) ? count( (array) $theme_updates->response ) : 0,
			'core_update_available' => ! empty( $core_updates ) && is_array( $core_updates ) && ! empty( $core_updates[0]->response ) && 'latest' !== $core_updates[0]->response,
			'rest_api'              => function_exists( 'rest_get_server' ),
			'cron_scheduled'        => is_array( _get_cron_array() ) ? count( _get_cron_array() ) : 0,
		);
	}

	private static function collect_rest_routes() {
		$routes = array();
		if ( function_exists( 'rest_get_server' ) ) {
			$server = rest_get_server();
			if ( $server && method_exists( $server, 'get_routes' ) ) {
				foreach ( $server->get_routes() as $route => $handlers ) {
					$methods              = array();
					$public               = false;
					$authenticated_only   = false;
					$permission_callbacks = array();
					$arg_keys             = array();
					foreach ( (array) $handlers as $handler ) {
						if ( ! is_array( $handler ) ) {
							continue;
						}
						if ( ! empty( $handler['methods'] ) ) {
							$raw_methods = is_array( $handler['methods'] ) ? array_keys( $handler['methods'] ) : preg_split( '/\s*,\s*/', (string) $handler['methods'] );
							foreach ( (array) $raw_methods as $method ) {
								$methods[] = sanitize_text_field( strtoupper( (string) $method ) );
							}
						}
						if ( ! empty( $handler['args'] ) && is_array( $handler['args'] ) ) {
							foreach ( array_keys( $handler['args'] ) as $arg_key ) {
								$arg_keys[] = sanitize_key( (string) $arg_key );
							}
						}
						$callback_name = self::describe_callback( isset( $handler['permission_callback'] ) ? $handler['permission_callback'] : null );
						if ( '' === $callback_name ) {
							$permission_callbacks[] = 'missing';
						} else {
							$permission_callbacks[] = $callback_name;
						}
						if ( '__return_true' === $callback_name || 'missing' === $callback_name ) {
							$public = true;
						} else {
							$authenticated_only = true;
						}
					}
					$routes[] = array(
						'route'                => sanitize_text_field( (string) $route ),
						'methods'              => array_values( array_unique( array_filter( $methods ) ) ),
						'public'               => $public,
						'authenticated_only'   => $authenticated_only,
						'permission_callbacks' => array_values( array_unique( array_filter( array_map( 'sanitize_text_field', $permission_callbacks ) ) ) ),
						'arg_keys'             => array_values( array_unique( array_filter( $arg_keys ) ) ),
					);
				}
			}
		}
		usort(
			$routes,
			static function( $a, $b ) {
				return strcmp( $a['route'], $b['route'] );
			}
		);
		return array_slice( $routes, 0, self::MAX_ROUTE_COUNT );
	}

	private static function collect_admin_ajax_actions() {
		global $wp_filter;

		$actions = array();
		if ( is_array( $wp_filter ) ) {
			foreach ( array_keys( $wp_filter ) as $hook_name ) {
				$hook_name = (string) $hook_name;
				if ( 0 === strpos( $hook_name, 'wp_ajax_nopriv_' ) ) {
					$actions[] = array(
						'action' => sanitize_key( substr( $hook_name, 16 ) ),
						'public' => true,
					);
				} elseif ( 0 === strpos( $hook_name, 'wp_ajax_' ) ) {
					$actions[] = array(
						'action' => sanitize_key( substr( $hook_name, 8 ) ),
						'public' => false,
					);
				}
			}
		}

		$indexed = array();
		foreach ( $actions as $action ) {
			if ( empty( $action['action'] ) ) {
				continue;
			}
			$key = $action['action'] . '|' . ( ! empty( $action['public'] ) ? '1' : '0' );
			$indexed[ $key ] = $action;
		}
		usort(
			$indexed,
			static function( $a, $b ) {
				return strcmp( $a['action'], $b['action'] );
			}
		);
		return array_slice( array_values( $indexed ), 0, self::MAX_ACTION_COUNT );
	}


	private static function describe_callback( $callback ) {
		if ( null === $callback ) {
			return '';
		}
		if ( is_string( $callback ) ) {
			return sanitize_text_field( $callback );
		}
		if ( is_array( $callback ) && 2 === count( $callback ) ) {
			$owner  = is_object( $callback[0] ) ? get_class( $callback[0] ) : (string) $callback[0];
			$method = (string) $callback[1];
			return sanitize_text_field( $owner . '::' . $method );
		}
		if ( $callback instanceof Closure ) {
			return 'closure';
		}
		if ( is_object( $callback ) && is_callable( $callback ) ) {
			return sanitize_text_field( get_class( $callback ) );
		}
		return 'callable';
	}

	private static function collect_manifest_inventory() {
		$composer_files = self::find_manifest_files( array( 'composer.lock' ), self::MAX_MANIFEST_FILES );
		$python_files   = self::find_manifest_files( array( 'requirements.txt', 'pyproject.toml', 'poetry.lock', 'Pipfile.lock' ), self::MAX_MANIFEST_FILES );
		$npm_files      = self::find_manifest_files( array( 'package-lock.json' ), self::MAX_MANIFEST_FILES );
		$yarn_files     = self::find_manifest_files( array( 'yarn.lock' ), self::MAX_MANIFEST_FILES );
		$pnpm_files     = self::find_manifest_files( array( 'pnpm-lock.yaml' ), self::MAX_MANIFEST_FILES );
		$js_files       = array_values( array_unique( array_merge( $npm_files, $yarn_files, $pnpm_files ) ) );

		$composer_packages = self::collect_composer_packages( $composer_files );
		$python_packages   = self::collect_python_packages( $python_files );
		$npm_packages      = self::collect_js_packages( $npm_files, 'npm' );
		$yarn_packages     = self::collect_js_packages( $yarn_files, 'yarn' );
		$pnpm_packages     = self::collect_js_packages( $pnpm_files, 'pnpm' );
		$js_packages       = self::merge_package_lists( array( $npm_packages, $yarn_packages, $pnpm_packages ) );

		return array(
			'composer_files'    => $composer_files,
			'python_files'      => $python_files,
			'npm_files'         => $npm_files,
			'yarn_files'        => $yarn_files,
			'pnpm_files'        => $pnpm_files,
			'js_files'          => $js_files,
			'composer_packages' => $composer_packages,
			'python_packages'   => $python_packages,
			'js_packages'       => $js_packages,
			'node_packages'     => $js_packages,
			'npm_packages'      => $npm_packages,
			'yarn_packages'     => $yarn_packages,
			'pnpm_packages'     => $pnpm_packages,
		);
	}

	private static function collect_composer_packages( array $manifest_files ) {
		$packages = array();
		foreach ( $manifest_files as $manifest_file ) {
			$body = self::read_file( $manifest_file );
			if ( '' === $body ) {
				continue;
			}
			foreach ( self::parse_composer_lock( $body, self::relative_path( $manifest_file ) ) as $package ) {
				$key = $package['name'] . '|' . $package['version'];
				$packages[ $key ] = $package;
			}
		}
		return array_values( $packages );
	}

	private static function collect_python_packages( array $manifest_files ) {
		$packages = array();
		foreach ( $manifest_files as $manifest_file ) {
			$body = self::read_file( $manifest_file );
			if ( '' === $body ) {
				continue;
			}
			$basename = basename( $manifest_file );
			$relative = self::relative_path( $manifest_file );
			if ( 'requirements.txt' === $basename ) {
				$parsed = self::parse_requirements_txt( $body, $relative );
			} elseif ( 'pyproject.toml' === $basename ) {
				$parsed = self::parse_pyproject_toml( $body, $relative );
			} elseif ( 'poetry.lock' === $basename ) {
				$parsed = self::parse_poetry_lock( $body, $relative );
			} else {
				$parsed = self::parse_pipfile_lock( $body, $relative );
			}
			foreach ( $parsed as $package ) {
				$key = $package['name'] . '|' . $package['version'];
				$packages[ $key ] = $package;
			}
		}
		return array_values( $packages );
	}

	private static function collect_js_packages( array $manifest_files, $package_manager = '' ) {
		$packages = array();
		foreach ( $manifest_files as $manifest_file ) {
			$body = self::read_file( $manifest_file );
			if ( '' === $body ) {
				continue;
			}
			$basename = basename( $manifest_file );
			$relative = self::relative_path( $manifest_file );
			if ( 'package-lock.json' === $basename ) {
				$parsed = self::parse_package_lock( $body, $relative );
				$manager = 'npm';
			} elseif ( 'yarn.lock' === $basename ) {
				$parsed = self::parse_yarn_lock( $body, $relative );
				$manager = 'yarn';
			} else {
				$parsed = self::parse_pnpm_lock( $body, $relative );
				$manager = 'pnpm';
			}
			foreach ( $parsed as $package ) {
				$package['package_manager'] = '' !== $package_manager ? sanitize_key( (string) $package_manager ) : $manager;
				$key = $package['name'] . '|' . $package['version'] . '|' . $package['package_manager'];
				$packages[ $key ] = $package;
			}
		}
		return array_values( $packages );
	}

	private static function run_dependency_audits( array $manifests ) {
		return array(
			'composer' => self::run_composer_audit( $manifests ),
			'pip'      => self::run_pip_audit( $manifests ),
			'npm'      => self::run_node_audit( $manifests, 'npm' ),
			'yarn'     => self::run_node_audit( $manifests, 'yarn' ),
			'pnpm'     => self::run_node_audit( $manifests, 'pnpm' ),
		);
	}

	private static function run_composer_audit( array $manifests ) {
		$composer_packages = ! empty( $manifests['composer_packages'] ) && is_array( $manifests['composer_packages'] ) ? $manifests['composer_packages'] : array();
		$composer_files    = ! empty( $manifests['composer_files'] ) && is_array( $manifests['composer_files'] ) ? $manifests['composer_files'] : array();
		if ( empty( $composer_packages ) || empty( $composer_files ) ) {
			return array( 'status' => 'skipped', 'error' => '', 'records' => array(), 'execution' => self::build_execution_summary( 'composer_audit', 'skipped', 'No composer manifests were found on the agent host.', array(), array( 'manifest_count' => count( $composer_files ) ) ) );
		}
		if ( ! self::binary_exists( 'composer' ) ) {
			return array( 'status' => 'unavailable', 'error' => 'composer binary unavailable', 'records' => array(), 'execution' => self::build_execution_summary( 'composer_audit', 'unavailable', 'composer binary unavailable', array( 'binary_missing' => true ), array( 'manifest_count' => count( $composer_files ) ) ) );
		}

		$records = array();
		$handled_dirs = array();
		foreach ( (array) $manifests['composer_files'] as $lock_path ) {
			$working_dir = dirname( $lock_path );
			if ( isset( $handled_dirs[ $working_dir ] ) ) {
				continue;
			}
			$handled_dirs[ $working_dir ] = true;

			$lock_body = self::read_file( $lock_path );
			if ( '' === $lock_body ) {
				continue;
			}
			$temp_dir = trailingslashit( get_temp_dir() ) . 'asa-composer-audit-' . wp_generate_password( 8, false, false );
			if ( ! wp_mkdir_p( $temp_dir ) ) {
				return array( 'status' => 'error', 'error' => 'unable to create composer audit temp dir', 'records' => array(), 'execution' => self::build_execution_summary( 'composer_audit', 'error', 'Unable to create composer audit temp directory.', array( 'invalid_environment' => true ), array( 'manifest_count' => count( $composer_files ) ) ) );
			}
			file_put_contents( trailingslashit( $temp_dir ) . 'composer.lock', $lock_body );
			$composer_json_path = trailingslashit( $working_dir ) . 'composer.json';
			if ( is_readable( $composer_json_path ) && is_file( $composer_json_path ) ) {
				file_put_contents( trailingslashit( $temp_dir ) . 'composer.json', self::read_file( $composer_json_path ) );
			} else {
				file_put_contents( trailingslashit( $temp_dir ) . 'composer.json', wp_json_encode( array( 'name' => 'aegisify/local-audit', 'require' => new stdClass() ) ) );
			}
			$run = self::run_command( array( 'composer', 'audit', '--locked', '--format=json', '--no-interaction' ), $temp_dir );
			self::cleanup_temp_dir( $temp_dir );
			if ( empty( $run['stdout'] ) ) {
				if ( 0 !== (int) $run['exit_code'] ) {
					return array( 'status' => 'error', 'error' => ! empty( $run['stderr'] ) ? sanitize_text_field( (string) $run['stderr'] ) : 'composer audit returned no output', 'records' => array(), 'execution' => self::build_execution_summary( 'composer_audit', 'error', 'composer audit returned no output', $run, array( 'manifest_count' => count( $composer_files ) ) ) );
				}
				continue;
			}
			$data = json_decode( (string) $run['stdout'], true );
			if ( ! is_array( $data ) ) {
				return array( 'status' => 'error', 'error' => 'invalid composer audit json', 'records' => array(), 'execution' => self::build_execution_summary( 'composer_audit', 'error', 'Invalid composer audit JSON was returned.', $run, array( 'manifest_count' => count( $composer_files ) ) ) );
			}
			$advisories = ! empty( $data['advisories'] ) && is_array( $data['advisories'] ) ? $data['advisories'] : array();
			foreach ( $advisories as $package_name => $package_advisories ) {
				foreach ( (array) $package_advisories as $advisory ) {
					$advisory_id = ! empty( $advisory['advisoryId'] ) ? sanitize_text_field( (string) $advisory['advisoryId'] ) : md5( wp_json_encode( $advisory ) );
					$records[] = array(
						'package'          => sanitize_text_field( (string) $package_name ),
						'ecosystem'        => 'composer',
						'provider_record_id'=> $advisory_id,
						'cve_id'           => ! empty( $advisory['cve'] ) ? strtoupper( sanitize_text_field( (string) $advisory['cve'] ) ) : '',
						'title'            => ! empty( $advisory['title'] ) ? sanitize_text_field( (string) $advisory['title'] ) : sanitize_text_field( (string) $package_name . ' advisory' ),
						'description'      => ! empty( $advisory['link'] ) ? esc_url_raw( (string) $advisory['link'] ) : '',
						'aliases'          => ! empty( $advisory['cve'] ) ? array( strtoupper( sanitize_text_field( (string) $advisory['cve'] ) ) ) : array(),
						'fixed_versions'   => ! empty( $advisory['affectedVersions'] ) ? array( sanitize_text_field( (string) $advisory['affectedVersions'] ) ) : array(),
						'references'       => ! empty( $advisory['link'] ) ? array( esc_url_raw( (string) $advisory['link'] ) ) : array(),
						'manifest'         => self::relative_path( $lock_path ),
						'raw'              => $advisory,
					);
				}
			}
		}
		return array( 'status' => empty( $records ) ? 'empty' : 'success', 'error' => '', 'records' => $records, 'execution' => self::build_execution_summary( 'composer_audit', empty( $records ) ? 'executed' : 'executed', empty( $records ) ? 'composer audit executed with no advisories.' : 'composer audit executed successfully.', isset( $run ) && is_array( $run ) ? $run : array(), array( 'manifest_count' => count( $composer_files ), 'record_count' => count( $records ) ) ) );
	}

	private static function run_pip_audit( array $manifests ) {
		$python_packages = ! empty( $manifests['python_packages'] ) && is_array( $manifests['python_packages'] ) ? $manifests['python_packages'] : array();
		$python_files    = array();
		foreach ( array( 'requirements_files', 'pipfile_lock_files', 'pyproject_files', 'poetry_lock_files' ) as $file_key ) {
			if ( ! empty( $manifests[ $file_key ] ) && is_array( $manifests[ $file_key ] ) ) {
				$python_files = array_merge( $python_files, $manifests[ $file_key ] );
			}
		}
		$python_files = array_values( array_unique( array_filter( $python_files ) ) );
		if ( empty( $python_packages ) || empty( $python_files ) ) {
			return array( 'status' => 'skipped', 'error' => '', 'records' => array(), 'execution' => self::build_execution_summary( 'pip_audit', 'skipped', 'No Python manifests were found on the agent host.', array(), array( 'manifest_count' => count( $python_files ) ) ) );
		}
		if ( ! self::binary_exists( 'pip-audit' ) ) {
			return array( 'status' => 'unavailable', 'error' => 'pip-audit binary unavailable', 'records' => array(), 'execution' => self::build_execution_summary( 'pip_audit', 'unavailable', 'pip-audit binary unavailable', array( 'binary_missing' => true ), array( 'manifest_count' => count( $python_files ) ) ) );
		}
		$requirements = array();
		foreach ( $python_packages as $package ) {
			if ( empty( $package['name'] ) || empty( $package['version'] ) ) {
				continue;
			}
			$requirements[] = sanitize_text_field( (string) $package['name'] ) . '==' . sanitize_text_field( (string) $package['version'] );
		}
		$requirements = array_values( array_unique( $requirements ) );
		if ( empty( $requirements ) ) {
			return array( 'status' => 'empty', 'error' => 'no pinned python packages for pip-audit', 'records' => array(), 'execution' => self::build_execution_summary( 'pip_audit', 'skipped', 'Python manifests were found but no pinned packages were available for pip-audit.', array(), array( 'manifest_count' => count( $python_files ) ) ) );
		}

		$temp_dir = trailingslashit( get_temp_dir() ) . 'asa-pip-audit-' . wp_generate_password( 8, false, false );
		if ( ! wp_mkdir_p( $temp_dir ) ) {
			return array( 'status' => 'error', 'error' => 'unable to create pip audit temp dir', 'records' => array(), 'execution' => self::build_execution_summary( 'pip_audit', 'error', 'Unable to create pip-audit temp directory.', array( 'invalid_environment' => true ), array( 'manifest_count' => count( $python_files ) ) ) );
		}
		$req_path = trailingslashit( $temp_dir ) . 'requirements.txt';
		file_put_contents( $req_path, implode( "\n", $requirements ) . "\n" );
		$run = self::run_command( array( 'pip-audit', '-r', $req_path, '--format=json' ), $temp_dir );
		self::cleanup_temp_dir( $temp_dir );
		if ( empty( $run['stdout'] ) ) {
			if ( 0 !== (int) $run['exit_code'] ) {
				return array( 'status' => 'error', 'error' => ! empty( $run['stderr'] ) ? sanitize_text_field( (string) $run['stderr'] ) : 'pip-audit returned no output', 'records' => array(), 'execution' => self::build_execution_summary( 'pip_audit', 'error', 'pip-audit returned no output', $run, array( 'manifest_count' => count( $python_files ) ) ) );
			}
			return array( 'status' => 'empty', 'error' => '', 'records' => array(), 'execution' => self::build_execution_summary( 'pip_audit', 'executed', 'pip-audit executed with no advisories.', $run, array( 'manifest_count' => count( $python_files ), 'record_count' => 0 ) ) );
		}
		$data = json_decode( (string) $run['stdout'], true );
		if ( ! is_array( $data ) ) {
			return array( 'status' => 'error', 'error' => 'invalid pip-audit json', 'records' => array(), 'execution' => self::build_execution_summary( 'pip_audit', 'error', 'Invalid pip-audit JSON was returned.', $run, array( 'manifest_count' => count( $python_files ) ) ) );
		}
		$records = array();
		foreach ( $data as $entry ) {
			if ( empty( $entry['name'] ) || empty( $entry['vulns'] ) || ! is_array( $entry['vulns'] ) ) {
				continue;
			}
			foreach ( $entry['vulns'] as $vuln ) {
				$aliases = ! empty( $vuln['aliases'] ) && is_array( $vuln['aliases'] ) ? array_map( 'sanitize_text_field', $vuln['aliases'] ) : array();
				$record_id = ! empty( $vuln['id'] ) ? sanitize_text_field( (string) $vuln['id'] ) : md5( wp_json_encode( $vuln ) );
				$records[] = array(
					'package'            => sanitize_text_field( (string) $entry['name'] ),
					'ecosystem'          => 'pypi',
					'provider_record_id' => $record_id,
					'cve_id'             => self::extract_cve_from_aliases( $aliases, ! empty( $vuln['id'] ) ? (string) $vuln['id'] : '' ),
					'title'              => ! empty( $vuln['id'] ) ? sanitize_text_field( (string) $vuln['id'] ) : sanitize_text_field( (string) $entry['name'] . ' advisory' ),
					'description'        => ! empty( $vuln['description'] ) ? wp_strip_all_tags( (string) $vuln['description'] ) : '',
					'aliases'            => $aliases,
					'fixed_versions'     => ! empty( $vuln['fix_versions'] ) && is_array( $vuln['fix_versions'] ) ? array_map( 'sanitize_text_field', $vuln['fix_versions'] ) : array(),
					'references'         => array(),
					'raw'                => $vuln,
				);
			}
		}
		return array( 'status' => empty( $records ) ? 'empty' : 'success', 'error' => '', 'records' => $records, 'execution' => self::build_execution_summary( 'pip_audit', 'executed', empty( $records ) ? 'pip-audit executed with no advisories.' : 'pip-audit executed successfully.', isset( $run ) && is_array( $run ) ? $run : array(), array( 'manifest_count' => count( $python_files ), 'record_count' => count( $records ) ) ) );
	}

	private static function run_node_audit( array $manifests, $package_manager ) {
		$package_manager = sanitize_key( (string) $package_manager );
		$binary_map = array(
			'npm'  => array(
				'binary'       => 'npm',
				'files_key'    => 'npm_files',
				'packages_key' => 'npm_packages',
				'lockfile'     => 'package-lock.json',
				'audit_label'  => 'npm audit',
				'command'      => array( 'npm', 'audit', '--json', '--package-lock-only' ),
			),
			'yarn' => array(
				'binary'       => 'yarn',
				'files_key'    => 'yarn_files',
				'packages_key' => 'yarn_packages',
				'lockfile'     => 'yarn.lock',
				'audit_label'  => 'yarn audit',
				'command'      => array( 'yarn', 'audit', '--json' ),
			),
			'pnpm' => array(
				'binary'       => 'pnpm',
				'files_key'    => 'pnpm_files',
				'packages_key' => 'pnpm_packages',
				'lockfile'     => 'pnpm-lock.yaml',
				'audit_label'  => 'pnpm audit',
				'command'      => array( 'pnpm', 'audit', '--json' ),
			),
		);

		if ( empty( $binary_map[ $package_manager ] ) ) {
			return array( 'status' => 'skipped', 'error' => 'unsupported package manager', 'records' => array(), 'execution' => self::build_execution_summary( $package_manager . '_audit', 'skipped', 'Unsupported package manager.', array(), array() ) );
		}

		$config = $binary_map[ $package_manager ];
		$js_packages = ! empty( $manifests[ $config['packages_key'] ] ) && is_array( $manifests[ $config['packages_key'] ] ) ? $manifests[ $config['packages_key'] ] : array();
		$lock_files  = ! empty( $manifests[ $config['files_key'] ] ) && is_array( $manifests[ $config['files_key'] ] ) ? $manifests[ $config['files_key'] ] : array();

		if ( empty( $js_packages ) || empty( $lock_files ) ) {
			return array( 'status' => 'skipped', 'error' => '', 'records' => array(), 'execution' => self::build_execution_summary( $package_manager . '_audit', 'skipped', sprintf( 'No %s manifests were found on the agent host.', $config['lockfile'] ), array(), array( 'manifest_count' => count( $lock_files ) ) ) );
		}
		if ( ! self::binary_exists( $config['binary'] ) ) {
			return array( 'status' => 'unavailable', 'error' => $config['binary'] . ' binary unavailable', 'records' => array(), 'execution' => self::build_execution_summary( $package_manager . '_audit', 'unavailable', $config['binary'] . ' binary unavailable', array( 'binary_missing' => true ), array( 'manifest_count' => count( $lock_files ) ) ) );
		}

		$records = array();
		$handled_dirs = array();
		$last_run = array();
		foreach ( $lock_files as $lock_path ) {
			$working_dir = dirname( $lock_path );
			if ( isset( $handled_dirs[ $working_dir ] ) ) {
				continue;
			}
			$handled_dirs[ $working_dir ] = true;

			$lock_body = self::read_file( $lock_path );
			if ( '' === $lock_body ) {
				continue;
			}

			$temp_dir = trailingslashit( get_temp_dir() ) . 'asa-' . $package_manager . '-audit-' . wp_generate_password( 8, false, false );
			if ( ! wp_mkdir_p( $temp_dir ) ) {
				return array( 'status' => 'error', 'error' => 'unable to create ' . $package_manager . ' audit temp dir', 'records' => array(), 'execution' => self::build_execution_summary( $package_manager . '_audit', 'error', 'Unable to create ' . $package_manager . ' audit temp directory.', array( 'invalid_environment' => true ), array( 'manifest_count' => count( $lock_files ) ) ) );
			}

			$package_json_path = trailingslashit( $working_dir ) . 'package.json';
			if ( is_readable( $package_json_path ) && is_file( $package_json_path ) ) {
				file_put_contents( trailingslashit( $temp_dir ) . 'package.json', self::read_file( $package_json_path ) );
			} else {
				file_put_contents( trailingslashit( $temp_dir ) . 'package.json', wp_json_encode( array( 'name' => 'aegisify/local-' . $package_manager . '-audit', 'version' => '1.0.0' ) ) );
			}
			file_put_contents( trailingslashit( $temp_dir ) . $config['lockfile'], $lock_body );

			$last_run = self::run_command( $config['command'], $temp_dir );
			self::cleanup_temp_dir( $temp_dir );

			if ( empty( $last_run['stdout'] ) ) {
				if ( 0 !== (int) $last_run['exit_code'] ) {
					return array( 'status' => 'error', 'error' => ! empty( $last_run['stderr'] ) ? sanitize_text_field( (string) $last_run['stderr'] ) : $config['audit_label'] . ' returned no output', 'records' => array(), 'execution' => self::build_execution_summary( $package_manager . '_audit', 'error', $config['audit_label'] . ' returned no output', $last_run, array( 'manifest_count' => count( $lock_files ) ) ) );
				}
				continue;
			}

			if ( 'npm' === $package_manager ) {
				$parsed_records = self::parse_npm_audit_records( (string) $last_run['stdout'], self::relative_path( $lock_path ) );
			} elseif ( 'yarn' === $package_manager ) {
				$parsed_records = self::parse_yarn_audit_records( (string) $last_run['stdout'], self::relative_path( $lock_path ) );
			} else {
				$parsed_records = self::parse_pnpm_audit_records( (string) $last_run['stdout'], self::relative_path( $lock_path ) );
			}

			foreach ( $parsed_records as $record ) {
				$record_key = sanitize_text_field( (string) $record['package'] ) . '|' . sanitize_text_field( (string) $record['provider_record_id'] ) . '|' . sanitize_text_field( (string) $record['manifest'] );
				$records[ $record_key ] = $record;
			}
		}

		return array( 'status' => empty( $records ) ? 'empty' : 'success', 'error' => '', 'records' => array_values( $records ), 'execution' => self::build_execution_summary( $package_manager . '_audit', 'executed', empty( $records ) ? $config['audit_label'] . ' executed with no advisories.' : $config['audit_label'] . ' executed successfully.', is_array( $last_run ) ? $last_run : array(), array( 'manifest_count' => count( $lock_files ), 'record_count' => count( $records ) ) ) );
	}

	private static function parse_npm_audit_records( $stdout, $manifest ) {
		$data = json_decode( (string) $stdout, true );
		if ( ! is_array( $data ) ) {
			return array();
		}

		$records = array();
		$via_entries = array();
		if ( ! empty( $data['vulnerabilities'] ) && is_array( $data['vulnerabilities'] ) ) {
			foreach ( $data['vulnerabilities'] as $package_name => $entry ) {
				if ( empty( $entry['via'] ) || ! is_array( $entry['via'] ) ) {
					continue;
				}
				foreach ( $entry['via'] as $via ) {
					if ( is_array( $via ) ) {
						$via['package'] = $package_name;
						$via_entries[] = $via;
					}
				}
			}
		} elseif ( ! empty( $data['advisories'] ) && is_array( $data['advisories'] ) ) {
			foreach ( $data['advisories'] as $advisory ) {
				if ( is_array( $advisory ) ) {
					$via_entries[] = $advisory;
				}
			}
		}

		foreach ( $via_entries as $advisory ) {
			$package = ! empty( $advisory['package'] ) ? sanitize_text_field( (string) $advisory['package'] ) : '';
			if ( '' === $package ) {
				continue;
			}
			$aliases = ! empty( $advisory['cves'] ) && is_array( $advisory['cves'] ) ? array_map( 'sanitize_text_field', $advisory['cves'] ) : array();
			if ( ! empty( $advisory['url'] ) ) {
				$aliases[] = sanitize_text_field( (string) $advisory['url'] );
			}
			$cve_id = self::extract_cve_from_aliases( $aliases, ! empty( $advisory['source'] ) ? (string) $advisory['source'] : '' );
			$record_id = ! empty( $advisory['source'] ) ? sanitize_text_field( (string) $advisory['source'] ) : md5( wp_json_encode( $advisory ) );
			$fixed_versions = array();
			if ( ! empty( $advisory['fixAvailable']['name'] ) ) {
				$fixed_versions[] = sanitize_text_field( (string) $advisory['fixAvailable']['name'] );
			} elseif ( ! empty( $advisory['range'] ) ) {
				$fixed_versions[] = sanitize_text_field( (string) $advisory['range'] );
			}
			$records[] = array(
				'package'            => $package,
				'ecosystem'          => 'npm',
				'provider_record_id' => $record_id,
				'cve_id'             => $cve_id,
				'title'              => ! empty( $advisory['title'] ) ? sanitize_text_field( (string) $advisory['title'] ) : sanitize_text_field( $package . ' advisory' ),
				'description'        => ! empty( $advisory['overview'] ) ? wp_strip_all_tags( (string) $advisory['overview'] ) : '',
				'aliases'            => array_values( array_unique( array_filter( $aliases ) ) ),
				'fixed_versions'     => array_values( array_unique( array_filter( $fixed_versions ) ) ),
				'references'         => ! empty( $advisory['url'] ) ? array( esc_url_raw( (string) $advisory['url'] ) ) : array(),
				'manifest'           => sanitize_text_field( (string) $manifest ),
				'raw'                => $advisory,
			);
		}
		return $records;
	}

	private static function parse_yarn_audit_records( $stdout, $manifest ) {
		$records = array();
		$advisories = array();
		$lines = preg_split( '/\r\n|\r|\n/', (string) $stdout );
		foreach ( (array) $lines as $line ) {
			$line = trim( (string) $line );
			if ( '' === $line ) {
				continue;
			}
			$decoded = json_decode( $line, true );
			if ( ! is_array( $decoded ) ) {
				continue;
			}
			if ( ! empty( $decoded['type'] ) && 'auditAdvisory' === $decoded['type'] && ! empty( $decoded['data']['advisory'] ) && is_array( $decoded['data']['advisory'] ) ) {
				$advisories[] = $decoded['data']['advisory'];
			} elseif ( ! empty( $decoded['advisories'] ) && is_array( $decoded['advisories'] ) ) {
				foreach ( $decoded['advisories'] as $advisory ) {
					if ( is_array( $advisory ) ) {
						$advisories[] = $advisory;
					}
				}
			}
		}
		foreach ( $advisories as $advisory ) {
			$package = ! empty( $advisory['module_name'] ) ? sanitize_text_field( (string) $advisory['module_name'] ) : ( ! empty( $advisory['package'] ) ? sanitize_text_field( (string) $advisory['package'] ) : '' );
			if ( '' === $package ) {
				continue;
			}
			$record_id = ! empty( $advisory['id'] ) ? sanitize_text_field( (string) $advisory['id'] ) : md5( wp_json_encode( $advisory ) );
			$aliases = ! empty( $advisory['cves'] ) && is_array( $advisory['cves'] ) ? array_map( 'sanitize_text_field', $advisory['cves'] ) : array();
			$cve_id = self::extract_cve_from_aliases( $aliases, $record_id );
			$records[] = array(
				'package'            => $package,
				'ecosystem'          => 'npm',
				'provider_record_id' => $record_id,
				'cve_id'             => $cve_id,
				'title'              => ! empty( $advisory['title'] ) ? sanitize_text_field( (string) $advisory['title'] ) : sanitize_text_field( $package . ' advisory' ),
				'description'        => ! empty( $advisory['overview'] ) ? wp_strip_all_tags( (string) $advisory['overview'] ) : '',
				'aliases'            => $aliases,
				'fixed_versions'     => ! empty( $advisory['patched_versions'] ) ? array( sanitize_text_field( (string) $advisory['patched_versions'] ) ) : array(),
				'references'         => ! empty( $advisory['url'] ) ? array( esc_url_raw( (string) $advisory['url'] ) ) : array(),
				'manifest'           => sanitize_text_field( (string) $manifest ),
				'raw'                => $advisory,
			);
		}
		return $records;
	}

	private static function parse_pnpm_audit_records( $stdout, $manifest ) {
		$data = json_decode( (string) $stdout, true );
		if ( ! is_array( $data ) ) {
			return array();
		}
		if ( ! empty( $data['advisories'] ) && is_array( $data['advisories'] ) ) {
			return self::parse_npm_audit_records( $stdout, $manifest );
		}

		$records = array();
		foreach ( (array) ( isset( $data['vulnerabilities'] ) ? $data['vulnerabilities'] : array() ) as $package => $entry ) {
			if ( empty( $entry['via'] ) || ! is_array( $entry['via'] ) ) {
				continue;
			}
			foreach ( $entry['via'] as $via ) {
				if ( ! is_array( $via ) ) {
					continue;
				}
				$record_id = ! empty( $via['source'] ) ? sanitize_text_field( (string) $via['source'] ) : md5( wp_json_encode( $via ) );
				$aliases = ! empty( $via['cves'] ) && is_array( $via['cves'] ) ? array_map( 'sanitize_text_field', $via['cves'] ) : array();
				$records[] = array(
					'package'            => sanitize_text_field( (string) $package ),
					'ecosystem'          => 'npm',
					'provider_record_id' => $record_id,
					'cve_id'             => self::extract_cve_from_aliases( $aliases, $record_id ),
					'title'              => ! empty( $via['title'] ) ? sanitize_text_field( (string) $via['title'] ) : sanitize_text_field( (string) $package . ' advisory' ),
					'description'        => ! empty( $via['overview'] ) ? wp_strip_all_tags( (string) $via['overview'] ) : '',
					'aliases'            => $aliases,
					'fixed_versions'     => ! empty( $via['range'] ) ? array( sanitize_text_field( (string) $via['range'] ) ) : array(),
					'references'         => ! empty( $via['url'] ) ? array( esc_url_raw( (string) $via['url'] ) ) : array(),
					'manifest'           => sanitize_text_field( (string) $manifest ),
					'raw'                => $via,
				);
			}
		}
		return $records;
	}

	private static function merge_package_lists( array $lists ) {
		$packages = array();
		foreach ( $lists as $list ) {
			foreach ( (array) $list as $package ) {
				if ( ! is_array( $package ) || empty( $package['name'] ) ) {
					continue;
				}
				$key = sanitize_text_field( (string) $package['name'] ) . '|' . sanitize_text_field( (string) ( isset( $package['version'] ) ? $package['version'] : '' ) ) . '|' . sanitize_key( (string) ( isset( $package['package_manager'] ) ? $package['package_manager'] : '' ) );
				$packages[ $key ] = $package;
			}
		}
		return array_values( $packages );
	}

	private static function find_manifest_files( array $filenames, $max_files ) {
		$roots = self::scan_roots();
		$found = array();
		$filename_lookup = array();
		foreach ( $filenames as $filename ) {
			$filename_lookup[ strtolower( (string) $filename ) ] = true;
		}
		foreach ( $roots as $root ) {
			self::scan_directory_for_manifests( $root, 0, $filename_lookup, $found, absint( $max_files ) );
			if ( count( $found ) >= $max_files ) {
				break;
			}
		}
		return array_values( array_unique( $found ) );
	}

	private static function scan_roots() {
		$roots = array(
			ABSPATH,
			defined( 'WP_CONTENT_DIR' ) ? WP_CONTENT_DIR : '',
			defined( 'WP_PLUGIN_DIR' ) ? WP_PLUGIN_DIR : '',
			defined( 'WPMU_PLUGIN_DIR' ) ? WPMU_PLUGIN_DIR : '',
		);
		if ( function_exists( 'get_theme_roots' ) ) {
			foreach ( (array) get_theme_roots() as $theme_root ) {
				$roots[] = $theme_root;
			}
		}
		$unique = array();
		foreach ( $roots as $root ) {
			$root = wp_normalize_path( (string) $root );
			if ( '' !== $root && is_dir( $root ) ) {
				$unique[ $root ] = $root;
			}
		}
		return array_values( $unique );
	}

	private static function scan_directory_for_manifests( $directory, $depth, array $filename_lookup, array &$found, $max_files ) {
		if ( count( $found ) >= $max_files ) {
			return;
		}
		$directory = wp_normalize_path( (string) $directory );
		if ( ! is_dir( $directory ) || ! is_readable( $directory ) ) {
			return;
		}
		$entries = @scandir( $directory );
		if ( ! is_array( $entries ) ) {
			return;
		}
		foreach ( $entries as $entry ) {
			if ( '.' === $entry || '..' === $entry ) {
				continue;
			}
			$path = wp_normalize_path( trailingslashit( $directory ) . $entry );
			if ( is_dir( $path ) ) {
				if ( $depth >= self::SEARCH_MAX_DEPTH || self::should_skip_directory( $entry ) ) {
					continue;
				}
				self::scan_directory_for_manifests( $path, $depth + 1, $filename_lookup, $found, $max_files );
				if ( count( $found ) >= $max_files ) {
					return;
				}
				continue;
			}
			if ( isset( $filename_lookup[ strtolower( (string) $entry ) ] ) ) {
				$found[] = $path;
				if ( count( $found ) >= $max_files ) {
					return;
				}
			}
		}
	}

	private static function should_skip_directory( $dirname ) {
		$dirname = strtolower( (string) $dirname );
		return in_array( $dirname, array( '.git', '.svn', 'node_modules', 'vendor', 'uploads', 'cache', 'upgrade', 'languages', 'wflogs', 'backups', 'ai1wm-backups' ), true );
	}

	private static function plugin_slug_from_file( $plugin_file, $textdomain ) {
		$textdomain = sanitize_key( (string) $textdomain );
		if ( '' !== $textdomain ) {
			return $textdomain;
		}
		$plugin_file = str_replace( '\\', '/', (string) $plugin_file );
		$parts       = array_values( array_filter( explode( '/', $plugin_file ) ) );
		if ( ! empty( $parts[0] ) ) {
			return sanitize_key( (string) $parts[0] );
		}
		return sanitize_key( preg_replace( '/\.php$/i', '', basename( $plugin_file ) ) );
	}

	private static function test_loopback() {
		$url = home_url( '/' );
		ASA_Logger::log( 'network', 'info', 'local_inventory', 'test_loopback', __( 'The local inventory module started the site loopback test.', 'aegisify-audit-agent' ), array( 'target' => $url ), 10 );
		$response = wp_remote_get(
			$url,
			array(
				'timeout'     => 10,
				'redirection' => 3,
				'sslverify'   => true,
			)
		);
		if ( is_wp_error( $response ) ) {
			ASA_Logger::log( 'network', 'error', 'local_inventory', 'test_loopback', __( 'The local inventory module loopback test failed.', 'aegisify-audit-agent' ), array( 'target' => $url, 'message' => $response->get_error_message() ), 10 );
			return array(
				'status' => 'failed',
				'code'   => 0,
			);
		}
		$code   = (int) wp_remote_retrieve_response_code( $response );
		$status = ( $code >= 200 && $code < 500 ) ? 'ok' : 'failed';
		ASA_Logger::log( 'network', 'ok' === $status ? 'success' : 'error', 'local_inventory', 'test_loopback', 'ok' === $status ? __( 'The local inventory module loopback test completed successfully.', 'aegisify-audit-agent' ) : __( 'The local inventory module loopback test returned an unexpected status code.', 'aegisify-audit-agent' ), array( 'target' => $url, 'http_code' => $code ), 10 );
		return array(
			'status' => $status,
			'code'   => $code,
		);
	}

	private static function db_engine_from_version_string( $version ) {
		return false !== stripos( (string) $version, 'mariadb' ) ? 'MariaDB' : 'MySQL';
	}

	private static function read_file( $path ) {
		$path = (string) $path;
		if ( '' === $path || ! is_readable( $path ) || ! is_file( $path ) ) {
			return '';
		}
		$contents = file_get_contents( $path );
		return is_string( $contents ) ? $contents : '';
	}

	private static function relative_path( $path ) {
		$path = wp_normalize_path( (string) $path );
		$roots = self::scan_roots();
		usort(
			$roots,
			static function( $a, $b ) {
				return strlen( $b ) - strlen( $a );
			}
		);
		foreach ( $roots as $root ) {
			$root = trailingslashit( wp_normalize_path( $root ) );
			if ( 0 === strpos( $path, $root ) ) {
				return ltrim( substr( $path, strlen( $root ) ), '/' );
			}
		}
		return basename( $path );
	}

	private static function parse_composer_lock( $body, $manifest ) {
		$data     = json_decode( (string) $body, true );
		$packages = array();
		if ( ! is_array( $data ) ) {
			return $packages;
		}
		foreach ( array( 'packages', 'packages-dev' ) as $bucket ) {
			if ( empty( $data[ $bucket ] ) || ! is_array( $data[ $bucket ] ) ) {
				continue;
			}
			foreach ( $data[ $bucket ] as $package ) {
				if ( empty( $package['name'] ) ) {
					continue;
				}
				$packages[] = array(
					'name'      => sanitize_text_field( (string) $package['name'] ),
					'version'   => ! empty( $package['version'] ) ? sanitize_text_field( (string) $package['version'] ) : '',
					'manifest'  => sanitize_text_field( (string) $manifest ),
					'dev'       => 'packages-dev' === $bucket,
				);
			}
		}
		return $packages;
	}

	private static function parse_requirements_txt( $body, $manifest ) {
		$packages = array();
		foreach ( preg_split( '/\r\n|\r|\n/', (string) $body ) as $line ) {
			$line = trim( preg_replace( '/\s+#.*$/', '', (string) $line ) );
			if ( '' === $line || 0 === strpos( $line, '#' ) ) {
				continue;
			}
			if ( preg_match( '/^([A-Za-z0-9_.\-]+)\s*(?:==|>=|<=|~=|!=|>|<)?\s*([A-Za-z0-9\.\-_]+)?/', $line, $matches ) ) {
				$packages[] = array(
					'name'     => sanitize_text_field( $matches[1] ),
					'version'  => ! empty( $matches[2] ) ? sanitize_text_field( $matches[2] ) : '',
					'manifest' => sanitize_text_field( (string) $manifest ),
				);
			}
		}
		return $packages;
	}

	private static function parse_pyproject_toml( $body, $manifest ) {
		$packages = array();
		$in_deps  = false;
		foreach ( preg_split( '/\r\n|\r|\n/', (string) $body ) as $line ) {
			$line = trim( (string) $line );
			if ( preg_match( '/^\[(project\.dependencies|tool\.poetry\.dependencies)\]/', $line ) ) {
				$in_deps = true;
				continue;
			}
			if ( $in_deps && preg_match( '/^\[.+\]$/', $line ) ) {
				$in_deps = false;
			}
			if ( ! $in_deps || '' === $line || 0 === strpos( $line, '#' ) ) {
				continue;
			}
			if ( preg_match( '/^([A-Za-z0-9_.\-]+)\s*=\s*["\']?([^"\']+)["\']?/', $line, $matches ) ) {
				$name = sanitize_text_field( $matches[1] );
				if ( in_array( strtolower( $name ), array( 'python', 'requires-python' ), true ) ) {
					continue;
				}
				$packages[] = array(
					'name'     => $name,
					'version'  => sanitize_text_field( preg_replace( '/^[\^~<>=! ]+/', '', $matches[2] ) ),
					'manifest' => sanitize_text_field( (string) $manifest ),
				);
			}
		}
		return $packages;
	}

	private static function parse_poetry_lock( $body, $manifest ) {
		$packages = array();
		$current  = array();
		foreach ( preg_split( '/\r\n|\r|\n/', (string) $body ) as $line ) {
			$line = trim( (string) $line );
			if ( '[[package]]' === $line ) {
				if ( ! empty( $current['name'] ) ) {
					$packages[] = array(
						'name'     => sanitize_text_field( (string) $current['name'] ),
						'version'  => ! empty( $current['version'] ) ? sanitize_text_field( (string) $current['version'] ) : '',
						'manifest' => sanitize_text_field( (string) $manifest ),
					);
				}
				$current = array();
				continue;
			}
			if ( preg_match( '/^(name|version)\s*=\s*["\']([^"\']+)["\']/', $line, $matches ) ) {
				$current[ $matches[1] ] = $matches[2];
			}
		}
		if ( ! empty( $current['name'] ) ) {
			$packages[] = array(
				'name'     => sanitize_text_field( (string) $current['name'] ),
				'version'  => ! empty( $current['version'] ) ? sanitize_text_field( (string) $current['version'] ) : '',
				'manifest' => sanitize_text_field( (string) $manifest ),
			);
		}
		return $packages;
	}

	private static function parse_pipfile_lock( $body, $manifest ) {
		$data     = json_decode( (string) $body, true );
		$packages = array();
		if ( ! is_array( $data ) ) {
			return $packages;
		}
		foreach ( array( 'default', 'develop' ) as $bucket ) {
			if ( empty( $data[ $bucket ] ) || ! is_array( $data[ $bucket ] ) ) {
				continue;
			}
			foreach ( $data[ $bucket ] as $name => $info ) {
				$version = '';
				if ( is_array( $info ) && ! empty( $info['version'] ) ) {
					$version = sanitize_text_field( preg_replace( '/^[=<>!~ ]+/', '', (string) $info['version'] ) );
				}
				$packages[] = array(
					'name'     => sanitize_text_field( (string) $name ),
					'version'  => $version,
					'manifest' => sanitize_text_field( (string) $manifest ),
				);
			}
		}
		return $packages;
	}

	private static function parse_package_lock( $body, $manifest ) {
		$data     = json_decode( (string) $body, true );
		$packages = array();
		if ( ! is_array( $data ) ) {
			return $packages;
		}
		if ( ! empty( $data['packages'] ) && is_array( $data['packages'] ) ) {
			foreach ( $data['packages'] as $path => $info ) {
				if ( ! is_array( $info ) || empty( $info['version'] ) || '' === (string) $path ) {
					continue;
				}
				$name = basename( (string) $path );
				if ( false !== strpos( (string) $path, 'node_modules/' ) ) {
					$name = basename( dirname( (string) $path ) ) === 'node_modules' ? basename( (string) $path ) : preg_replace( '#^node_modules/#', '', (string) $path );
				}
				$packages[] = array(
					'name'     => sanitize_text_field( (string) $name ),
					'version'  => sanitize_text_field( (string) $info['version'] ),
					'manifest' => sanitize_text_field( (string) $manifest ),
				);
			}
		} elseif ( ! empty( $data['dependencies'] ) && is_array( $data['dependencies'] ) ) {
			foreach ( $data['dependencies'] as $name => $info ) {
				if ( ! is_array( $info ) ) {
					continue;
				}
				$packages[] = array(
					'name'     => sanitize_text_field( (string) $name ),
					'version'  => ! empty( $info['version'] ) ? sanitize_text_field( (string) $info['version'] ) : '',
					'manifest' => sanitize_text_field( (string) $manifest ),
				);
			}
		}
		return $packages;
	}

	private static function parse_yarn_lock( $body, $manifest ) {
		$packages     = array();
		$current_name = '';
		foreach ( preg_split( '/\r\n|\r|\n/', (string) $body ) as $line ) {
			$trimmed = trim( (string) $line );
			if ( preg_match( '/^([@A-Za-z0-9_\/.\-]+)@[^:]+:$/', $trimmed, $matches ) ) {
				$current_name = sanitize_text_field( $matches[1] );
				continue;
			}
			if ( '' !== $current_name && preg_match( '/^version\s+["\']([^"\']+)["\']$/', $trimmed, $matches ) ) {
				$packages[] = array(
					'name'     => $current_name,
					'version'  => sanitize_text_field( $matches[1] ),
					'manifest' => sanitize_text_field( (string) $manifest ),
				);
				$current_name = '';
			}
		}
		return $packages;
	}

	private static function parse_pnpm_lock( $body, $manifest ) {
		$packages = array();
		foreach ( preg_split( '/\r\n|\r|\n/', (string) $body ) as $line ) {
			$trimmed = trim( (string) $line );
			if ( preg_match( '#^/((?:@[^/]+/)?[^@]+)@([^:]+):$#', $trimmed, $matches ) ) {
				$packages[] = array(
					'name'     => sanitize_text_field( $matches[1] ),
					'version'  => sanitize_text_field( $matches[2] ),
					'manifest' => sanitize_text_field( (string) $manifest ),
				);
			}
		}
		return $packages;
	}

	private static function extract_cve_from_aliases( array $aliases, $fallback_id ) {
		foreach ( $aliases as $alias ) {
			$alias = strtoupper( sanitize_text_field( (string) $alias ) );
			if ( preg_match( '/^CVE-\d{4}-\d+$/', $alias ) ) {
				return $alias;
			}
		}
		$fallback_id = strtoupper( sanitize_text_field( (string) $fallback_id ) );
		return preg_match( '/^CVE-\d{4}-\d+$/', $fallback_id ) ? $fallback_id : '';
	}

	private static function binary_exists( $binary ) {
		$binary = sanitize_text_field( (string) $binary );
		if ( '' === $binary || ! function_exists( 'proc_open' ) ) {
			return false;
		}
		$descriptors = array(
			0 => array( 'pipe', 'r' ),
			1 => array( 'pipe', 'w' ),
			2 => array( 'pipe', 'w' ),
		);
		// phpcs:ignore Generic.PHP.ForbiddenFunctions.Found -- Agent host capability detection needs a subprocess to check for local binaries.
		$process = @proc_open( 'command -v ' . escapeshellarg( $binary ), $descriptors, $pipes );
		if ( ! is_resource( $process ) ) {
			return false;
		}
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing process pipes is required for subprocess cleanup and is not a filesystem write operation.
		fclose( $pipes[0] );
		$stdout = stream_get_contents( $pipes[1] );
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing process pipes is required for subprocess cleanup and is not a filesystem write operation.
		fclose( $pipes[1] );
		$stderr = stream_get_contents( $pipes[2] );
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing process pipes is required for subprocess cleanup and is not a filesystem write operation.
		fclose( $pipes[2] );
		$exit_code = proc_close( $process );
		return 0 === (int) $exit_code && '' !== trim( (string) $stdout ) && '' === trim( (string) $stderr );
	}

	private static function run_command( array $command, $cwd ) {
		if ( ! function_exists( 'proc_open' ) || empty( $command ) ) {
			return array( 'exit_code' => 1, 'stdout' => '', 'stderr' => 'proc_open unavailable' );
		}
		$command_line = array();
		foreach ( $command as $part ) {
			$command_line[] = escapeshellarg( (string) $part );
		}
		$descriptors = array(
			0 => array( 'pipe', 'r' ),
			1 => array( 'pipe', 'w' ),
			2 => array( 'pipe', 'w' ),
		);
		$environment = self::build_subprocess_environment( $cwd );

		// phpcs:ignore Generic.PHP.ForbiddenFunctions.Found -- Agent-local scanners need subprocess execution for binary invocations.
		$process = @proc_open( implode( ' ', $command_line ), $descriptors, $pipes, $cwd, $environment );
		if ( ! is_resource( $process ) ) {
			return array( 'exit_code' => 1, 'stdout' => '', 'stderr' => 'unable to spawn process' );
		}
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing process pipes is required for subprocess cleanup and is not a filesystem write operation.
		fclose( $pipes[0] );
		$stdout = stream_get_contents( $pipes[1] );
		$stderr = stream_get_contents( $pipes[2] );
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing process pipes is required for subprocess cleanup and is not a filesystem write operation.
		fclose( $pipes[1] );
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing process pipes is required for subprocess cleanup and is not a filesystem write operation.
		fclose( $pipes[2] );
		$exit_code = proc_close( $process );
		return array(
			'exit_code' => (int) $exit_code,
			'stdout'    => is_string( $stdout ) ? $stdout : '',
			'stderr'    => is_string( $stderr ) ? $stderr : '',
		);
	}

	private static function build_subprocess_environment( $cwd ) {
		$cwd      = (string) $cwd;
		$home_dir = '';

		foreach ( array( 'HOME', 'USERPROFILE' ) as $env_key ) {
			$value = getenv( $env_key );
			if ( is_string( $value ) && '' !== trim( $value ) ) {
				$home_dir = trim( $value );
				break;
			}
		}

		if ( '' === $home_dir ) {
			$candidates = array_filter( array( $cwd, ABSPATH, WP_CONTENT_DIR, get_temp_dir() ) );
			foreach ( $candidates as $candidate ) {
				$candidate = wp_normalize_path( (string) $candidate );
				if ( '' !== $candidate && is_dir( $candidate ) && wp_is_writable( $candidate ) ) {
					$home_dir = $candidate;
					break;
				}
			}
		}

		if ( '' === $home_dir ) {
			$home_dir = wp_normalize_path( get_temp_dir() );
		}

		$composer_home = trailingslashit( $home_dir ) . '.composer';
		if ( ! is_dir( $composer_home ) ) {
			wp_mkdir_p( $composer_home );
		}

		return array(
			'HOME'                   => $home_dir,
			'USERPROFILE'            => $home_dir,
			'COMPOSER_HOME'          => $composer_home,
			'COMPOSER_ALLOW_SUPERUSER' => '1',
			'TMPDIR'                 => get_temp_dir(),
		);
	}

	private static function ensure_wp_file_api() {
		if ( ! function_exists( 'wp_delete_file' ) || ! function_exists( 'WP_Filesystem' ) ) {
			require_once ABSPATH . 'wp-admin/includes/file.php';
		}
	}

	private static function delete_file( $path ) {
		$path = wp_normalize_path( (string) $path );
		if ( '' === $path || ! file_exists( $path ) || ! is_file( $path ) ) {
			return;
		}
		self::ensure_wp_file_api();
		wp_delete_file( $path );
	}

	private static function delete_directory( $dir ) {
		$dir = wp_normalize_path( (string) $dir );
		if ( '' === $dir || ! is_dir( $dir ) ) {
			return;
		}
		self::ensure_wp_file_api();
		global $wp_filesystem;
		WP_Filesystem();
		if ( isset( $wp_filesystem ) && is_object( $wp_filesystem ) && method_exists( $wp_filesystem, 'rmdir' ) ) {
			$wp_filesystem->rmdir( $dir, true );
			return;
		}
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- Narrow fallback when the WordPress filesystem API is unavailable.
		@rmdir( $dir );
	}

	private static function cleanup_temp_dir( $dir ) {
		$dir = (string) $dir;
		if ( '' === $dir || ! is_dir( $dir ) ) {
			return;
		}
		$entries = @scandir( $dir );
		if ( is_array( $entries ) ) {
			foreach ( $entries as $entry ) {
				if ( '.' === $entry || '..' === $entry ) {
					continue;
				}
				$path = trailingslashit( $dir ) . $entry;
				if ( is_dir( $path ) ) {
					self::cleanup_temp_dir( $path );
				} elseif ( file_exists( $path ) ) {
					self::delete_file( $path );
				}
			}
		}
		self::delete_directory( $dir );
	}
}
