<?php
/**
 * License Manager
 *
 * Integrates Elite Licenser with WP PowerSuite. Handles license activation,
 * deactivation, validation, and admin UI. Validation follows the official
 * Elite Licenser encrypted-cache protocol via {@see W_P_Power_Suite_Premium_Base}.
 *
 * @package WP_PowerSuite
 * @since 1.0.0
 */

// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * License Manager Class
 *
 * @since 1.0.0
 */
class WPPS_License_Manager {

	/**
	 * License option key base (domain-scoped name is built via Elite Licenser helper).
	 *
	 * @var string
	 */
	private const LIC_KEY_OPTION = 'wp_powersuite_lic_key';

	/**
	 * License email option.
	 *
	 * @var string
	 */
	private const LIC_EMAIL_OPTION = 'wp_powersuite_lic_email';

	/**
	 * Legacy Elite Licenser option names (migrated on read).
	 *
	 * @var string
	 */
	private const LEGACY_LIC_KEY_OPTION = 'WPPowerSuitePremium_lic_Key';

	/**
	 * @var string
	 */
	private const LEGACY_LIC_EMAIL_OPTION = 'WPPowerSuitePremium_lic_email';

	/**
	 * Admin post action for activation.
	 *
	 * @var string
	 */
	private const ACTION_ACTIVATE = 'WP_PowerSuite_el_activate_license';

	/**
	 * Admin post action for deactivation.
	 *
	 * @var string
	 */
	private const ACTION_DEACTIVATE = 'WP_PowerSuite_el_deactivate_license';

	/**
	 * Admin post action for a manual status refresh.
	 *
	 * @var string
	 */
	private const ACTION_REFRESH = 'WP_PowerSuite_el_refresh_license';

	/**
	 * Admin post action for clearing update caches only.
	 *
	 * @var string
	 */
	private const ACTION_CHECK_UPDATES = 'WP_PowerSuite_el_check_updates';

	/**
	 * Cached license status option.
	 *
	 * @var string
	 */
	private const STATUS_OPTION = 'wp_powersuite_license_status';

	/**
	 * Cron hook for periodic server-side rechecks.
	 *
	 * @var string
	 */
	private const CRON_HOOK = 'wp_powersuite_license_recheck';

	/**
	 * WordPress cron schedule for soft license rechecks.
	 *
	 * @var string
	 */
	private const CRON_SCHEDULE = 'weekly';

	/**
	 * How long cached active/invalid status is trusted for UI display only.
	 *
	 * @var int
	 */
	private const CACHE_TTL = 43200; // 12 hours.

	/**
	 * Singleton instance.
	 *
	 * @var WPPS_License_Manager|null
	 */
	private static ?WPPS_License_Manager $instance = null;

	/**
	 * License key (domain-specific option name).
	 *
	 * @var string
	 */
	private string $lic_key_name = '';

	/**
	 * License key value.
	 *
	 * @var string
	 */
	private string $license_key = '';

	/**
	 * License email.
	 *
	 * @var string
	 */
	private string $license_email = '';

	/**
	 * License validation message.
	 *
	 * @var string
	 */
	public string $license_message = '';

	/**
	 * Response object from Elite Licenser.
	 *
	 * @var object|null
	 */
	private $response_obj = null;

	/**
	 * Whether to show license message (e.g. after failed validation).
	 *
	 * @var bool
	 */
	public bool $show_message = false;

	/**
	 * Whether license grants premium feature access (entitlement).
	 *
	 * @var bool
	 */
	private bool $is_valid = false;

	/**
	 * Cached/derived status data for UI and gating.
	 *
	 * @var array<string,mixed>
	 */
	private array $status_data = [];

	/** @var array<string,mixed>|null */
	private ?array $page_pending_update = null;

	private bool $page_pending_update_loaded = false;

	/**
	 * Request-level cache for {@see STATUS_OPTION} reads.
	 *
	 * @var array<string,mixed>|null
	 */
	private ?array $cached_status_option = null;

	/**
	 * Whether {@see STATUS_OPTION} has been loaded this request.
	 *
	 * @var bool
	 */
	private bool $cached_status_loaded = false;

	/**
	 * Plugin file path for Elite Licenser.
	 *
	 * @var string
	 */
	private string $plugin_file = '';

