<?php
/**
 * 247ChatMate Remote Updater - manifest status and package verification scaffold.
 *
 * REMOTEUPDATERCLIENTMANIFESTCHECK1LOCK:
 * Isolated updater client file. This checkpoint reads and validates a remote
 * manifest, caches a safe status result, and optionally shows a small admin
 * notice on the 247ChatMate admin page.
 *
 * REMOTEUPDATERPACKAGEVERIFY1LOCK:
 * Adds package verification helpers only. This checkpoint can download a
 * manifest-declared package to a temporary WordPress file, compare its SHA-256
 * checksum, and inspect the zip structure for 247chatmate/247chatmate.php. It
 * still does not install updates, hook into the WordPress updater, alter
 * licence/trial/agency state, or send sensitive site data to the update server.
 *
 * REMOTEUPDATERWORDPRESSUPDATEHOOK1LOCK:
 * Connects the read-only manifest and package verification layer to the
 * standard WordPress plugin update flow. Updates are advertised only when the
 * manifest is valid, the installed plugin basename is safe for a standard
 * replacement, and automatic updates remain explicitly disabled. Package
 * download is intercepted and SHA-256/zip-structure verified before WordPress
 * is allowed to use the package. No custom installer is added.
 *
 * REMOTEUPDATERREQUIREMENTGATEFIX1LOCK:
 * Enforces minimum WordPress and PHP requirements before an update offer is
 * added to WordPress and again immediately before a package is downloaded.
 * This keeps requirement failures fail-closed even if WordPress has cached a
 * previous update offer.
 *
 * REMOTEUPDATERNOSAMEVERSIONOFFER1LOCK:
 * Adds a read-time update transient cleanup and explicit version guard so
 * WordPress cannot keep showing a stale 247ChatMate update offer when the
 * installed version is equal to or newer than the manifest latest_version.
 *
 * PLUGINLOGOBRANDING1LOCK:
 * Adds safe local 247ChatMate icon and banner asset URLs to the private
 * WordPress update offer and plugin information modal. This is branding-only
 * and does not change update security, package verification, licence, trial,
 * agency, PDF, widget, chip, WhatsApp, lead routing, or save logic.
 *
 * PLUGINLOGODETAILSMODALFIX1LOCK:
 * Strengthens private-plugin branding metadata for WordPress' update/details
 * UI by adding default icon/banner fallbacks and optional trusted remote
 * manifest icon/banner URLs. This remains visual metadata only and does not
 * alter checksum, package verification, requirement gates, automatic updates,
 * licence, trial, agency, PDF, widget, chips, WhatsApp, leads, or save logic.
 *
 * @package 247ChatMate
 */

defined( 'ABSPATH' ) || exit;

if ( ! defined( 'MATEWA_REMOTE_UPDATER_MANIFEST_URL' ) ) {
	define( 'MATEWA_REMOTE_UPDATER_MANIFEST_URL', 'https://247trademate.pro/247chatmate/update-manifest.json' );
}

if ( ! defined( 'MATEWA_REMOTE_UPDATER_CACHE_KEY' ) ) {
	define( 'MATEWA_REMOTE_UPDATER_CACHE_KEY', 'matewa_remote_updater_manifest_status' );
}

if ( ! defined( 'MATEWA_REMOTE_UPDATER_CACHE_TTL' ) ) {
	define( 'MATEWA_REMOTE_UPDATER_CACHE_TTL', 12 * HOUR_IN_SECONDS );
}


if ( ! defined( 'MATEWA_REMOTE_UPDATER_EXPECTED_PLUGIN_FILE' ) ) {
	define( 'MATEWA_REMOTE_UPDATER_EXPECTED_PLUGIN_FILE', '247chatmate/247chatmate.php' );
}

if ( ! defined( 'MATEWA_REMOTE_UPDATER_PLUGIN_SLUG' ) ) {
	define( 'MATEWA_REMOTE_UPDATER_PLUGIN_SLUG', '247chatmate' );
}

if ( ! defined( 'MATEWA_REMOTE_UPDATER_PLUGIN_NAME' ) ) {
	define( 'MATEWA_REMOTE_UPDATER_PLUGIN_NAME', '247ChatMate' );
}

if ( ! defined( 'MATEWA_REMOTE_UPDATER_PLUGIN_HOMEPAGE' ) ) {
	define( 'MATEWA_REMOTE_UPDATER_PLUGIN_HOMEPAGE', 'https://247trademate.pro' );
}


if ( ! function_exists( 'matewa_remote_updater_asset_url' ) ) {
	/**
	 * Build a local plugin asset URL for update UI branding.
	 *
	 * @param string $relative_path Asset path relative to the plugin root.
	 * @return string
	 */
	function matewa_remote_updater_asset_url( $relative_path ) {
		$relative_path = ltrim( (string) $relative_path, '/' );
		$base_url      = defined( 'MATEWA_PLUGIN_URL' ) ? MATEWA_PLUGIN_URL : '';

		if ( '' === $base_url && defined( 'MATEWA_MAIN_FILE' ) ) {
			$base_url = plugin_dir_url( MATEWA_MAIN_FILE );
		}

		if ( '' === $base_url ) {
			return '';
		}

		return esc_url_raw( trailingslashit( $base_url ) . $relative_path );
	}
}

if ( ! function_exists( 'matewa_remote_updater_sanitise_asset_map' ) ) {
	/**
	 * Sanitise optional icon/banner URL maps from the remote manifest.
	 *
	 * @param mixed $assets Raw asset map.
	 * @param array<int,string> $allowed_keys Allowed map keys.
	 * @return array<string,string>
	 */
	function matewa_remote_updater_sanitise_asset_map( $assets, $allowed_keys ) {
		if ( ! is_array( $assets ) ) {
			return array();
		}

		$clean = array();

		foreach ( $allowed_keys as $key ) {
			if ( ! isset( $assets[ $key ] ) || ! is_scalar( $assets[ $key ] ) ) {
				continue;
			}

			$url = esc_url_raw( (string) $assets[ $key ] );

			if ( '' === $url || ! matewa_remote_updater_is_https_url( $url ) || ! matewa_remote_updater_allowed_host( $url ) ) {
				continue;
			}

			$clean[ $key ] = $url;
		}

		return $clean;
	}
}

if ( ! function_exists( 'matewa_remote_updater_icon_urls' ) ) {
	/**
	 * Return icon URLs for WordPress private-plugin update screens.
	 *
	 * @param array<string,mixed> $status Optional updater status/manifest data.
	 * @return array<string,string>
	 */
	function matewa_remote_updater_icon_urls( $status = array() ) {
		if ( is_array( $status ) && ! empty( $status['icon_urls'] ) && is_array( $status['icon_urls'] ) ) {
			$remote_icons = matewa_remote_updater_sanitise_asset_map(
				$status['icon_urls'],
				array( 'svg', '2x', '1x', 'default', '128x128', '256x256' )
			);

			if ( ! empty( $remote_icons ) ) {
				return $remote_icons;
			}
		}

		$icon_128 = matewa_remote_updater_asset_url( 'assets/icon-128x128.png' );
		$icon_256 = matewa_remote_updater_asset_url( 'assets/icon-256x256.png' );

		$icons = array();

		if ( '' !== $icon_128 ) {
			$icons['1x']      = $icon_128;
			$icons['128x128'] = $icon_128;
		}

		if ( '' !== $icon_256 ) {
			$icons['2x']      = $icon_256;
			$icons['256x256'] = $icon_256;
			$icons['default'] = $icon_256;
		} elseif ( '' !== $icon_128 ) {
			$icons['default'] = $icon_128;
		}

		return $icons;
	}
}