	/**
	 * Get instance.
	 *
	 * @return WPPS_License_Manager
	 */
	public static function get_instance(): WPPS_License_Manager {
		if ( self::$instance === null ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Constructor.
	 */
	private function __construct() {
		$this->plugin_file = defined( 'WP_POWERSUITE_PLUGIN_FILE' ) ? WP_POWERSUITE_PLUGIN_FILE : __DIR__ . '/../../wp-powersuite.php';
		$this->lic_key_name = W_P_Power_Suite_Premium_Base::get_lic_key_param( self::LIC_KEY_OPTION );
		$this->load_license_data();
	}

	/**
	 * Load license key and email from options.
	 */
	private function load_license_data(): void {
		$this->license_key = (string) get_option( $this->lic_key_name, '' );

		if ( $this->license_key === '' ) {
			$this->license_key = $this->migrate_license_key_from_legacy();
		}

		$this->license_email = (string) get_option( self::LIC_EMAIL_OPTION, '' );
		if ( $this->license_email === '' ) {
			$this->license_email = $this->migrate_license_email_from_legacy();
		}
	}

	/**
	 * Copy license key from legacy Elite Licenser option names into the prefixed store.
	 */
	private function migrate_license_key_from_legacy(): string {
		$legacy_candidates = array_values(
			array_unique(
				[
					self::get_legacy_lic_key_option_name(),
					W_P_Power_Suite_Premium_Base::get_lic_key_param( self::LEGACY_LIC_KEY_OPTION ),
					self::LEGACY_LIC_KEY_OPTION,
				]
			)
		);

		foreach ( $legacy_candidates as $option_name ) {
			if ( $option_name === $this->lic_key_name ) {
				continue;
			}

			$key = get_option( $option_name, '' );
			if ( ! is_string( $key ) || $key === '' ) {
				continue;
			}

			update_option( $this->lic_key_name, $key, false );
			delete_option( $option_name );
			delete_option( self::LEGACY_LIC_KEY_OPTION );

			return $key;
		}

		return '';
	}

	/**
	 * Copy license email from the legacy option into the prefixed store.
	 */
	private function migrate_license_email_from_legacy(): string {
		$email = get_option( self::LEGACY_LIC_EMAIL_OPTION, '' );
		if ( ! is_string( $email ) || $email === '' ) {
			return '';
		}

		update_option( self::LIC_EMAIL_OPTION, $email, false );
		delete_option( self::LEGACY_LIC_EMAIL_OPTION );

		return $email;
	}

	/**
	 * Legacy option suffix used briefly by the readable client rewrite.
	 */
	private static function get_legacy_lic_key_option_name(): string {
		$host = parse_url( home_url( '/' ), PHP_URL_HOST );
		if ( ! is_string( $host ) || $host === '' ) {
			return self::LEGACY_LIC_KEY_OPTION . '_s00000000';
		}

		$host = strtolower( $host );
		if ( str_starts_with( $host, 'www.' ) ) {
			$host = substr( $host, 4 );
		}

		return self::LEGACY_LIC_KEY_OPTION . '_s' . substr( md5( $host ), 0, 8 );
	}

	/**
	 * Initialize license manager. Call early in plugin bootstrap.
	 *
	 * Hydrates validity from cache without translation calls (WP 6.7+). UI status
	 * messages refresh locally on {@see 'init'}; remote rechecks use weekly cron only.
	 *
	 * @return bool True if license is valid, false otherwise.
	 */
	public function init(): bool {
		W_P_Power_Suite_Premium_Base::register_remote_handler();

		$lic_key_name = $this->lic_key_name;
		W_P_Power_Suite_Premium_Base::add_on_delete(
			static function () use ( $lic_key_name ): void {
				delete_option( $lic_key_name );
				delete_option( self::LEGACY_LIC_KEY_OPTION );
			}
		);

		$this->purge_legacy_ui_cache();
		$this->hydrate_validity_from_cache();

		if ( ! has_action( 'init', [ $this, 'run_scheduled_validation' ] ) ) {
			add_action( 'init', [ $this, 'run_scheduled_validation' ], 1 );
		}

		return $this->is_valid;
	}

	/**
	 * Restore license validity from the Elite Licenser encrypted cache only.
	 *
	 * Must not call translation functions — WordPress 6.7+ only allows domain
	 * loading on init or later.
	 *
	 * @return void
	 */
	private function hydrate_validity_from_cache(): void {
		if ( empty( $this->license_key ) || empty( $this->license_email ) ) {
			$this->is_valid     = false;
			$this->response_obj = null;
			$this->status_data  = $this->build_status( 'inactive', '' );
			return;
		}

		$registered = W_P_Power_Suite_Premium_Base::get_register_info( $this->plugin_file );
		if ( $this->elite_cache_is_entitled( $registered ) ) {
			$this->is_valid     = true;
			$this->response_obj = $this->clone_register_response( $registered );
			$cached             = $this->get_cached_status();
			if ( ! empty( $cached ) && $this->is_cache_fresh_for_current_site( $cached ) ) {
				$this->status_data     = $cached;
				$this->license_message = (string) ( $cached['message'] ?? '' );
				return;
			}

			if ( $this->is_expired_response( $registered ) ) {
				$this->status_data = $this->build_status( 'expired', '', $registered );
			}

			return;
		}

		$this->is_valid     = false;
		$this->response_obj = null;
	}

	/**
	 * Reload license credentials from the database.
	 */
	private function reload_license_data(): void {
		$this->load_license_data();
	}

	/**
	 * Sync license state for the admin License screen.
	 *
	 * @param bool $force_remote When true, always contact the license server.
	 */
	public function sync_for_admin_page( bool $force_remote = false ): void {
		$this->reload_license_data();

		if ( ! $force_remote ) {
			$cached = $this->get_status_data();
			if ( $this->is_cache_fresh_for_current_site( $cached ) ) {
				$this->ensure_response_object();
				return;
			}
		}

		$this->validate_license( false, $force_remote, $force_remote );
		$this->ensure_response_object();
	}

	/**
	 * Cached pending update summary for the current license page render.
	 *
	 * @return array<string,mixed>|null
	 */
	public function get_page_pending_update(): ?array {
		if ( $this->page_pending_update_loaded ) {
			return $this->page_pending_update;
		}

		$this->page_pending_update_loaded = true;
		$this->page_pending_update        = class_exists( 'WPPS_License_Updater', false )
			? WPPS_License_Updater::get_pending_update()
			: null;

		return $this->page_pending_update;
	}

	/**
	 * Email address on file with Elite Licenser for this license key.
	 */
	public function get_license_customer_email(): string {
		if ( is_object( $this->response_obj ) && ! empty( $this->response_obj->customer_email ) ) {
			return sanitize_email( (string) $this->response_obj->customer_email );
		}

		$cached = $this->get_cached_status();
		if ( ! empty( $cached['details']['customer_email'] ) ) {
			return sanitize_email( (string) $cached['details']['customer_email'] );
		}

		return '';
	}

	/**
	 * Warning when the purchase email entered locally differs from Elite Licenser.
	 */
	public function get_license_email_mismatch_warning(): string {
		$server_email = strtolower( trim( $this->get_license_customer_email() ) );
		$local_email  = strtolower( trim( $this->license_email ) );
		if ( $server_email === '' || $local_email === '' || $server_email === $local_email ) {
			return '';
		}

		return sprintf(
			/* translators: 1: email on file with the license vendor, 2: email entered on this site */
			__( 'The email on file for this license is %1$s, which does not match the address saved on this site (%2$s). Use the purchase email to avoid activation issues.', 'wp-powersuite' ),
			$this->get_license_customer_email(),
			$this->license_email
		);
	}

	/**
	 * Nonce-protected URL that clears update caches and re-fetches update metadata.
	 */
	public static function get_check_updates_url( ?string $redirect_to = null ): string {
		$args = [ 'action' => self::ACTION_CHECK_UPDATES ];
		if ( $redirect_to !== null && $redirect_to !== '' ) {
			$args['redirect_to'] = rawurlencode( $redirect_to );
		}

		return wp_nonce_url(
			\WP_PowerSuite\Core\NetworkScope::adminUrl( 'admin-post.php', $args ),
			'el-license'
		);
	}

	/**
	 * Ensure a response object exists whenever the license is considered valid.
	 */
	public function ensure_response_object(): void {
		if ( is_object( $this->response_obj ) ) {
			$this->backfill_response_customer_email( $this->response_obj );
			return;
		}

		$registered = W_P_Power_Suite_Premium_Base::get_register_info( $this->plugin_file );
		if ( is_object( $registered ) ) {
			$this->response_obj = $this->clone_register_response( $registered );
			$this->backfill_response_customer_email( $this->response_obj );
			return;
		}

		$cached = $this->get_cached_status();
		if ( ! empty( $cached['details'] ) && is_array( $cached['details'] ) ) {
			$this->response_obj = $this->array_to_response_object( $cached['details'] );
		}
	}

	/**
	 * Copy customer email from UI cache when the Elite register blob predates that field.
	 */
	private function backfill_response_customer_email( object $response ): void {
		if ( ! empty( $response->customer_email ) ) {
			return;
		}

		$cached = $this->get_cached_status();
		if ( empty( $cached['details']['customer_email'] ) ) {
			return;
		}

		$response->customer_email = sanitize_email( (string) $cached['details']['customer_email'] );
	}

	/**
	 * Get the Elite Licenser response object (read-only for UI display).
	 *
	 * @return object|null
	 */
	public function get_response_obj(): ?object {
		return $this->response_obj;
	}

	/**
	 * Refresh translatable UI status from the local Elite cache on init.
	 *
	 * Remote license rechecks run only via {@see self::CRON_HOOK} so page loads
	 * never block on HTTP calls to the license server.
	 *
	 * @return void
	 */
	public function run_scheduled_validation(): void {
		if ( empty( $this->license_key ) || empty( $this->license_email ) || ! $this->is_valid ) {
			return;
		}

		$cached = $this->get_cached_status();
		if ( $this->is_cache_fresh_for_current_site( $cached ) ) {
			return;
		}

		$registered = W_P_Power_Suite_Premium_Base::get_register_info( $this->plugin_file );
		if ( ! is_object( $registered ) ) {
			return;
		}

		$status            = $this->resolve_entitlement_status( $registered, false );
		$this->status_data = $this->build_status( $status, $this->status_message_for( $status ), $registered );
		$this->persist_status_cache( $this->status_data );
	}

	/**
	 * Whether this install is the vendor/licensing host (Elite Licenser same site).
	 *
	 * Detection is host-based against the configured license server URL.
	 * It does not grant entitlement by itself — a real license activation is still required.
	 * License API calls always use real HTTP (Elite Licenser echo+die is unsafe in-process).
	 */
	public function is_vendor_site(): bool {
		if ( ! class_exists( 'W_P_Power_Suite_Premium_Base', false ) ) {
			return false;
		}

		return (bool) apply_filters(
			'wp_powersuite_is_vendor_site',
			W_P_Power_Suite_Premium_Base::is_running_on_license_server()
		);
	}

	/**
	 * Admin-only label when a real license is active on the vendor/licensing site.
	 */
	public function get_vendor_site_status_label(): string {
		if ( ! $this->is_vendor_site() || ! $this->is_valid() ) {
			return '';
		}

		return (string) apply_filters(
			'wp_powersuite_vendor_site_status_label',
			__( 'Licensed to vendor site', 'wp-powersuite' )
		);
	}

	/**
	 * Whether the active license is an Agency / white-label tier.
	 *
	 * Primary signal: Elite Licenser license type {@see extra_param} (Extra Param field).
	 * Fallback: {@see license_title} keyword match for legacy license types.
	 * Never grants access on its own — requires {@see is_valid()}.
	 */
	public function is_agency_license(): bool {
		if ( ! $this->is_valid() ) {
			return false;
		}

		$this->ensure_response_object();
		if ( ! is_object( $this->response_obj ) ) {
			return false;
		}

		$extra_param = self::resolve_extra_param( $this->response_obj );
		if ( $extra_param !== '' ) {
			$agency_values = (array) apply_filters(
				'wp_powersuite_agency_extra_param_values',
				[ 'agency', 'white_label', 'white-label', 'whitelabel' ]
			);
			foreach ( $agency_values as $value ) {
				$value = strtolower( trim( (string) $value ) );
				if ( $value !== '' && $extra_param === $value ) {
					return (bool) apply_filters( 'wp_powersuite_is_agency_license', true, $this->response_obj );
				}
			}
		}

		$needles = (array) apply_filters(
			'wp_powersuite_agency_license_titles',
			[
				'agency',
				'white label',
				'white-label',
				'whitelabel',
			]
		);

		$candidates = array_filter(
			[
				(string) ( $this->response_obj->license_title ?? '' ),
				(string) ( $this->response_obj->plan ?? '' ),
			]
		);

		foreach ( $candidates as $candidate ) {
			$candidate = strtolower( trim( $candidate ) );
			if ( $candidate === '' ) {
				continue;
			}
			foreach ( $needles as $needle ) {
				$needle = strtolower( trim( (string) $needle ) );
				if ( $needle !== '' && str_contains( $candidate, $needle ) ) {
					return (bool) apply_filters( 'wp_powersuite_is_agency_license', true, $this->response_obj );
				}
			}
		}

		return (bool) apply_filters( 'wp_powersuite_is_agency_license', false, $this->response_obj );
	}

	/**
	 * Read Elite Licenser Extra Param from a cached or live response object.
	 */
	public static function resolve_extra_param( ?object $response ): string {
		if ( ! is_object( $response ) ) {
			return '';
		}

		foreach ( [ 'extra_param', 'extra', 'lic_extra_param' ] as $key ) {
			if ( ! empty( $response->{$key} ) && is_scalar( $response->{$key} ) ) {
				return strtolower( trim( sanitize_text_field( (string) $response->{$key} ) ) );
			}
		}

		return '';
	}

	/**
	 * Check if the license entitles this site to premium features.
	 *
	 * @return bool
	 */
	public function is_valid(): bool {
		return $this->is_valid;
	}

	/**
	 * Whether the license is current for updates and support (not expired).
	 */
	public function is_current(): bool {
		if ( ! $this->is_valid() ) {
			return false;
		}

		$this->ensure_response_object();
		if ( ! is_object( $this->response_obj ) ) {
			$registered = W_P_Power_Suite_Premium_Base::get_register_info( $this->plugin_file );
			if ( ! is_object( $registered ) ) {
				return false;
			}

			return ! $this->is_expired_response( $registered );
		}

		return ! $this->is_expired_response( $this->response_obj );
	}

	/**
	 * Register admin hooks (menus, styles, admin_post handlers).
	 */
	public function register_admin_hooks(): void {
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_styles' ] );
		add_action( 'admin_post_' . self::ACTION_ACTIVATE, [ $this, 'handle_activate' ] );
		add_action( 'admin_post_' . self::ACTION_DEACTIVATE, [ $this, 'handle_deactivate' ] );
		add_action( 'admin_post_' . self::ACTION_REFRESH, [ $this, 'handle_refresh' ] );
		add_action( 'admin_post_' . self::ACTION_CHECK_UPDATES, [ $this, 'handle_check_updates' ] );
		add_action( self::CRON_HOOK, [ $this, 'cron_recheck' ] );

		$this->maybe_reschedule_cron();
	}

	/**
	 * Migrate legacy twice-daily license rechecks to weekly soft checks.
	 */
	private function maybe_reschedule_cron(): void {
		if ( ! get_option( 'wp_powersuite_license_cron_weekly', false ) ) {
			wp_clear_scheduled_hook( self::CRON_HOOK );
			update_option( 'wp_powersuite_license_cron_weekly', '1', false );
		}

		if ( ! wp_next_scheduled( self::CRON_HOOK ) ) {
			wp_schedule_event( time() + DAY_IN_SECONDS, self::CRON_SCHEDULE, self::CRON_HOOK );
		}
	}

	/**
	 * Enqueue license form styles.
	 */
	public function enqueue_styles(): void {
		$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
		if ( $page !== self::get_license_page_slug() ) {
			return;
		}

		$url = defined( 'WP_POWERSUITE_PLUGIN_URL' )
			? WP_POWERSUITE_PLUGIN_URL . 'includes/licensing/_lic_style.css'
			: plugins_url( 'includes/licensing/_lic_style.css', $this->plugin_file );
		wp_register_style( 'wpps-license', $url, [ 'wp-powersuite-admin' ], defined( 'WP_POWERSUITE_VERSION' ) ? WP_POWERSUITE_VERSION : '1.0.0' );
		wp_enqueue_style( 'wpps-license' );
	}

	/**
	 * Handle license activation.
	 */
	public function handle_activate(): void {
		check_admin_referer( 'el-license' );
		if ( ! current_user_can( \WP_PowerSuite\Core\NetworkScope::adminCapability() ) ) {
			wp_die( esc_html__( 'You do not have permission to activate the license.', 'wp-powersuite' ) );
		}

		$license_key = ! empty( $_POST['el_license_key'] ) ? sanitize_text_field( wp_unslash( $_POST['el_license_key'] ) ) : '';
		$license_email = ! empty( $_POST['el_license_email'] ) ? sanitize_email( wp_unslash( $_POST['el_license_email'] ) ) : '';

		update_option( $this->lic_key_name, $license_key, false );
		update_option( self::LIC_EMAIL_OPTION, $license_email, false );
		$this->license_key   = $license_key;
		$this->license_email = $license_email;
		$this->clear_status_cache();
		$this->validate_license( true, true, true );
		$this->flush_plugin_update_cache();

		$redirect_args = [ 'page' => self::get_license_page_slug() ];
		if ( $this->is_valid ) {
			$redirect_args['wpps_license_notice'] = 'activated';
		} else {
			$redirect_args['wpps_license_notice'] = 'failed';
		}

		wp_safe_redirect( \WP_PowerSuite\Core\NetworkScope::adminUrl( 'admin.php', $redirect_args ) );
		exit;
	}

	/**
	 * Handle license deactivation.
	 */
	public function handle_deactivate(): void {
		check_admin_referer( 'el-license' );
		if ( ! current_user_can( \WP_PowerSuite\Core\NetworkScope::adminCapability() ) ) {
			wp_die( esc_html__( 'You do not have permission to deactivate the license.', 'wp-powersuite' ) );
		}

		$message = '';
		if ( W_P_Power_Suite_Premium_Base::remove_license_key( $this->plugin_file, $message ) ) {
			update_option( $this->lic_key_name, '', false );
			update_option( self::LIC_EMAIL_OPTION, '', false );
			$this->clear_status_cache();
			W_P_Power_Suite_Premium_Base::clear_cached_response( $this->plugin_file );
			$this->reload_license_data();
			$this->is_valid     = false;
			$this->response_obj = null;
			$this->status_data  = $this->build_status( 'inactive', '' );
			$this->flush_plugin_update_cache();
		} else {
			$this->license_message = is_string( $message ) ? $message : __( 'License could not be deactivated.', 'wp-powersuite' );
			$this->show_message    = true;
		}

		$redirect_args = [ 'page' => self::get_license_page_slug() ];
		if ( ! $this->show_message ) {
			$redirect_args['wpps_license_notice'] = 'deactivated';
		}

		wp_safe_redirect( \WP_PowerSuite\Core\NetworkScope::adminUrl( 'admin.php', $redirect_args ) );
		exit;
	}

	/**
	 * Handle manual license status refresh.
	 */
	public function handle_refresh(): void {
		check_admin_referer( 'el-license' );
		if ( ! current_user_can( \WP_PowerSuite\Core\NetworkScope::adminCapability() ) ) {
			wp_die( esc_html__( 'You do not have permission to refresh the license.', 'wp-powersuite' ) );
		}

		$this->reload_license_data();
		$this->validate_license( false, true, true );
		$this->flush_plugin_update_cache();

		wp_safe_redirect( \WP_PowerSuite\Core\NetworkScope::adminUrl( 'admin.php', [
			'page'                 => self::get_license_page_slug(),
			'wpps_license_notice'  => 'refreshed',
		] ) );
		exit;
	}

	/**
	 * Clear update caches and fetch fresh update metadata.
	 */
	public function handle_check_updates(): void {
		check_admin_referer( 'el-license' );
		if ( ! current_user_can( \WP_PowerSuite\Core\NetworkScope::adminCapability() ) ) {
			wp_die( esc_html__( 'You do not have permission to check for updates.', 'wp-powersuite' ) );
		}

		if ( class_exists( 'WPPS_License_Updater', false ) ) {
			WPPS_License_Updater::clear_update_caches();
			WPPS_License_Updater::get_pending_update();
		} else {
			delete_site_transient( 'update_plugins' );
		}

		$redirect = isset( $_GET['redirect_to'] ) ? sanitize_text_field( wp_unslash( $_GET['redirect_to'] ) ) : '';
		$redirect = rawurldecode( $redirect );
		if ( $redirect === '' || ! wp_validate_redirect( $redirect, false ) ) {
			$redirect = wp_get_referer();
		}
		if ( ! is_string( $redirect ) || $redirect === '' || ! wp_validate_redirect( $redirect, false ) ) {
			$redirect = \WP_PowerSuite\Core\NetworkScope::adminUrl( 'admin.php', [
				'page' => self::get_license_page_slug(),
			] );
		}

		wp_safe_redirect( add_query_arg( 'wpps_license_notice', 'updates_checked', $redirect ) );
		exit;
	}

	/**
	 * Cron-based server-side revalidation (soft: never clears local entitlement cache).
	 */
	public function cron_recheck(): void {
		if ( empty( $this->license_key ) ) {
			return;
		}

		$registered = W_P_Power_Suite_Premium_Base::get_register_info( $this->plugin_file );
		if (
			is_object( $registered )
			&& ! empty( $registered->next_request )
			&& (int) $registered->next_request > time()
		) {
			return;
		}

		$this->validate_license( false, false, false );
	}

	/**
	 * Render license activation form.
	 */
	public function render_activation_form(): void {
		$view_file = defined( 'WP_POWERSUITE_PLUGIN_DIR' )
			? WP_POWERSUITE_PLUGIN_DIR . 'src/Admin/Views/license-activate.php'
			: dirname( __DIR__, 2 ) . '/src/Admin/Views/license-activate.php';
		if ( file_exists( $view_file ) ) {
			$manager = $this;
			include $view_file;
		} else {
			$this->render_activation_form_inline();
		}
	}

	/**
	 * Render activation form inline (fallback).
	 */
	private function render_activation_form_inline(): void {
		$purchase_email = $this->get_license_email() ?: get_bloginfo( 'admin_email' );
		$product_url   = self::get_product_url();
		$renew_url     = self::get_renew_url();
		?>
		<div class="wpps-license-page">
		<form method="post" action="<?php echo esc_url( \WP_PowerSuite\Core\NetworkScope::adminUrl( 'admin-post.php' ) ); ?>">
			<input type="hidden" name="action" value="<?php echo esc_attr( self::ACTION_ACTIVATE ); ?>"/>
			<div class="el-license-container">
				<div class="el-license-header">
					<div class="el-license-header__icon">
						<span class="dashicons dashicons-admin-network"></span>
					</div>
					<div class="el-license-header__text">
						<h3><?php esc_html_e( 'Activate License', 'wp-powersuite' ); ?></h3>
						<p><?php echo esc_html( sprintf(
							/* translators: %s: plugin name */
							__( 'Enter your license key to unlock %s and receive updates.', 'wp-powersuite' ),
							class_exists( '\WP_PowerSuite\Core\WhiteLabel', false ) ? \WP_PowerSuite\Core\WhiteLabel::pluginName() : 'WP PowerSuite'
						) ); ?></p>
					</div>
				</div>
				<div class="el-license-content">
				<?php if ( ! empty( $this->show_message ) && ! empty( $this->license_message ) ) : ?>
					<div class="wpps-license-alert wpps-license-alert--error"><p><?php echo esc_html( $this->license_message ); ?></p></div>
				<?php endif; ?>
				<div class="el-license-form-block">
				<p class="el-license-intro"><?php echo esc_html( sprintf(
					/* translators: %s: plugin name */
					__( 'Enter your license key and the email address used at purchase to activate %s.', 'wp-powersuite' ),
					class_exists( '\WP_PowerSuite\Core\WhiteLabel', false ) ? \WP_PowerSuite\Core\WhiteLabel::pluginName() : 'WP PowerSuite'
				) ); ?></p>
				<div class="el-license-field">
					<label for="el_license_key"><?php esc_html_e( 'License Key', 'wp-powersuite' ); ?></label>
					<input type="text" class="regular-text code" name="el_license_key" id="el_license_key" size="50" placeholder="xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx" required>
				</div>
				<div class="el-license-field">
					<label for="el_license_email"><?php esc_html_e( 'Email Address', 'wp-powersuite' ); ?></label>
					<input type="email" class="regular-text code" name="el_license_email" id="el_license_email" size="50" value="<?php echo esc_attr( $purchase_email ); ?>" required>
					<p class="description"><?php esc_html_e( 'We will send update notifications to this email. We do not spam.', 'wp-powersuite' ); ?></p>
				</div>
				</div>
				<div class="el-license-active-btn">
					<?php wp_nonce_field( 'el-license' ); ?>
					<?php submit_button( __( 'Activate License', 'wp-powersuite' ) ); ?>
				</div>
				<div class="el-license-links">
					<p class="el-license-links__heading"><?php esc_html_e( "Don't have a license?", 'wp-powersuite' ); ?></p>
					<a href="<?php echo esc_url( $product_url ); ?>" target="_blank" rel="noopener noreferrer" class="button button-primary">
						<span class="dashicons dashicons-cart" aria-hidden="true"></span>
						<?php echo esc_html( sprintf(
							/* translators: %s: plugin name */
							__( 'Purchase %s', 'wp-powersuite' ),
							class_exists( '\WP_PowerSuite\Core\WhiteLabel', false ) ? \WP_PowerSuite\Core\WhiteLabel::pluginName() : 'WP PowerSuite'
						) ); ?>
					</a>
					<p class="el-license-links__sub">
						<?php esc_html_e( 'Need to renew?', 'wp-powersuite' ); ?>
						<a href="<?php echo esc_url( $renew_url ); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Renew your license', 'wp-powersuite' ); ?></a>
					</p>
				</div>
				</div>
			</div>
		</form>
		</div>
		<?php
	}

	/**
	 * Render license info (when valid).
	 */
	public function render_license_info(): void {
		$this->ensure_response_object();

		$view_file = defined( 'WP_POWERSUITE_PLUGIN_DIR' )
			? WP_POWERSUITE_PLUGIN_DIR . 'src/Admin/Views/license-info.php'
			: dirname( __DIR__, 2 ) . '/src/Admin/Views/license-info.php';
		if ( file_exists( $view_file ) ) {
			$manager = $this;
			include $view_file;
		} else {
			$this->render_license_info_inline();
		}
	}

	/**
	 * Render license info inline (fallback).
	 */
	private function render_license_info_inline(): void {
		if ( ! $this->response_obj ) {
			return;
		}
		$r = $this->response_obj;
		?>
		<div class="wpps-license-page">
		<form method="post" action="<?php echo esc_url( \WP_PowerSuite\Core\NetworkScope::adminUrl( 'admin-post.php' ) ); ?>">
			<input type="hidden" name="action" value="<?php echo esc_attr( self::ACTION_DEACTIVATE ); ?>"/>
			<div class="el-license-container">
				<div class="el-license-header">
					<div class="el-license-header__icon">
						<span class="dashicons dashicons-yes-alt"></span>
					</div>
					<div class="el-license-header__text">
						<h3><?php esc_html_e( 'License Status', 'wp-powersuite' ); ?></h3>
						<p><?php echo esc_html( sprintf(
							/* translators: %s: plugin name */
							__( 'Your %s license is active. Manage your license details below.', 'wp-powersuite' ),
							class_exists( '\WP_PowerSuite\Core\WhiteLabel', false ) ? \WP_PowerSuite\Core\WhiteLabel::pluginName() : 'WP PowerSuite'
						) ); ?></p>
					</div>
				</div>
				<div class="el-license-content">
				<ul class="el-license-info">
					<?php if ( ! empty( $r->license_title ) ) : ?>
						<li>
							<div>
								<span class="el-license-info-title"><?php esc_html_e( 'License Type', 'wp-powersuite' ); ?></span>
								<?php echo esc_html( $r->license_title ); ?>
							</div>
						</li>
					<?php endif; ?>
					<?php if ( isset( $r->expire_date ) ) : ?>
						<li>
							<div>
								<span class="el-license-info-title"><?php esc_html_e( 'Expires', 'wp-powersuite' ); ?></span>
								<?php echo esc_html( $r->expire_date ); ?>
								<?php if ( ! empty( $r->expire_renew_link ) ) : ?>
									<a target="_blank" rel="noopener noreferrer" class="button button-small" href="<?php echo esc_url( $r->expire_renew_link ); ?>"><?php esc_html_e( 'Renew', 'wp-powersuite' ); ?></a>
								<?php endif; ?>
							</div>
						</li>
					<?php endif; ?>
					<?php if ( isset( $r->support_end ) ) : ?>
						<li>
							<div>
								<span class="el-license-info-title"><?php esc_html_e( 'Support Until', 'wp-powersuite' ); ?></span>
								<?php echo esc_html( $r->support_end ); ?>
								<?php if ( ! empty( $r->support_renew_link ) ) : ?>
									<a target="_blank" rel="noopener noreferrer" class="button button-small" href="<?php echo esc_url( $r->support_renew_link ); ?>"><?php esc_html_e( 'Renew', 'wp-powersuite' ); ?></a>
								<?php endif; ?>
							</div>
						</li>
					<?php endif; ?>
					<?php if ( ! empty( $r->license_key ) && strlen( $r->license_key ) >= 27 ) : ?>
						<li>
							<div>
								<span class="el-license-info-title"><?php esc_html_e( 'License Key', 'wp-powersuite' ); ?></span>
								<span class="el-license-key"><?php echo esc_html( substr( $r->license_key, 0, 9 ) . 'XXXXXXXX-XXXXXXXX' . substr( $r->license_key, -9 ) ); ?></span>
							</div>
						</li>
					<?php endif; ?>
				</ul>
				<div class="el-license-active-btn">
					<?php wp_nonce_field( 'el-license' ); ?>
					<?php submit_button( __( 'Deactivate License', 'wp-powersuite' ), 'secondary' ); ?>
				</div>
				<?php
				$product_url = self::get_product_url();
				$renew_url   = self::get_renew_url();
				$support_url = self::get_support_url();
				?>
				<div class="el-license-quick-links">
					<p class="el-license-quick-links__heading"><?php esc_html_e( 'Quick links', 'wp-powersuite' ); ?></p>
					<div class="el-license-quick-links__grid">
						<a href="<?php echo esc_url( $renew_url ); ?>" target="_blank" rel="noopener noreferrer" class="button button-secondary">
							<span class="dashicons dashicons-update" aria-hidden="true"></span>
							<?php esc_html_e( 'Renew License', 'wp-powersuite' ); ?>
						</a>
						<a href="<?php echo esc_url( $support_url ); ?>" target="_blank" rel="noopener noreferrer" class="button button-secondary">
							<span class="dashicons dashicons-sos" aria-hidden="true"></span>
							<?php esc_html_e( 'Get Support', 'wp-powersuite' ); ?>
						</a>
						<a href="<?php echo esc_url( $product_url ); ?>" target="_blank" rel="noopener noreferrer" class="button button-secondary">
							<span class="dashicons dashicons-external" aria-hidden="true"></span>
							<?php esc_html_e( 'Product Page', 'wp-powersuite' ); ?>
						</a>
					</div>
				</div>
				</div>
			</div>
		</form>
		</div>
		<?php
	}

	/**
	 * Validate license with local entitlement caching and soft remote rechecks.
	 *
	 * @param bool $clear_cache   When true, wipe local Elite Licenser cache before validating (activation only).
	 * @param bool $force_remote  When true, contact the license server even if the cache window has not elapsed.
	 * @return bool
	 */
	private function validate_license( bool $clear_cache = false, bool $force_remote = false, bool $strict_remote = false ): bool {
		if ( empty( $this->license_key ) || empty( $this->license_email ) ) {
			$this->is_valid        = false;
			$this->response_obj    = null;
			$this->status_data     = $this->build_status( 'inactive', __( 'No license key has been saved yet.', 'wp-powersuite' ) );
			$this->show_message    = false;
			$this->license_message = '';
			return false;
		}

		if ( $clear_cache ) {
			W_P_Power_Suite_Premium_Base::clear_cached_response( $this->plugin_file );
			$this->clear_status_cache();
		}

		$registered_before = W_P_Power_Suite_Premium_Base::get_register_info( $this->plugin_file );
		$entitled_before   = $this->elite_cache_is_entitled( $registered_before );

		$message  = '';
		$response = null;
		$is_valid = W_P_Power_Suite_Premium_Base::check_wp_plugin(
			$this->license_key,
			$this->license_email,
			$message,
			$response,
			$this->plugin_file,
			$force_remote,
			$strict_remote
		);

		$this->license_message = is_string( $message ) ? $message : '';
		$this->response_obj    = is_object( $response ) ? $response : null;
		$this->show_message    = $this->should_show_license_notice();

		if ( $is_valid ) {
			$this->is_valid = true;
			if ( ! is_object( $this->response_obj ) ) {
				$registered = W_P_Power_Suite_Premium_Base::get_register_info( $this->plugin_file );
				if ( is_object( $registered ) ) {
					$this->response_obj = $this->clone_register_response( $registered );
				}
			}

			$status  = $this->resolve_entitlement_status( $this->response_obj, false );
			$message = $this->status_message_for( $status );

			$this->status_data = $this->build_status( $status, $message, $this->response_obj );
			$this->persist_status_cache( $this->status_data );
			return true;
		}

		if ( ! $strict_remote && $this->is_connection_failure( $this->license_message ) && $entitled_before && is_object( $registered_before ) ) {
			$this->is_valid        = true;
			$this->response_obj    = $this->clone_register_response( $registered_before );
			$this->show_message    = false;
			$status                = $this->resolve_entitlement_status( $this->response_obj, true );
			$message               = $this->status_message_for( $status );
			$this->status_data     = $this->build_status( $status, $message, $this->response_obj );
			$this->persist_status_cache( $this->status_data );
			return true;
		}

		if ( $entitled_before && is_object( $registered_before ) && $this->apply_expired_entitlement_fallback( $registered_before, $this->license_message ) ) {
			return true;
		}

		$this->is_valid        = false;
		$this->response_obj    = is_object( $response ) ? $response : null;
		$status                = $this->is_connection_failure( $this->license_message ) ? 'connection_issue' : 'invalid';
		$this->status_data     = $this->build_status(
			$status,
			$this->license_message !== '' ? $this->license_message : __( 'License could not be validated.', 'wp-powersuite' ),
			$this->response_obj
		);
		$this->persist_status_cache( $this->status_data );
		return false;
	}

	/**
	 * Get license status data for admin UI.
	 *
	 * @return array<string,mixed>
	 */
	public function get_status_data(): array {
		if ( empty( $this->status_data ) ) {
			$this->status_data = $this->get_cached_status();
		}

		if ( empty( $this->status_data ) ) {
			$this->status_data = $this->build_status( empty( $this->license_key ) ? 'inactive' : 'unknown', '' );
		}

		return $this->status_data;
	}

	/**
	 * Return the site hash used to bind local cache to this installation.
	 */
	public function get_site_hash(): string {
		return substr( hash_hmac( 'sha256', home_url( '/' ), wp_salt( 'auth' ) ), 0, 16 );
	}

	/**
	 * Return masked license key.
	 */
	public function get_masked_license_key(): string {
		$key = (string) $this->license_key;
		if ( strlen( $key ) < 18 ) {
			return $key === '' ? '' : str_repeat( '*', max( 0, strlen( $key ) - 4 ) ) . substr( $key, -4 );
		}

		return substr( $key, 0, 6 ) . str_repeat( '*', max( 8, strlen( $key ) - 12 ) ) . substr( $key, -6 );
	}

	/**
	 * Return saved license email.
	 */
	public function get_license_email(): string {
		return $this->license_email;
	}

	/**
	 * Build a sanitized status array for storage/UI.
	 *
	 * @param string      $status   Status slug.
	 * @param string      $message  Human-readable message.
	 * @param object|null $response Remote response object.
	 * @return array<string,mixed>
	 */
	private function build_status( string $status, string $message, ?object $response = null ): array {
		$now     = time();
		$details = $this->sanitize_response_details( $response );
		$is_good = in_array( $status, [ 'active', 'grace', 'expired' ], true );

		return [
			'status'       => sanitize_key( $status ),
			'message'      => sanitize_text_field( $message ),
			'checked_at'   => $now,
			'last_success' => $is_good ? $now : (int) ( $this->status_data['last_success'] ?? 0 ),
			'site_url'     => home_url( '/' ),
			'site_hash'    => $this->get_site_hash(),
			'license_hash' => $this->get_license_hash(),
			'email'        => sanitize_email( $this->license_email ),
			'details'      => $details,
		];
	}

	/**
	 * Get cached status.
	 *
	 * @return array<string,mixed>
	 */
	private function get_cached_status(): array {
		if ( $this->cached_status_loaded ) {
			return is_array( $this->cached_status_option ) ? $this->cached_status_option : [];
		}

		$status = get_option( self::STATUS_OPTION, [] );
		$this->cached_status_option = is_array( $status ) ? $status : [];
		$this->cached_status_loaded = true;

		return $this->cached_status_option;
	}

	/**
	 * Persist license status and refresh the request-level cache.
	 *
	 * @param array<string,mixed> $status Status payload.
	 */
	private function persist_status_cache( array $status ): void {
		$this->cached_status_option = $status;
		$this->cached_status_loaded = true;
		update_option( self::STATUS_OPTION, $status, false );
	}

	/**
	 * Delete persisted license status and clear the request-level cache.
	 */
	private function clear_status_cache(): void {
		$this->cached_status_option = null;
		$this->cached_status_loaded = false;
		delete_option( self::STATUS_OPTION );
	}

	/**
	 * Invalidate WordPress plugin update metadata after license changes.
	 */
	private function flush_plugin_update_cache(): void {
		if ( class_exists( 'WPPS_License_Updater', false ) ) {
			WPPS_License_Updater::clear_update_caches();
			return;
		}

		delete_site_transient( 'update_plugins' );
		if ( class_exists( 'W_P_Power_Suite_Premium_Base', false ) ) {
			delete_transient( W_P_Power_Suite_Premium_Base::get_product_base() . '_up' );
		}
	}

	/**
	 * Check cached status freshness and binding.
	 *
	 * @param array<string,mixed> $cached Cached status.
	 */
	private function is_cache_fresh_for_current_site( array $cached ): bool {
		if ( empty( $cached['checked_at'] ) || empty( $cached['status'] ) ) {
			return false;
		}

		if ( ( $cached['site_hash'] ?? '' ) !== $this->get_site_hash() || ( $cached['license_hash'] ?? '' ) !== $this->get_license_hash() ) {
			return false;
		}

		return ( time() - (int) $cached['checked_at'] ) < self::CACHE_TTL;
	}

	/**
	 * Whether the license message should appear as an admin notice.
	 */
	public function should_show_license_notice(): bool {
		return ! $this->is_valid && $this->license_message !== '';
	}

	/**
	 * Whether a message indicates the remote server confirmed a valid license.
	 *
	 * UI helper only — never used to grant premium access.
	 */
	public function is_license_success_message( string $message ): bool {
		$message = strtolower( trim( $message ) );
		if ( $message === '' ) {
			return false;
		}

		foreach ( [ 'successfully verified', 'license is active', 'license activated', 'license valid', 'valid license' ] as $needle ) {
			if ( strpos( $message, $needle ) !== false ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Detect temporary connection/API failures without treating explicit invalid responses as grace.
	 */
	private function is_connection_failure( string $message ): bool {
		$message = strtolower( $message );
		if ( $message === '' ) {
			return true;
		}

		foreach ( [
			'timeout',
			'timed out',
			'connection refused',
			'connection reset',
			'connection error',
			'could not connect',
			'could not reach',
			'failed to connect',
			'unavailable',
			'temporarily',
			'ssl',
			'curl',
			'http error',
			'http request',
			'http status',
			'license server returned an empty response',
		] as $needle ) {
			if ( strpos( $message, $needle ) !== false ) {
				return true;
			}
		}

		if ( preg_match( '/\b(connection|connect)\b.*\b(fail|error|lost|issue|problem)\b/', $message ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Whether the Elite Licenser encrypted cache confirms prior activation for this site.
	 *
	 * Expired subscriptions remain entitled to premium features; use {@see is_current()} for updates.
	 */
	private function elite_cache_is_entitled( ?object $registered ): bool {
		if ( ! is_object( $registered ) || empty( $registered->is_valid ) || empty( $registered->license_key ) ) {
			return false;
		}

		return hash_equals( (string) $registered->license_key, $this->license_key );
	}

	/**
	 * Keep premium access when a previously activated license has expired.
	 *
	 * Handles both a locally expired cache and server rejections such as "renew needed"
	 * while the local cache still shows a future expiry date.
	 */
	private function apply_expired_entitlement_fallback( object $registered_before, string $server_message ): bool {
		$cache_expired  = $this->is_expired_response( $registered_before );
		$server_expired = W_P_Power_Suite_Premium_Base::message_indicates_expiration( $server_message );
		if ( ! $cache_expired && ! $server_expired ) {
			return false;
		}

		$this->is_valid     = true;
		$this->response_obj = $this->clone_register_response( $registered_before );
		if ( $server_expired && ! $this->is_expired_response( $this->response_obj ) ) {
			$this->response_obj->expire_date = gmdate( 'Y-m-d', time() - DAY_IN_SECONDS );
		}

		W_P_Power_Suite_Premium_Base::persist_register_info( $this->plugin_file, $this->response_obj );
		$this->show_message = false;
		$message            = $this->status_message_for( 'expired' );
		$this->status_data  = $this->build_status( 'expired', $message, $this->response_obj );
		$this->persist_status_cache( $this->status_data );

		return true;
	}

	/**
	 * Resolve entitlement status slug for UI and storage.
	 */
	private function resolve_entitlement_status( ?object $response, bool $offline ): string {
		if ( $offline ) {
			return 'grace';
		}

		if ( is_object( $response ) && $this->is_expired_response( $response ) ) {
			return 'expired';
		}

		return 'active';
	}

	/**
	 * Human-readable status message for entitlement states.
	 */
	private function status_message_for( string $status ): string {
		switch ( $status ) {
			case 'grace':
				return __( 'License server could not be reached. Premium features remain available from your local activation.', 'wp-powersuite' );
			case 'expired':
				return __( 'Your license has expired. Premium features remain available; renew to receive updates and support.', 'wp-powersuite' );
			case 'active':
				return __( 'License is active.', 'wp-powersuite' );
			default:
				return '';
		}
	}

	/**
	 * Clone a register response for UI use without internal cache metadata.
	 */
	private function clone_register_response( object $registered ): object {
		$response = clone $registered;
		unset( $response->next_request, $response->tried );

		return $response;
	}

	/**
	 * Remove UI cache rows written by older builds that could bypass validation.
	 */
	private function purge_legacy_ui_cache(): void {
		if ( get_option( 'wp_powersuite_license_legacy_ui_purged', false ) ) {
			return;
		}

		$cached = $this->get_cached_status();
		if ( empty( $cached ) ) {
			update_option( 'wp_powersuite_license_legacy_ui_purged', '1', false );
			return;
		}

		$status = (string) ( $cached['status'] ?? '' );
		if ( in_array( $status, [ 'active', 'grace', 'expired' ], true ) && ! $this->elite_cache_is_entitled( W_P_Power_Suite_Premium_Base::get_register_info( $this->plugin_file ) ) ) {
			$this->clear_status_cache();
		}

		update_option( 'wp_powersuite_license_legacy_ui_purged', '1', false );
	}

	/**
	 * Whether a cached license response has passed its expiry date.
	 */
	private function is_expired_response( object $response ): bool {
		if ( empty( $response->expire_date ) ) {
			return false;
		}

		$expire_str = strtolower( trim( (string) $response->expire_date ) );
		if ( in_array( $expire_str, [ 'no expiry', 'unlimited' ], true ) ) {
			return false;
		}

		$expire_ts = strtotime( (string) $response->expire_date );
		return $expire_ts !== false && $expire_ts < time();
	}

	/**
	 * Convert remote response details to a sanitized array.
	 *
	 * @param object|null $response Remote response object.
	 * @return array<string,string|bool>
	 */
	private function sanitize_response_details( ?object $response ): array {
		if ( ! is_object( $response ) ) {
			return [];
		}

		$fields = [ 'is_valid', 'license_title', 'extra_param', 'expire_date', 'expire_renew_link', 'support_end', 'support_renew_link', 'license_key', 'customer_name', 'customer_email', 'plan' ];
		$details = [];
		foreach ( $fields as $field ) {
			if ( ! isset( $response->{$field} ) ) {
				continue;
			}
			$value = $response->{$field};
			if ( is_bool( $value ) ) {
				$details[ $field ] = $value;
			} elseif ( in_array( $field, [ 'expire_renew_link', 'support_renew_link' ], true ) ) {
				$details[ $field ] = esc_url_raw( (string) $value );
			} else {
				$details[ $field ] = sanitize_text_field( (string) $value );
			}
		}

		return $details;
	}

	/**
	 * Convert sanitized details back to an object for existing views.
	 *
	 * @param mixed $details Cached response details.
	 */
	private function array_to_response_object( mixed $details ): ?object {
		if ( ! is_array( $details ) || empty( $details ) ) {
			return null;
		}

		return (object) $details;
	}

	/**
	 * Hash the license key for cache binding without storing it in status metadata.
	 */
	private function get_license_hash(): string {
		return hash_hmac( 'sha256', (string) $this->license_key, wp_salt( 'secure_auth' ) );
	}

	/**
	 * Get license page slug.
	 *
	 * @return string
	 */
	public static function get_license_page_slug(): string {
		return 'powersuite-license';
	}

	/**
	 * Get product/purchase URL.
	 *
	 * @return string
	 */
	public static function get_product_url(): string {
		$url = defined( 'WP_POWERSUITE_PRODUCT_URL' ) ? WP_POWERSUITE_PRODUCT_URL : 'https://wppowersuite.com/';
		return (string) apply_filters( 'wp_powersuite_product_url', $url );
	}

	/**
	 * Get renew URL.
	 *
	 * @return string
	 */
	public static function get_renew_url(): string {
		$url = defined( 'WP_POWERSUITE_RENEW_URL' ) ? WP_POWERSUITE_RENEW_URL : 'https://wppowersuite.com/renew';
		return (string) apply_filters( 'wp_powersuite_renew_url', $url );
	}

	/**
	 * Get support/documentation URL.
	 *
	 * @return string
	 */
	public static function get_support_url(): string {
		$url = defined( 'WP_POWERSUITE_SUPPORT_URL' ) ? WP_POWERSUITE_SUPPORT_URL : 'https://wppowersuite.com/support';
		return (string) apply_filters( 'wp_powersuite_support_url', $url );
	}
}