if ( ! function_exists( 'matewa_remote_updater_banner_urls' ) ) {
	/**
	 * Return banner URLs for the WordPress plugin information modal.
	 *
	 * @param array<string,mixed> $status Optional updater status/manifest data.
	 * @return array<string,string>
	 */
	function matewa_remote_updater_banner_urls( $status = array() ) {
		if ( is_array( $status ) && ! empty( $status['banner_urls'] ) && is_array( $status['banner_urls'] ) ) {
			$remote_banners = matewa_remote_updater_sanitise_asset_map(
				$status['banner_urls'],
				array( 'high', 'low', 'default' )
			);

			if ( ! empty( $remote_banners ) ) {
				return $remote_banners;
			}
		}

		$banner_low  = matewa_remote_updater_asset_url( 'assets/banner-772x250.png' );
		$banner_high = matewa_remote_updater_asset_url( 'assets/banner-1544x500.png' );

		$banners = array();

		if ( '' !== $banner_low ) {
			$banners['low']     = $banner_low;
			$banners['default'] = $banner_low;
		}

		if ( '' !== $banner_high ) {
			$banners['high'] = $banner_high;
		}

		return $banners;
	}
}

if ( ! defined( 'MATEWA_REMOTE_UPDATER_PACKAGE_TIMEOUT' ) ) {
	define( 'MATEWA_REMOTE_UPDATER_PACKAGE_TIMEOUT', 20 );
}

if ( ! defined( 'MATEWA_REMOTE_UPDATER_PACKAGE_MAX_BYTES' ) ) {
	define( 'MATEWA_REMOTE_UPDATER_PACKAGE_MAX_BYTES', 50 * 1024 * 1024 );
}

if ( ! function_exists( 'matewa_remote_updater_default_status' ) ) {
	/**
	 * Build the local default updater status.
	 *
	 * @return array<string,mixed>
	 */
	function matewa_remote_updater_default_status() {
		return array(
			'checked_at'         => 0,
			'state'              => 'not_checked',
			'message'            => 'Remote update status has not been checked yet.',
			'update_available'   => false,
			'installed_version'  => defined( 'MATEWA_VERSION' ) ? MATEWA_VERSION : '',
			'latest_version'     => '',
			'plugin_slug'        => MATEWA_REMOTE_UPDATER_PLUGIN_SLUG,
			'plugin_basename'    => defined( 'MATEWA_MAIN_FILE' ) ? plugin_basename( MATEWA_MAIN_FILE ) : '',
			'manifest_plugin_basename' => '',
			'manifest_url'       => MATEWA_REMOTE_UPDATER_MANIFEST_URL,
			'package_url'        => '',
			'checksum_algorithm' => '',
			'checksum_value'     => '',
			'critical_update'    => false,
			'rollout_channel'    => 'stable',
			'release_date'       => '',
			'minimum_wp_version' => '',
			'tested_wp_version'  => '',
			'minimum_php_version' => '',
			'wordpress_update_ready' => false,
			'wordpress_update_block_reason' => '',
			'release_notes'                  => array(),
			'icon_urls'                      => array(),
			'banner_urls'                    => array(),
			'package_verification_state'     => 'not_checked',
			'package_verification_message'   => 'Package verification has not been run yet.',
			'package_verified'               => false,
			'package_structure_verified'     => false,
			'package_hash'                   => '',
			'expected_plugin_file'           => MATEWA_REMOTE_UPDATER_EXPECTED_PLUGIN_FILE,
			'warnings'                       => array(),
		);
	}
}

if ( ! function_exists( 'matewa_remote_updater_is_https_url' ) ) {
	/**
	 * Confirm a URL uses HTTPS.
	 *
	 * @param string $url URL to check.
	 * @return bool
	 */
	function matewa_remote_updater_is_https_url( $url ) {
		$scheme = wp_parse_url( $url, PHP_URL_SCHEME );

		return 'https' === strtolower( (string) $scheme );
	}
}

if ( ! function_exists( 'matewa_remote_updater_allowed_host' ) ) {
	/**
	 * Confirm the updater host is trusted.
	 *
	 * @param string $url URL to check.
	 * @return bool
	 */
	function matewa_remote_updater_allowed_host( $url ) {
		$host = strtolower( (string) wp_parse_url( $url, PHP_URL_HOST ) );

		$allowed_hosts = array(
			'247trademate.pro',
			'www.247trademate.pro',
		);

		return in_array( $host, $allowed_hosts, true );
	}
}

if ( ! function_exists( 'matewa_remote_updater_sanitise_release_notes' ) ) {
	/**
	 * Sanitise release notes from the remote manifest.
	 *
	 * @param mixed $release_notes Raw release notes.
	 * @return array<int,string>
	 */
	function matewa_remote_updater_sanitise_release_notes( $release_notes ) {
		if ( is_string( $release_notes ) ) {
			$release_notes = array( $release_notes );
		}

		if ( ! is_array( $release_notes ) ) {
			return array();
		}

		$clean_notes = array();

		foreach ( $release_notes as $note ) {
			if ( ! is_scalar( $note ) ) {
				continue;
			}

			$note = sanitize_text_field( (string) $note );

			if ( '' !== $note ) {
				$clean_notes[] = $note;
			}
		}

		return array_slice( $clean_notes, 0, 20 );
	}
}

if ( ! function_exists( 'matewa_remote_updater_extract_checksum' ) ) {
	/**
	 * Extract checksum fields from a manifest.
	 *
	 * @param array<string,mixed> $manifest Remote manifest.
	 * @return array{algorithm:string,value:string}
	 */
	function matewa_remote_updater_extract_checksum( $manifest ) {
		$algorithm = '';
		$value     = '';

		if ( isset( $manifest['checksum'] ) && is_array( $manifest['checksum'] ) ) {
			$algorithm = isset( $manifest['checksum']['algorithm'] ) ? sanitize_key( (string) $manifest['checksum']['algorithm'] ) : '';
			$value     = isset( $manifest['checksum']['value'] ) ? sanitize_text_field( (string) $manifest['checksum']['value'] ) : '';
		} elseif ( isset( $manifest['checksum'] ) && is_scalar( $manifest['checksum'] ) ) {
			$algorithm = 'sha256';
			$value     = sanitize_text_field( (string) $manifest['checksum'] );
		}

		return array(
			'algorithm' => $algorithm,
			'value'     => $value,
		);
	}
}

if ( ! function_exists( 'matewa_remote_updater_validate_manifest' ) ) {
	/**
	 * Validate and normalise a remote manifest.
	 *
	 * @param mixed $manifest Raw decoded manifest.
	 * @return array<string,mixed>|WP_Error
	 */
	function matewa_remote_updater_validate_manifest( $manifest ) {
		if ( ! is_array( $manifest ) ) {
			return new WP_Error( 'matewa_remote_manifest_invalid_json', 'Remote update manifest is not valid JSON.' );
		}

		$plugin_slug = isset( $manifest['plugin_slug'] ) ? sanitize_key( (string) $manifest['plugin_slug'] ) : '';

		if ( MATEWA_REMOTE_UPDATER_PLUGIN_SLUG !== $plugin_slug ) {
			return new WP_Error( 'matewa_remote_manifest_slug_mismatch', 'Remote update manifest does not match this plugin.' );
		}

		$latest_version = isset( $manifest['latest_version'] ) ? sanitize_text_field( (string) $manifest['latest_version'] ) : '';

		if ( '' === $latest_version ) {
			return new WP_Error( 'matewa_remote_manifest_missing_version', 'Remote update manifest is missing latest_version.' );
		}

		$package_url = isset( $manifest['package_url'] ) ? esc_url_raw( (string) $manifest['package_url'] ) : '';

		if ( '' !== $package_url ) {
			if ( ! matewa_remote_updater_is_https_url( $package_url ) ) {
				return new WP_Error( 'matewa_remote_manifest_package_not_https', 'Remote update package URL must use HTTPS.' );
			}

			if ( ! matewa_remote_updater_allowed_host( $package_url ) ) {
				return new WP_Error( 'matewa_remote_manifest_package_host_blocked', 'Remote update package host is not trusted.' );
			}
		}

		$checksum = matewa_remote_updater_extract_checksum( $manifest );

		if ( '' !== $package_url ) {
			if ( 'sha256' !== $checksum['algorithm'] || '' === $checksum['value'] ) {
				return new WP_Error( 'matewa_remote_manifest_checksum_missing', 'Remote update package requires a SHA-256 checksum before it can be trusted.' );
			}

			if ( ! preg_match( '/^[a-f0-9]{64}$/i', $checksum['value'] ) ) {
				return new WP_Error( 'matewa_remote_manifest_checksum_invalid', 'Remote update package checksum is not a valid SHA-256 value.' );
			}
		}

		$plugin_basename          = defined( 'MATEWA_MAIN_FILE' ) ? plugin_basename( MATEWA_MAIN_FILE ) : '';
		$manifest_plugin_basename = isset( $manifest['plugin_basename'] ) ? sanitize_text_field( (string) $manifest['plugin_basename'] ) : '';
		$warnings                 = array();

		if ( '' !== $manifest_plugin_basename && '' !== $plugin_basename && $manifest_plugin_basename !== $plugin_basename ) {
			$warnings[] = 'Manifest plugin_basename differs from the installed plugin basename. Standard WordPress update flow will be held closed until the plugin path is stable.';
		}

		$minimum_wp_version  = isset( $manifest['minimum_wp_version'] ) ? sanitize_text_field( (string) $manifest['minimum_wp_version'] ) : '';
		$tested_wp_version   = isset( $manifest['tested_wp_version'] ) ? sanitize_text_field( (string) $manifest['tested_wp_version'] ) : '';
		$minimum_php_version = isset( $manifest['minimum_php_version'] ) ? sanitize_text_field( (string) $manifest['minimum_php_version'] ) : '';
		$rollout_channel     = isset( $manifest['rollout_channel'] ) ? sanitize_key( (string) $manifest['rollout_channel'] ) : 'stable';
		$icon_urls           = matewa_remote_updater_sanitise_asset_map( $manifest['icons'] ?? array(), array( 'svg', '2x', '1x', 'default', '128x128', '256x256' ) );
		$banner_urls         = matewa_remote_updater_sanitise_asset_map( $manifest['banners'] ?? array(), array( 'high', 'low', 'default' ) );

		return array(
			'plugin_slug'              => $plugin_slug,
			'manifest_plugin_basename' => $manifest_plugin_basename,
			'latest_version'           => $latest_version,
			'minimum_wp_version'       => $minimum_wp_version,
			'tested_wp_version'        => $tested_wp_version,
			'minimum_php_version'      => $minimum_php_version,
			'package_url'              => $package_url,
			'checksum_algorithm'       => $checksum['algorithm'],
			'checksum_value'           => strtolower( $checksum['value'] ),
			'release_notes'            => matewa_remote_updater_sanitise_release_notes( $manifest['release_notes'] ?? array() ),
			'icon_urls'                => $icon_urls,
			'banner_urls'              => $banner_urls,
			'release_date'             => isset( $manifest['release_date'] ) ? sanitize_text_field( (string) $manifest['release_date'] ) : '',
			'critical_update'          => ! empty( $manifest['critical_update'] ),
			'rollout_channel'          => '' !== $rollout_channel ? $rollout_channel : 'stable',
			'warnings'                 => $warnings,
		);
	}
}

if ( ! function_exists( 'matewa_remote_updater_fetch_manifest' ) ) {
	/**
	 * Fetch and validate the remote manifest.
	 *
	 * @return array<string,mixed>|WP_Error
	 */
	function matewa_remote_updater_fetch_manifest() {
		$manifest_url = esc_url_raw( MATEWA_REMOTE_UPDATER_MANIFEST_URL );

		if ( ! matewa_remote_updater_is_https_url( $manifest_url ) ) {
			return new WP_Error( 'matewa_remote_manifest_not_https', 'Remote update manifest URL must use HTTPS.' );
		}

		if ( ! matewa_remote_updater_allowed_host( $manifest_url ) ) {
			return new WP_Error( 'matewa_remote_manifest_host_blocked', 'Remote update manifest host is not trusted.' );
		}

		$response = wp_remote_get(
			$manifest_url,
			array(
				'timeout'     => 5,
				'redirection' => 2,
				'sslverify'   => true,
				'user-agent'  => '247ChatMate/' . ( defined( 'MATEWA_VERSION' ) ? MATEWA_VERSION : 'unknown' ),
			)
		);

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$code = absint( wp_remote_retrieve_response_code( $response ) );

		if ( 200 !== $code ) {
			return new WP_Error( 'matewa_remote_manifest_http_error', 'Remote update manifest could not be reached.' );
		}

		$body = wp_remote_retrieve_body( $response );

		if ( ! is_string( $body ) || '' === trim( $body ) ) {
			return new WP_Error( 'matewa_remote_manifest_empty', 'Remote update manifest is empty.' );
		}

		$decoded = json_decode( $body, true );

		if ( JSON_ERROR_NONE !== json_last_error() ) {
			return new WP_Error( 'matewa_remote_manifest_json_error', 'Remote update manifest JSON could not be decoded.' );
		}

		return matewa_remote_updater_validate_manifest( $decoded );
	}
}


if ( ! function_exists( 'matewa_remote_updater_normalise_zip_entry' ) ) {
	/**
	 * Normalise a zip entry name for safe comparison.
	 *
	 * @param string $entry Zip entry name.
	 * @return string
	 */
	function matewa_remote_updater_normalise_zip_entry( $entry ) {
		$entry = str_replace( '\\', '/', (string) $entry );
		$entry = preg_replace( '#/+#', '/', $entry );
		$entry = ltrim( (string) $entry, './' );

		return $entry;
	}
}

if ( ! function_exists( 'matewa_remote_updater_zip_entry_is_safe' ) ) {
	/**
	 * Check that a zip entry cannot escape the plugin directory when extracted.
	 *
	 * This does not extract anything. It only inspects entry names before any
	 * future WordPress update integration is allowed to trust a package.
	 *
	 * @param string $entry Zip entry name.
	 * @return bool
	 */
	function matewa_remote_updater_zip_entry_is_safe( $entry ) {
		$entry = str_replace( '\\', '/', (string) $entry );

		if ( '' === $entry ) {
			return false;
		}

		if ( '/' === $entry[0] ) {
			return false;
		}

		if ( preg_match( '/^[a-zA-Z]:\//', $entry ) ) {
			return false;
		}

		$parts = explode( '/', $entry );

		foreach ( $parts as $part ) {
			if ( '..' === $part ) {
				return false;
			}
		}

		return true;
	}
}

if ( ! function_exists( 'matewa_remote_updater_package_entries_with_ziparchive' ) ) {
	/**
	 * Read package entries using ZipArchive when available.
	 *
	 * @param string $package_path Local package path.
	 * @return array<int,string>|WP_Error
	 */
	function matewa_remote_updater_package_entries_with_ziparchive( $package_path ) {
		if ( ! class_exists( 'ZipArchive' ) ) {
			return new WP_Error( 'matewa_remote_package_ziparchive_missing', 'ZipArchive is not available.' );
		}

		$zip = new ZipArchive();
		$open_result = $zip->open( $package_path );

		if ( true !== $open_result ) {
			return new WP_Error( 'matewa_remote_package_zip_open_failed', 'Remote update package could not be opened as a zip file.' );
		}

		$entries = array();

		for ( $i = 0; $i < $zip->numFiles; $i++ ) {
			$name = $zip->getNameIndex( $i );

			if ( false === $name ) {
				continue;
			}

			$entries[] = (string) $name;
		}

		$zip->close();

		return $entries;
	}
}

if ( ! function_exists( 'matewa_remote_updater_package_entries_with_pclzip' ) ) {
	/**
	 * Read package entries using WordPress' bundled PclZip fallback.
	 *
	 * @param string $package_path Local package path.
	 * @return array<int,string>|WP_Error
	 */
	function matewa_remote_updater_package_entries_with_pclzip( $package_path ) {
		if ( ! class_exists( 'PclZip' ) ) {
			$pclzip_file = ABSPATH . 'wp-admin/includes/class-pclzip.php';

			if ( is_readable( $pclzip_file ) ) {
				require_once $pclzip_file;
			}
		}

		if ( ! class_exists( 'PclZip' ) ) {
			return new WP_Error( 'matewa_remote_package_pclzip_missing', 'WordPress zip inspection fallback is not available.' );
		}

		$archive = new PclZip( $package_path );
		$list    = $archive->listContent();

		if ( ! is_array( $list ) ) {
			return new WP_Error( 'matewa_remote_package_pclzip_open_failed', 'Remote update package could not be inspected as a zip file.' );
		}

		$entries = array();

		foreach ( $list as $item ) {
			if ( isset( $item['filename'] ) ) {
				$entries[] = (string) $item['filename'];
			}
		}

		return $entries;
	}
}

if ( ! function_exists( 'matewa_remote_updater_get_package_entries' ) ) {
	/**
	 * Return zip entries without extracting the package.
	 *
	 * @param string $package_path Local package path.
	 * @return array<int,string>|WP_Error
	 */
	function matewa_remote_updater_get_package_entries( $package_path ) {
		$entries = matewa_remote_updater_package_entries_with_ziparchive( $package_path );

		if ( ! is_wp_error( $entries ) ) {
			return $entries;
		}

		return matewa_remote_updater_package_entries_with_pclzip( $package_path );
	}
}

if ( ! function_exists( 'matewa_remote_updater_inspect_package_structure' ) ) {
	/**
	 * Inspect package structure without extracting or installing anything.
	 *
	 * @param string $package_path Local package path.
	 * @return array<string,mixed>|WP_Error
	 */
	function matewa_remote_updater_inspect_package_structure( $package_path ) {
		if ( ! is_readable( $package_path ) ) {
			return new WP_Error( 'matewa_remote_package_not_readable', 'Remote update package could not be read.' );
		}

		$entries = matewa_remote_updater_get_package_entries( $package_path );

		if ( is_wp_error( $entries ) ) {
			return $entries;
		}

		if ( empty( $entries ) ) {
			return new WP_Error( 'matewa_remote_package_empty_zip', 'Remote update package zip contains no files.' );
		}

		$normalised_entries = array();
		$unsafe_entries     = array();
		$expected_file      = matewa_remote_updater_normalise_zip_entry( MATEWA_REMOTE_UPDATER_EXPECTED_PLUGIN_FILE );
		$found_expected     = false;

		foreach ( $entries as $entry ) {
			$entry = (string) $entry;

			if ( ! matewa_remote_updater_zip_entry_is_safe( $entry ) ) {
				$unsafe_entries[] = $entry;
			}

			$normalised = matewa_remote_updater_normalise_zip_entry( $entry );

			if ( '' !== $normalised ) {
				$normalised_entries[] = $normalised;
			}

			if ( $expected_file === $normalised ) {
				$found_expected = true;
			}
		}

		if ( ! empty( $unsafe_entries ) ) {
			return new WP_Error( 'matewa_remote_package_unsafe_entries', 'Remote update package contains unsafe zip entry paths.' );
		}

		if ( ! $found_expected ) {
			return new WP_Error( 'matewa_remote_package_missing_plugin_file', 'Remote update package must contain 247chatmate/247chatmate.php.' );
		}

		return array(
			'expected_plugin_file' => $expected_file,
			'entry_count'          => count( $normalised_entries ),
			'has_expected_file'    => true,
		);
	}
}

if ( ! function_exists( 'matewa_remote_updater_download_package_to_temp' ) ) {
	/**
	 * Download a manifest-declared package to a temporary WordPress file.
	 *
	 * This does not install or extract the package.
	 *
	 * @param string $package_url Trusted package URL.
	 * @return string|WP_Error Local temporary package path.
	 */
	function matewa_remote_updater_download_package_to_temp( $package_url ) {
		$package_url = esc_url_raw( (string) $package_url );

		if ( '' === $package_url ) {
			return new WP_Error( 'matewa_remote_package_url_missing', 'Remote update package URL is missing.' );
		}

		if ( ! matewa_remote_updater_is_https_url( $package_url ) ) {
			return new WP_Error( 'matewa_remote_package_not_https', 'Remote update package URL must use HTTPS.' );
		}

		if ( ! matewa_remote_updater_allowed_host( $package_url ) ) {
			return new WP_Error( 'matewa_remote_package_host_blocked', 'Remote update package host is not trusted.' );
		}

		if ( ! function_exists( 'wp_tempnam' ) ) {
			$file_functions = ABSPATH . 'wp-admin/includes/file.php';

			if ( is_readable( $file_functions ) ) {
				require_once $file_functions;
			}
		}

		$temp_file = function_exists( 'wp_tempnam' ) ? wp_tempnam( $package_url ) : tempnam( sys_get_temp_dir(), 'matewa-update-' );

		if ( ! is_string( $temp_file ) || '' === $temp_file ) {
			return new WP_Error( 'matewa_remote_package_temp_failed', 'Temporary package file could not be created.' );
		}

		$response = wp_remote_get(
			$package_url,
			array(
				'timeout'             => MATEWA_REMOTE_UPDATER_PACKAGE_TIMEOUT,
				'redirection'         => 2,
				'sslverify'           => true,
				'stream'              => true,
				'filename'            => $temp_file,
				'limit_response_size' => MATEWA_REMOTE_UPDATER_PACKAGE_MAX_BYTES,
				'user-agent'          => '247ChatMate/' . ( defined( 'MATEWA_VERSION' ) ? MATEWA_VERSION : 'unknown' ),
			)
		);

		if ( is_wp_error( $response ) ) {
			@unlink( $temp_file );
			return $response;
		}

		$code = absint( wp_remote_retrieve_response_code( $response ) );

		if ( 200 !== $code ) {
			@unlink( $temp_file );
			return new WP_Error( 'matewa_remote_package_http_error', 'Remote update package could not be downloaded.' );
		}

		if ( ! is_readable( $temp_file ) || 0 === filesize( $temp_file ) ) {
			@unlink( $temp_file );
			return new WP_Error( 'matewa_remote_package_empty_download', 'Remote update package download was empty.' );
		}

		return $temp_file;
	}
}

if ( ! function_exists( 'matewa_remote_updater_verify_package_file' ) ) {
	/**
	 * Verify a local package file against checksum and expected structure.
	 *
	 * @param string $package_path Local package path.
	 * @param string $expected_checksum Expected SHA-256 checksum.
	 * @return array<string,mixed>|WP_Error
	 */
	function matewa_remote_updater_verify_package_file( $package_path, $expected_checksum ) {
		$expected_checksum = strtolower( sanitize_text_field( (string) $expected_checksum ) );

		if ( ! preg_match( '/^[a-f0-9]{64}$/', $expected_checksum ) ) {
			return new WP_Error( 'matewa_remote_package_expected_checksum_invalid', 'Expected package checksum is not a valid SHA-256 value.' );
		}

		if ( ! is_readable( $package_path ) ) {
			return new WP_Error( 'matewa_remote_package_file_missing', 'Remote update package file is missing or unreadable.' );
		}

		$actual_checksum = hash_file( 'sha256', $package_path );

		if ( ! is_string( $actual_checksum ) || '' === $actual_checksum ) {
			return new WP_Error( 'matewa_remote_package_hash_failed', 'Remote update package checksum could not be calculated.' );
		}

		$actual_checksum = strtolower( $actual_checksum );

		if ( ! hash_equals( $expected_checksum, $actual_checksum ) ) {
			return new WP_Error( 'matewa_remote_package_checksum_mismatch', 'Remote update package checksum does not match the manifest.' );
		}

		$structure = matewa_remote_updater_inspect_package_structure( $package_path );

		if ( is_wp_error( $structure ) ) {
			return $structure;
		}

		return array(
			'package_verified'           => true,
			'package_structure_verified' => true,
			'checksum_algorithm'         => 'sha256',
			'checksum_value'             => $actual_checksum,
			'expected_plugin_file'       => $structure['expected_plugin_file'],
			'entry_count'                => $structure['entry_count'],
		);
	}
}

if ( ! function_exists( 'matewa_remote_updater_verify_manifest_package' ) ) {
	/**
	 * Download and verify the manifest-declared package without installing it.
	 *
	 * This function is intentionally not hooked to the WordPress update flow yet.
	 * It is here as the safety layer before any future update UI integration.
	 *
	 * @param array<string,mixed> $manifest Normalised manifest/status array.
	 * @return array<string,mixed>|WP_Error
	 */
	function matewa_remote_updater_verify_manifest_package( $manifest ) {
		$package_url       = isset( $manifest['package_url'] ) ? esc_url_raw( (string) $manifest['package_url'] ) : '';
		$checksum_value    = isset( $manifest['checksum_value'] ) ? strtolower( sanitize_text_field( (string) $manifest['checksum_value'] ) ) : '';
		$checksum_algorithm = isset( $manifest['checksum_algorithm'] ) ? sanitize_key( (string) $manifest['checksum_algorithm'] ) : '';

		if ( '' === $package_url ) {
			return new WP_Error( 'matewa_remote_package_url_missing', 'Remote update package URL is missing.' );
		}

		if ( 'sha256' !== $checksum_algorithm ) {
			return new WP_Error( 'matewa_remote_package_algorithm_unsupported', 'Remote update package currently requires SHA-256 verification.' );
		}

		$temp_file = matewa_remote_updater_download_package_to_temp( $package_url );

		if ( is_wp_error( $temp_file ) ) {
			return $temp_file;
		}

		try {
			$result = matewa_remote_updater_verify_package_file( $temp_file, $checksum_value );
		} finally {
			@unlink( $temp_file );
		}

		return $result;
	}
}

if ( ! function_exists( 'matewa_remote_updater_verify_latest_package' ) ) {
	/**
	 * Force a manifest refresh and verify its package without installing it.
	 *
	 * No hooks call this automatically in this checkpoint. It exists so the next
	 * WordPress update integration pass can reuse proven verification logic.
	 *
	 * @return array<string,mixed>|WP_Error
	 */
	function matewa_remote_updater_verify_latest_package() {
		$manifest = matewa_remote_updater_fetch_manifest();

		if ( is_wp_error( $manifest ) ) {
			return $manifest;
		}

		return matewa_remote_updater_verify_manifest_package( $manifest );
	}
}

if ( ! function_exists( 'matewa_remote_updater_build_status' ) ) {
	/**
	 * Build a cached safe status result.
	 *
	 * @return array<string,mixed>
	 */
	function matewa_remote_updater_build_status() {
		$status = matewa_remote_updater_default_status();
		$result = matewa_remote_updater_fetch_manifest();

		$status['checked_at'] = time();

		if ( is_wp_error( $result ) ) {
			$status['state']   = 'unavailable';
			$status['message'] = 'Remote update status is currently unavailable. The plugin will continue running normally.';
			$status['error']   = $result->get_error_code();

			return $status;
		}

		$status['latest_version']           = $result['latest_version'];
		$status['package_url']              = $result['package_url'];
		$status['checksum_algorithm']       = $result['checksum_algorithm'];
		$status['checksum_value']           = $result['checksum_value'];
		$status['critical_update']          = $result['critical_update'];
		$status['rollout_channel']          = $result['rollout_channel'];
		$status['release_date']             = $result['release_date'];
		$status['release_notes']            = $result['release_notes'];
		$status['warnings']                 = $result['warnings'];
		$status['manifest_plugin_basename'] = $result['manifest_plugin_basename'];
		$status['minimum_wp_version']       = $result['minimum_wp_version'];
		$status['tested_wp_version']        = $result['tested_wp_version'];
		$status['minimum_php_version']      = $result['minimum_php_version'];

		$current_version = defined( 'MATEWA_VERSION' ) ? MATEWA_VERSION : '0.0.0';

		if ( version_compare( $result['latest_version'], $current_version, '<=' ) ) {
			$status['state']            = 'current';
			$status['message']          = '247ChatMate is up to date.';
			$status['update_available'] = false;

			return $status;
		}

		$requirement_block_reason = matewa_remote_updater_requirement_block_reason( $status );

		if ( '' !== $requirement_block_reason ) {
			$status['state']                         = 'blocked';
			$status['message']                       = 'A 247ChatMate update is available, but this site does not meet the minimum WordPress or PHP requirement.';
			$status['update_available']              = false;
			$status['wordpress_update_ready']        = false;
			$status['wordpress_update_block_reason'] = $requirement_block_reason;

			return $status;
		}

		$status['state']            = 'available';
		$status['message']          = 'A 247ChatMate update is available. Standard WordPress update flow is enabled only after path, package and checksum validation pass.';
		$status['update_available'] = true;

		return $status;
	}
}

if ( ! function_exists( 'matewa_remote_updater_get_status' ) ) {
	/**
	 * Get cached updater status, optionally forcing a refresh.
	 *
	 * @param bool $force_refresh Whether to bypass the cache.
	 * @return array<string,mixed>
	 */
	function matewa_remote_updater_get_status( $force_refresh = false ) {
		if ( ! $force_refresh ) {
			$cached = get_site_transient( MATEWA_REMOTE_UPDATER_CACHE_KEY );

			if ( is_array( $cached ) ) {
				return $cached;
			}
		}

		$status = matewa_remote_updater_build_status();

		set_site_transient( MATEWA_REMOTE_UPDATER_CACHE_KEY, $status, MATEWA_REMOTE_UPDATER_CACHE_TTL );

		return $status;
	}
}


if ( ! function_exists( 'matewa_remote_updater_get_installed_plugin_basename' ) ) {
	/**
	 * Get the installed plugin basename used by WordPress update APIs.
	 *
	 * @return string
	 */
	function matewa_remote_updater_get_installed_plugin_basename() {
		return defined( 'MATEWA_MAIN_FILE' ) ? plugin_basename( MATEWA_MAIN_FILE ) : '';
	}
}

if ( ! function_exists( 'matewa_remote_updater_standard_update_block_reason' ) ) {
	/**
	 * Explain why the standard WordPress updater should not be offered.
	 *
	 * The remote release package is intentionally structured as
	 * 247chatmate/247chatmate.php. If a test site has this plugin installed under
	 * a temporary zip folder name, WordPress may not replace the correct plugin
	 * cleanly. In that case the updater remains fail-closed.
	 *
	 * @param array<string,mixed> $status Current updater status.
	 * @return string Empty when standard update flow is safe.
	 */
	function matewa_remote_updater_standard_update_block_reason( $status ) {
		$installed_basename = matewa_remote_updater_get_installed_plugin_basename();
		$expected_basename  = isset( $status['manifest_plugin_basename'] ) && '' !== $status['manifest_plugin_basename']
			? (string) $status['manifest_plugin_basename']
			: MATEWA_REMOTE_UPDATER_EXPECTED_PLUGIN_FILE;

		if ( '' === $installed_basename ) {
			return 'Installed plugin basename could not be detected.';
		}

		if ( MATEWA_REMOTE_UPDATER_EXPECTED_PLUGIN_FILE !== $expected_basename ) {
			return 'Remote manifest plugin basename is not the expected stable 247ChatMate basename.';
		}

		if ( $installed_basename !== MATEWA_REMOTE_UPDATER_EXPECTED_PLUGIN_FILE ) {
			return 'This site is not installed under the stable 247chatmate/247chatmate.php plugin path yet, so the WordPress update hook is held closed.';
		}

		return '';
	}
}


if ( ! function_exists( 'matewa_remote_updater_requirement_block_reason' ) ) {
	/**
	 * Explain why this site does not meet the manifest's runtime requirements.
	 *
	 * This is checked both while building the WordPress update offer and again
	 * before package download. It deliberately uses the remote manifest values
	 * already normalised into the updater status array.
	 *
	 * @param array<string,mixed> $status Current updater status.
	 * @return string Empty when requirements are met.
	 */
	function matewa_remote_updater_requirement_block_reason( $status ) {
		$minimum_wp_version  = isset( $status['minimum_wp_version'] ) ? sanitize_text_field( (string) $status['minimum_wp_version'] ) : '';
		$minimum_php_version = isset( $status['minimum_php_version'] ) ? sanitize_text_field( (string) $status['minimum_php_version'] ) : '';

		if ( '' !== $minimum_wp_version && version_compare( get_bloginfo( 'version' ), $minimum_wp_version, '<' ) ) {
			return 'This site does not meet the minimum WordPress version required for this 247ChatMate update.';
		}

		if ( '' !== $minimum_php_version && version_compare( PHP_VERSION, $minimum_php_version, '<' ) ) {
			return 'This site does not meet the minimum PHP version required for this 247ChatMate update.';
		}

		return '';
	}
}


if ( ! function_exists( 'matewa_remote_updater_should_offer_update' ) ) {
	/**
	 * Confirm the manifest version is genuinely newer than the installed plugin.
	 *
	 * This guard is intentionally repeated close to the update-offer builder so a
	 * stale WordPress update transient cannot keep advertising the current version
	 * after a successful remote update.
	 *
	 * @param array<string,mixed> $status Current updater status.
	 * @return bool
	 */
	function matewa_remote_updater_should_offer_update( $status ) {
		$latest_version    = isset( $status['latest_version'] ) ? sanitize_text_field( (string) $status['latest_version'] ) : '';
		$installed_version = defined( 'MATEWA_VERSION' ) ? sanitize_text_field( (string) MATEWA_VERSION ) : '';

		if ( '' === $latest_version || '' === $installed_version ) {
			return false;
		}

		return version_compare( $latest_version, $installed_version, '>' );
	}
}

if ( ! function_exists( 'matewa_remote_updater_build_update_offer' ) ) {
	/**
	 * Build the update object inserted into WordPress' plugin update transient.
	 *
	 * @param array<string,mixed> $status Current updater status.
	 * @return object|null
	 */
	function matewa_remote_updater_build_update_offer( $status ) {
		if ( empty( $status['update_available'] ) || 'available' !== ( $status['state'] ?? '' ) ) {
			return null;
		}

		if ( ! matewa_remote_updater_should_offer_update( $status ) ) {
			return null;
		}

		$package_url    = isset( $status['package_url'] ) ? esc_url_raw( (string) $status['package_url'] ) : '';
		$latest_version = isset( $status['latest_version'] ) ? sanitize_text_field( (string) $status['latest_version'] ) : '';

		if ( '' === $package_url || '' === $latest_version ) {
			return null;
		}

		if ( ! matewa_remote_updater_is_https_url( $package_url ) || ! matewa_remote_updater_allowed_host( $package_url ) ) {
			return null;
		}

		$requirement_block_reason = matewa_remote_updater_requirement_block_reason( $status );

		if ( '' !== $requirement_block_reason ) {
			return null;
		}

		$block_reason = matewa_remote_updater_standard_update_block_reason( $status );

		if ( '' !== $block_reason ) {
			return null;
		}

		return (object) array(
			'id'            => MATEWA_REMOTE_UPDATER_PLUGIN_HOMEPAGE . '/247chatmate',
			'slug'          => MATEWA_REMOTE_UPDATER_PLUGIN_SLUG,
			'plugin'        => matewa_remote_updater_get_installed_plugin_basename(),
			'new_version'   => $latest_version,
			'url'           => MATEWA_REMOTE_UPDATER_PLUGIN_HOMEPAGE,
			'package'       => $package_url,
			'tested'        => isset( $status['tested_wp_version'] ) ? (string) $status['tested_wp_version'] : '',
			'requires'      => isset( $status['minimum_wp_version'] ) ? (string) $status['minimum_wp_version'] : '',
			'requires_php'  => isset( $status['minimum_php_version'] ) ? (string) $status['minimum_php_version'] : '',
			'icons'         => matewa_remote_updater_icon_urls( $status ),
			'banners'       => matewa_remote_updater_banner_urls( $status ),
			'banners_rtl'   => array(),
			'requires_licence_status' => false,
		);
	}
}

if ( ! function_exists( 'matewa_remote_updater_filter_update_transient' ) ) {
	/**
	 * Advertise a verified remote update through the standard WordPress update UI.
	 *
	 * This only adds update availability. It does not install anything and does
	 * not add a custom installer button.
	 *
	 * @param mixed $transient WordPress update_plugins transient value.
	 * @return mixed
	 */
	function matewa_remote_updater_filter_update_transient( $transient ) {
		if ( ! is_object( $transient ) ) {
			return $transient;
		}

		$plugin_basename = matewa_remote_updater_get_installed_plugin_basename();

		if ( '' === $plugin_basename ) {
			return $transient;
		}

		if ( isset( $transient->checked ) && is_array( $transient->checked ) && ! array_key_exists( $plugin_basename, $transient->checked ) ) {
			return $transient;
		}

		$status                   = matewa_remote_updater_get_status( true );

		if ( ! matewa_remote_updater_should_offer_update( $status ) ) {
			if ( isset( $transient->response ) && is_array( $transient->response ) && isset( $transient->response[ $plugin_basename ] ) ) {
				unset( $transient->response[ $plugin_basename ] );
			}

			return $transient;
		}

		$requirement_block_reason = matewa_remote_updater_requirement_block_reason( $status );
		$path_block_reason        = matewa_remote_updater_standard_update_block_reason( $status );
		$block_reason             = '' !== $requirement_block_reason ? $requirement_block_reason : $path_block_reason;

		$status['wordpress_update_ready']        = '' === $block_reason;
		$status['wordpress_update_block_reason'] = $block_reason;
		set_site_transient( MATEWA_REMOTE_UPDATER_CACHE_KEY, $status, MATEWA_REMOTE_UPDATER_CACHE_TTL );

		$offer = matewa_remote_updater_build_update_offer( $status );

		if ( null === $offer ) {
			if ( isset( $transient->response ) && is_array( $transient->response ) && isset( $transient->response[ $plugin_basename ] ) ) {
				unset( $transient->response[ $plugin_basename ] );
			}

			return $transient;
		}

		if ( ! isset( $transient->response ) || ! is_array( $transient->response ) ) {
			$transient->response = array();
		}

		$transient->response[ $plugin_basename ] = $offer;

		return $transient;
	}
}

if ( ! function_exists( 'matewa_remote_updater_plugin_information' ) ) {
	/**
	 * Provide the WordPress plugin information modal for the private updater.
	 *
	 * @param mixed  $result Existing result.
	 * @param string $action Requested plugin API action.
	 * @param object $args   API args.
	 * @return mixed
	 */
	function matewa_remote_updater_plugin_information( $result, $action, $args ) {
		if ( 'plugin_information' !== $action ) {
			return $result;
		}

		$requested_slug = isset( $args->slug ) ? sanitize_key( (string) $args->slug ) : '';

		if ( MATEWA_REMOTE_UPDATER_PLUGIN_SLUG !== $requested_slug ) {
			return $result;
		}

		$status = matewa_remote_updater_get_status();

		if ( empty( $status['latest_version'] ) ) {
			return $result;
		}

		$release_notes = isset( $status['release_notes'] ) && is_array( $status['release_notes'] ) ? $status['release_notes'] : array();
		$changelog     = '';

		foreach ( $release_notes as $note ) {
			$note = sanitize_text_field( (string) $note );

			if ( '' !== $note ) {
				$changelog .= '<li>' . esc_html( $note ) . '</li>';
			}
		}

		if ( '' === $changelog ) {
			$changelog = '<li>Remote update package available through the 247ChatMate release channel.</li>';
		}

		return (object) array(
			'name'          => MATEWA_REMOTE_UPDATER_PLUGIN_NAME,
			'slug'          => MATEWA_REMOTE_UPDATER_PLUGIN_SLUG,
			'version'       => sanitize_text_field( (string) $status['latest_version'] ),
			'author'        => '<a href="' . esc_url( MATEWA_REMOTE_UPDATER_PLUGIN_HOMEPAGE ) . '">247TradeMate</a>',
			'homepage'      => MATEWA_REMOTE_UPDATER_PLUGIN_HOMEPAGE,
			'requires'      => isset( $status['minimum_wp_version'] ) ? sanitize_text_field( (string) $status['minimum_wp_version'] ) : '',
			'tested'        => isset( $status['tested_wp_version'] ) ? sanitize_text_field( (string) $status['tested_wp_version'] ) : '',
			'requires_php'  => isset( $status['minimum_php_version'] ) ? sanitize_text_field( (string) $status['minimum_php_version'] ) : '',
			'download_link' => isset( $status['package_url'] ) ? esc_url_raw( (string) $status['package_url'] ) : '',
			'icons'         => matewa_remote_updater_icon_urls( $status ),
			'banners'       => matewa_remote_updater_banner_urls( $status ),
			'banners_rtl'   => array(),
			'last_updated'  => isset( $status['release_date'] ) ? sanitize_text_field( (string) $status['release_date'] ) : '',
			'active_installs' => 0,
			'num_ratings'   => 0,
			'rating'        => 0,
			'external'      => true,
			'short_description' => 'Intent-first WhatsApp chat widget with guided prompts for faster lead capture.',
			'sections'      => array(
				'description' => '<p>247ChatMate remote update information from the controlled 247TradeMate release channel.</p>',
				'changelog'   => '<ul>' . $changelog . '</ul>',
			),
		);
	}
}

if ( ! function_exists( 'matewa_remote_updater_verify_before_download' ) ) {
	/**
	 * Intercept WordPress' package download and supply a pre-verified zip file.
	 *
	 * If verification fails, return WP_Error and WordPress will not install the
	 * package. The returned temp file is intentionally not deleted here because
	 * WordPress' upgrader needs it for the install step.
	 *
	 * @param mixed  $reply      Existing download reply.
	 * @param string $package    Package URL.
	 * @param object $upgrader   Upgrader instance.
	 * @param array  $hook_extra Extra upgrade context.
	 * @return mixed|string|WP_Error
	 */
	function matewa_remote_updater_verify_before_download( $reply, $package, $upgrader, $hook_extra ) {
		unset( $upgrader );

		if ( false !== $reply ) {
			return $reply;
		}

		if ( ! is_array( $hook_extra ) || empty( $hook_extra['plugin'] ) ) {
			return $reply;
		}

		$plugin_basename = matewa_remote_updater_get_installed_plugin_basename();

		if ( $plugin_basename !== (string) $hook_extra['plugin'] ) {
			return $reply;
		}

		$status                   = matewa_remote_updater_get_status( true );
		$requirement_block_reason = matewa_remote_updater_requirement_block_reason( $status );

		if ( '' !== $requirement_block_reason ) {
			return new WP_Error( 'matewa_remote_update_requirement_blocked', $requirement_block_reason );
		}

		$block_reason = matewa_remote_updater_standard_update_block_reason( $status );

		if ( '' !== $block_reason ) {
			return new WP_Error( 'matewa_remote_update_installation_blocked', $block_reason );
		}

		$expected_package = isset( $status['package_url'] ) ? esc_url_raw( (string) $status['package_url'] ) : '';
		$requested_package = esc_url_raw( (string) $package );

		if ( '' === $expected_package || $requested_package !== $expected_package ) {
			return new WP_Error( 'matewa_remote_update_package_mismatch', 'WordPress requested a package URL that does not match the trusted 247ChatMate manifest.' );
		}

		if ( empty( $status['checksum_value'] ) || 'sha256' !== ( $status['checksum_algorithm'] ?? '' ) ) {
			return new WP_Error( 'matewa_remote_update_checksum_missing', 'Remote update package cannot be installed without a SHA-256 checksum.' );
		}

		$temp_file = matewa_remote_updater_download_package_to_temp( $expected_package );

		if ( is_wp_error( $temp_file ) ) {
			return $temp_file;
		}

		$verification = matewa_remote_updater_verify_package_file( $temp_file, (string) $status['checksum_value'] );

		if ( is_wp_error( $verification ) ) {
			@unlink( $temp_file );
			return $verification;
		}

		return $temp_file;
	}
}

if ( ! function_exists( 'matewa_remote_updater_disable_automatic_updates' ) ) {
	/**
	 * Keep 247ChatMate remote updates manual-only at this checkpoint.
	 *
	 * @param bool|null $update Whether to update.
	 * @param object    $item   Update item.
	 * @return bool|null
	 */
	function matewa_remote_updater_disable_automatic_updates( $update, $item ) {
		$plugin_basename = matewa_remote_updater_get_installed_plugin_basename();
		$item_plugin     = isset( $item->plugin ) ? (string) $item->plugin : '';
		$item_slug       = isset( $item->slug ) ? sanitize_key( (string) $item->slug ) : '';

		if ( '' !== $plugin_basename && $plugin_basename === $item_plugin ) {
			return false;
		}

		if ( MATEWA_REMOTE_UPDATER_PLUGIN_SLUG === $item_slug ) {
			return false;
		}

		return $update;
	}
}

if ( ! function_exists( 'matewa_remote_updater_admin_notice' ) ) {
	/**
	 * Show a small read-only update status notice on the 247ChatMate admin page.
	 *
	 * This notice is deliberately informational only. It does not trigger an
	 * update flow and does not add a manual installer button.
	 *
	 * @return void
	 */
	function matewa_remote_updater_admin_notice() {
		if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) {
			return;
		}

		$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;

		if ( ! $screen || false === strpos( (string) $screen->id, '247chatmate' ) ) {
			return;
		}

		$status = matewa_remote_updater_get_status();

		if ( empty( $status['update_available'] ) && 'blocked' !== ( $status['state'] ?? '' ) ) {
			return;
		}

		$notice_class = 'available' === ( $status['state'] ?? '' ) ? 'notice notice-info' : 'notice notice-warning';
		$message      = isset( $status['message'] ) ? sanitize_text_field( (string) $status['message'] ) : '';
		$latest       = isset( $status['latest_version'] ) ? sanitize_text_field( (string) $status['latest_version'] ) : '';

		if ( '' === $message ) {
			return;
		}

		echo '<div class="' . esc_attr( $notice_class ) . '"><p><strong>247ChatMate remote update status:</strong> ' . esc_html( $message );

		if ( '' !== $latest ) {
			echo ' Latest version: ' . esc_html( $latest ) . '.';
		}

		$block_reason = isset( $status['wordpress_update_block_reason'] ) ? sanitize_text_field( (string) $status['wordpress_update_block_reason'] ) : '';

		if ( '' === $block_reason && ! empty( $status['update_available'] ) ) {
			$block_reason = sanitize_text_field( matewa_remote_updater_standard_update_block_reason( $status ) );
		}

		if ( '' !== $block_reason ) {
			echo ' Standard WordPress update flow is currently held closed: ' . esc_html( $block_reason );
		}

		echo '</p></div>';
	}
}

add_filter( 'pre_set_site_transient_update_plugins', 'matewa_remote_updater_filter_update_transient' );
add_filter( 'site_transient_update_plugins', 'matewa_remote_updater_filter_update_transient' );
add_filter( 'plugins_api', 'matewa_remote_updater_plugin_information', 10, 3 );
add_filter( 'upgrader_pre_download', 'matewa_remote_updater_verify_before_download', 10, 4 );
add_filter( 'auto_update_plugin', 'matewa_remote_updater_disable_automatic_updates', 10, 2 );
add_action( 'admin_notices', 'matewa_remote_updater_admin_notice' );
