<?php
namespace Pay4All\Pro\License;

if ( ! defined( 'ABSPATH' ) ) { exit; }

/**
 * Top-level « Pay4All PRO » menu with the Licences page as its landing
 * child. Positioned just below the other Pay4All-family top-level entries
 * (SHOP 80.1 / AGE 80.3 / STRIPE 80.7 / TWINT 81.5) at position 82.0 so
 * the sidebar reads Shop → Age → Stripe → Twint → Pro from top to bottom.
 *
 * The page renders :
 *   - Trial countdown or licence status summary.
 *   - Domain + installation UUID (advisory, non-editable).
 *   - « Activer » button — POSTs back, triggers register + activation-token
 *     request, then redirects the merchant to pay4all.net/checkout.
 *   - Return handshake : `?activation=success` on the URL runs an
 *     immediate check so the merchant sees « active » right after paying.
 */
final class AdminPage {

	public const SLUG_TOP = 'pay4all-pro';
	public const SLUG     = 'pay4all-pro-licenses';

	public static function register() : void {
		add_action( 'admin_menu', [ self::class, 'add_menu' ], 20 );
	}

	public static function bootstrap() : void {
		add_action( 'admin_init',    [ self::class, 'handle_post' ] );
		add_action( 'admin_notices', [ self::class, 'show_notices' ] );
	}

	public static function add_menu() : void {
		add_menu_page(
			__( 'Pay4All PRO', 'pay4all-pro' ),
			__( 'Pay4All PRO', 'pay4all-pro' ),
			'manage_options',
			self::SLUG_TOP,
			[ self::class, 'render' ],
			'dashicons-awards',
			// Between Pay4All TWINT (81.5) and any downstream companion —
			// keeps the whole Pay4All family visually grouped.
			82.0
		);

		// The « Licences » submenu shares the parent slug so clicking the
		// top-level entry lands directly on the licence page (WP core auto-
		// injects a submenu that mirrors the parent).
		add_submenu_page(
			self::SLUG_TOP,
			__( 'Licences', 'pay4all-pro' ),
			__( 'Licences', 'pay4all-pro' ),
			'manage_options',
			self::SLUG_TOP,
			[ self::class, 'render' ]
		);

		// Historical `pay4all-pro-licenses` slug kept as an orphan submenu so
		// any bookmark / notice link the merchant already saved keeps working.
		add_submenu_page(
			null,
			__( 'Licences', 'pay4all-pro' ),
			__( 'Licences', 'pay4all-pro' ),
			'manage_options',
			self::SLUG,
			[ self::class, 'render' ]
		);
	}

	public static function render() : void {
		if ( ! self::can_manage() ) {
			wp_die( esc_html__( 'Permission refusée.', 'pay4all-pro' ) );
		}

		// Auto-refresh from the SaaS whenever the merchant opens the page
		// so the status shown is never staler than the last page load. The
		// cost is one HTTP round-trip per admin visit — acceptable because
		// this screen is opened on demand, never on every admin page.
		// Silent failure : summary() below reads the cached wp_options so
		// the page still renders when the SaaS is unreachable.
		try {
			LicenseManager::instance()->run_periodic_check( true );
		} catch ( \Throwable $e ) {
			// Non-fatal — the network layer already logged into last_error.
		}

		$summary    = LicenseManager::summary();
		$snap       = Store::snapshot();
		$uuid       = $snap['installation_uuid'];
		$domain     = DomainNormalizer::current_domain();
		$action_url = admin_url( 'admin.php?page=' . self::SLUG_TOP );
		$last_check = Store::last_check_at();

		$level_bg = [
			'info'    => '#eaf5ff',
			'success' => '#e8f6e5',
			'warning' => '#fff8e5',
			'error'   => '#fdecea',
		];
		$bg = $level_bg[ $summary['level'] ] ?? '#f1f1f1';
		?>
		<div class="wrap">
			<h1><?php esc_html_e( 'Licences : Pay4All Pro', 'pay4all-pro' ); ?></h1>

			<?php if ( is_multisite() ) : ?>
				<div class="notice notice-warning"><p>
					<?php esc_html_e( 'WordPress Multisite n\'est pas pris en charge pour la gestion des licences Pay4All PRO en V1.', 'pay4all-pro' ); ?>
				</p></div>
			<?php endif; ?>

			<?php if ( $snap['previous_domain'] !== '' && $snap['previous_domain'] !== $domain ) : ?>
				<div class="notice notice-warning"><p>
					<?php
					echo wp_kses(
						sprintf(
							/* translators: %1$s = previous domain, %2$s = current domain */
							__( 'Le domaine détecté a changé (%1$s → %2$s). Réactivez votre licence pour rattacher le nouveau domaine.', 'pay4all-pro' ),
							'<code>' . esc_html( $snap['previous_domain'] ) . '</code>',
							'<code>' . esc_html( $domain ) . '</code>'
						),
						[ 'code' => [] ]
					);
					?>
				</p></div>
			<?php endif; ?>

			<div style="max-width:780px;background:<?php echo esc_attr( $bg ); ?>;border-left:4px solid #999;padding:14px 18px;margin:12px 0 24px;border-radius:2px;">
				<p style="margin:0;font-size:14px;">
					<strong><?php echo esc_html( $summary['message'] ); ?></strong>
				</p>
			</div>

			<table class="widefat striped" style="max-width:780px;">
				<tbody>
					<tr>
						<th style="width:220px;"><?php esc_html_e( 'Domaine', 'pay4all-pro' ); ?></th>
						<td><code><?php echo esc_html( $domain ); ?></code></td>
					</tr>
					<tr>
						<th><?php esc_html_e( 'Identifiant d\'installation', 'pay4all-pro' ); ?></th>
						<td><code><?php echo esc_html( $uuid ); ?></code></td>
					</tr>
					<tr>
						<th><?php esc_html_e( 'Statut', 'pay4all-pro' ); ?></th>
						<td>
							<?php
							echo esc_html( LicenseManager::status_label( $summary['status'] ) );
							// « Annulation en fin de période » — surface the
							// exact end-of-period date parsed from the SaaS-
							// signed valid_until so the merchant knows when
							// their access will drop.
							if ( $summary['status'] === 'cancel_at_period_end' ) {
								$validUntil = Store::valid_until();
								if ( $validUntil > 0 ) {
									$dateFmt = get_option( 'date_format', 'Y-m-d' );
									echo ' - ' . esc_html( wp_date( $dateFmt, $validUntil ) );
								}
							}
							?>
						</td>
					</tr>
					<?php
					// Subscription type — populated from /domains/check when
					// a paid or lifetime subscription is attached to the
					// domain. Cleared during pure trial / pending states so
					// we don't render an empty row on those.
					$plan_type_label = self::plan_type_label();
					if ( $plan_type_label !== '' ) : ?>
					<tr>
						<th><?php esc_html_e( 'Type d\'abonnement', 'pay4all-pro' ); ?></th>
						<td><?php echo esc_html( $plan_type_label ); ?></td>
					</tr>
					<?php endif; ?>
					<?php if ( $summary['status'] === 'trial' ) : ?>
					<tr>
						<th><?php esc_html_e( 'Jours restants (essai)', 'pay4all-pro' ); ?></th>
						<td><?php echo (int) $summary['days_left']; ?></td>
					</tr>
					<?php endif; ?>
					<tr>
						<th><?php esc_html_e( 'Dernière vérification', 'pay4all-pro' ); ?></th>
						<td><?php echo esc_html( $last_check ? self::fmt( $last_check ) : '—' ); ?></td>
					</tr>
					<tr>
						<th><?php esc_html_e( 'Version', 'pay4all-pro' ); ?></th>
						<?php
						// Reading the plugin file's own Version header — not the
						// shared P4ALL_PRO_VERSION constant which sibling PRO
						// plugins (e.g. pay4all-for-twint) also define. Whichever
						// plugin's autoloader wins the boot race gets to fix the
						// constant, so we would end up displaying the sibling's
						// version here instead of pay4all-pro's own.
						$p4all_pro_plugin_headers = get_file_data( P4ALL_PRO_FILE, [ 'Version' => 'Version' ] );
						?>
						<td><?php echo esc_html( (string) ( $p4all_pro_plugin_headers['Version'] ?? '' ) ); ?></td>
					</tr>
					<?php if ( $snap['last_error'] !== '' ) : ?>
						<tr>
							<th><?php esc_html_e( 'Dernière erreur', 'pay4all-pro' ); ?></th>
							<td><code style="color:#c0392b;"><?php echo esc_html( $snap['last_error'] ); ?></code></td>
						</tr>
					<?php endif; ?>
				</tbody>
			</table>

			<p style="margin-top:24px;display:flex;gap:.75rem;flex-wrap:wrap;">
				<?php
				// « Tester ma licence » — force an immediate SaaS check
				// (identical to what render() already does at page load,
				// but with an explicit success/error flash so the merchant
				// gets visible confirmation of the round-trip).
				?>
				<form method="post" action="<?php echo esc_url( $action_url ); ?>" style="display:inline-block;margin:0;">
					<?php wp_nonce_field( 'pay4all_pro_test', '_pay4all_pro_nonce' ); ?>
					<input type="hidden" name="pay4all_pro_action" value="test_license">
					<button type="submit" class="button button-primary">
						<?php esc_html_e( 'Tester et activer ma licence', 'pay4all-pro' ); ?>
					</button>
				</form>

				<?php
				// « Annuler mon abonnement » — self-service cancellation,
				// programmed for the end of the current period (merchant
				// keeps their access until the paid-for term runs out).
				// Hidden for trial / lifetime / already-scheduled / hard-
				// locked states — the SaaS would answer 409 anyway.
				$show_cancel = in_array(
					$summary['status'],
					[ 'active', 'trialing', 'past_due' ],
					true
				);
				if ( $show_cancel ) : ?>
					<form method="post" action="<?php echo esc_url( $action_url ); ?>" style="display:inline-block;margin:0;"
						  onsubmit="return confirm('<?php echo esc_js( __( 'Confirmez-vous l\'annulation de votre abonnement à la fin de la période en cours ? Vous conservez l\'accès jusqu\'à la date d\'échéance.', 'pay4all-pro' ) ); ?>');">
						<?php wp_nonce_field( 'pay4all_pro_cancel', '_pay4all_pro_nonce' ); ?>
						<input type="hidden" name="pay4all_pro_action" value="cancel_subscription">
						<button type="submit" class="button" style="color:#b32d2e;border-color:#b32d2e;">
							<?php esc_html_e( 'Annuler mon abonnement', 'pay4all-pro' ); ?>
						</button>
					</form>
				<?php endif; ?>

				<?php
				// « Réactiver mon abonnement » — mirror action to undo
				// a pending cancellation. Only visible when Stripe has
				// scheduled the sub for cancellation at period end ; a
				// single click restores normal billing (no confirmation
				// needed — reactivating is safe).
				if ( $summary['status'] === 'cancel_at_period_end' ) : ?>
					<form method="post" action="<?php echo esc_url( $action_url ); ?>" style="display:inline-block;margin:0;">
						<?php wp_nonce_field( 'pay4all_pro_reactivate', '_pay4all_pro_nonce' ); ?>
						<input type="hidden" name="pay4all_pro_action" value="reactivate_subscription">
						<button type="submit" class="button" style="color:#1a7a3a;border-color:#1a7a3a;background:transparent;">
							<?php esc_html_e( 'Réactiver mon abonnement', 'pay4all-pro' ); ?>
						</button>
					</form>
				<?php endif; ?>

				<?php
				// « Ajouter un abonnement » — shortcut to the SaaS
				// register / checkout page when the merchant has no
				// paid subscription attached (trial, pending, or hard-
				// locked). Green outline mirrors the « Réactiver »
				// style ; opens in a new tab.
				$has_paid_sub = in_array(
					$summary['status'],
					[ 'active', 'trialing', 'past_due', 'cancel_at_period_end' ],
					true
				);
				if ( ! $has_paid_sub ) : ?>
					<a href="<?php echo esc_url( self::register_url() ); ?>" target="_blank" rel="noopener noreferrer"
					   class="button" style="color:#1a7a3a;border-color:#1a7a3a;background:transparent;">
						<?php esc_html_e( 'Ajouter un abonnement', 'pay4all-pro' ); ?>
					</a>
				<?php endif; ?>

			</p>

			<p class="description" style="margin-top:14px;color:#555;max-width:780px;">
				<?php esc_html_e( 'Le rattachement d\'un domaine à un abonnement se fait depuis votre espace client Pay4All.net. Après paiement, la vérification automatique s\'exécute toutes les 48 heures depuis ce site.', 'pay4all-pro' ); ?>
				<br>
				<a href="<?php echo esc_url( self::login_url() ); ?>" target="_blank" rel="noopener noreferrer">
					<?php esc_html_e( 'Me connecter à mon compte Pay4All.net', 'pay4all-pro' ); ?> ↗
				</a>
			</p>
		</div>
		<?php
	}

	public static function handle_post() : void {
		// Return from the SaaS checkout — run an immediate check so the
		// merchant lands on the Licences page with the fresh status.
		if ( isset( $_GET['activation'] ) && $_GET['activation'] === 'success'
			&& isset( $_GET['page'] ) && sanitize_key( wp_unslash( (string) $_GET['page'] ) ) === self::SLUG_TOP
		) {
			if ( self::can_manage() ) {
				LicenseManager::instance()->run_periodic_check( true );
				self::flash( 'success', __( 'Merci : votre paiement a été enregistré. La licence sera confirmée dans quelques instants.', 'pay4all-pro' ) );
			}
		}

		if ( empty( $_POST['pay4all_pro_action'] ) ) { return; }
		if ( ! self::can_manage() ) { return; }

		$action = sanitize_text_field( wp_unslash( (string) $_POST['pay4all_pro_action'] ) );
		$lm     = LicenseManager::instance();

		// « Tester ma licence » — force a fresh SaaS round-trip and
		// surface the outcome as a flash notice so the merchant sees the
		// check actually happened (auto-refresh on page load is silent).
		if ( $action === 'test_license' ) {
			check_admin_referer( 'pay4all_pro_test', '_pay4all_pro_nonce' );
			$check = $lm->run_periodic_check( true );
			// On success, no flash notice — the licence card on the page
			// already reflects the fresh status and a repetitive
			// « Vérification effectuée — statut : … » banner adds noise.
			if ( empty( $check['ok'] ) ) {
				self::flash( 'error', sprintf(
					/* translators: %s = short error message */
					__( 'Vérification échouée : %s', 'pay4all-pro' ),
					self::error_message( $check )
				) );
			}
			wp_safe_redirect( admin_url( 'admin.php?page=' . self::SLUG_TOP ) );
			exit;
		}

		// « Réactiver mon abonnement » — undo a pending cancellation.
		// Stripe billing resumes normally on the next period. Safe to
		// re-submit : the SaaS returns the current state without
		// hitting Stripe again if already active.
		if ( $action === 'reactivate_subscription' ) {
			check_admin_referer( 'pay4all_pro_reactivate', '_pay4all_pro_nonce' );
			$reactivate = $lm->reactivate_subscription();
			if ( ! empty( $reactivate['ok'] ) ) {
				self::flash( 'success', __( 'Abonnement réactivé : la facturation reprend normalement à la prochaine période.', 'pay4all-pro' ) );
			} else {
				self::flash(
					'error',
					sprintf(
						/* translators: %s = short error message */
						__( 'Réactivation échouée : %s. Connectez-vous à votre compte Pay4All.net pour réactiver manuellement.', 'pay4all-pro' ),
						self::error_message( $reactivate )
					),
					self::login_url(),
					__( 'Me connecter à mon compte Pay4All.net', 'pay4all-pro' )
				);
			}
			wp_safe_redirect( admin_url( 'admin.php?page=' . self::SLUG_TOP ) );
			exit;
		}

		// « Annuler mon abonnement » — call the SaaS to schedule the
		// Stripe cancellation at period end. The merchant keeps their
		// access until the end of the current billing period ; we don't
		// need to force a lockout locally.
		if ( $action === 'cancel_subscription' ) {
			check_admin_referer( 'pay4all_pro_cancel', '_pay4all_pro_nonce' );
			$cancel = $lm->cancel_subscription();
			if ( ! empty( $cancel['ok'] ) ) {
				$validUntil = (string) ( $cancel['valid_until'] ?? '' );
				if ( $validUntil !== '' && ( $ts = strtotime( $validUntil ) ) ) {
					self::flash( 'success', sprintf(
						/* translators: %s = formatted end-of-period date */
						__( 'Abonnement annulé. Vous conservez l\'accès jusqu\'au %s.', 'pay4all-pro' ),
						self::fmt( (int) $ts )
					) );
				} else {
					self::flash( 'success', __( 'Abonnement annulé. Vous conservez l\'accès jusqu\'à la fin de la période en cours.', 'pay4all-pro' ) );
				}
			} else {
				self::flash(
					'error',
					sprintf(
						/* translators: %s = short error message */
						__( 'Annulation échouée : %s. Connectez-vous à votre compte Pay4All.net pour annuler manuellement.', 'pay4all-pro' ),
						self::error_message( $cancel )
					),
					self::login_url(),
					__( 'Me connecter à mon compte Pay4All.net', 'pay4all-pro' )
				);
			}
			wp_safe_redirect( admin_url( 'admin.php?page=' . self::SLUG_TOP ) );
			exit;
		}

		if ( $action !== 'activate' ) { return; }
		check_admin_referer( 'pay4all_pro_activate', '_pay4all_pro_nonce' );

		$reg = $lm->register_installation();
		if ( empty( $reg['ok'] ) ) {
			self::flash( 'error', sprintf(
				/* translators: %s = short error message */
				__( 'Impossible d\'enregistrer cette installation : %s', 'pay4all-pro' ),
				self::error_message( $reg )
			) );
			return;
		}

		// If the SaaS already knows the domain as active (e.g. re-activation
		// after uninstall), the periodic check will surface it without a
		// redirect.
		$check  = $lm->run_periodic_check( true );
		$status = (string) ( $check['status'] ?? '' );
		if ( ! empty( $check['ok'] ) && in_array( $status, Constants::LIVE_STATUSES, true ) ) {
			self::flash( 'success', sprintf(
				/* translators: %s = localised licence status label (e.g. "Active") */
				__( 'Licence activée. Statut : %s', 'pay4all-pro' ),
				LicenseManager::status_label( $status )
			) );
			return;
		}

		$url = $lm->request_activation_token();
		if ( $url !== null ) {
			self::redirect_to_saas( $url );
			exit;
		}

		self::flash( 'error', sprintf(
			/* translators: %s = short error message */
			__( 'Activation impossible : %s', 'pay4all-pro' ),
			self::error_message( $check )
		) );
	}

	public static function show_notices() : void {
		$flash = get_transient( Constants::FLASH_TRANSIENT );
		if ( ! is_array( $flash ) ) { return; }
		delete_transient( Constants::FLASH_TRANSIENT );
		$class    = ( $flash['type'] ?? '' ) === 'success' ? 'notice-success' : 'notice-error';
		$msg      = (string) ( $flash['msg'] ?? '' );
		$linkUrl  = (string) ( $flash['link_url'] ?? '' );
		$linkText = (string) ( $flash['link_text'] ?? '' );

		$link_html = '';
		if ( $linkUrl !== '' && $linkText !== '' ) {
			$link_html = ' <a href="' . esc_url( $linkUrl ) . '" target="_blank" rel="noopener noreferrer">'
				. esc_html( $linkText ) . ' ↗</a>';
		}
		printf(
			'<div class="notice %s is-dismissible"><p>%s%s</p></div>',
			esc_attr( $class ),
			esc_html( $msg ),
			// $link_html is fully built from escaped pieces above.
			$link_html // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		);
	}

	/**
	 * Records a flash notice for the next admin request.
	 *
	 * Optionally attaches a trailing anchor (URL + visible label) — used
	 * by failed cancel/reactivate flows to invite the merchant to their
	 * pay4all.net account where they can finish the operation manually.
	 */
	private static function flash( string $type, string $msg, ?string $linkUrl = null, ?string $linkText = null ) : void {
		$payload = [ 'type' => $type, 'msg' => $msg ];
		if ( $linkUrl !== null && $linkText !== null && $linkUrl !== '' && $linkText !== '' ) {
			$payload['link_url']  = $linkUrl;
			$payload['link_text'] = $linkText;
		}
		set_transient( Constants::FLASH_TRANSIENT, $payload, 60 );
	}

	/**
	 * wp_safe_redirect() rejects off-site hosts unless whitelisted; we add
	 * the SaaS host just long enough to bounce the user out.
	 */
	private static function redirect_to_saas( string $url ) : void {
		$host = wp_parse_url( $url, PHP_URL_HOST );
		if ( $host ) {
			add_filter( 'allowed_redirect_hosts', static function ( $hosts ) use ( $host ) {
				$hosts[] = $host;
				return $hosts;
			} );
		}
		wp_safe_redirect( $url );
	}

	private static function can_manage() : bool {
		return current_user_can( 'manage_options' );
	}

	private static function error_message( array $res ) : string {
		$raw = '';
		if ( ! empty( $res['error'] ) ) {
			$raw = (string) $res['error'];
		} elseif ( ! empty( $res['body']['error'] ) ) {
			$raw = (string) $res['body']['error'];
		}
		if ( $raw !== '' ) {
			return self::translate_saas_error( $raw );
		}
		if ( isset( $res['status'] ) && (int) $res['status'] > 0 ) {
			return 'HTTP ' . (int) $res['status'];
		}
		return __( 'erreur inconnue', 'pay4all-pro' );
	}

	/**
	 * Translates the machine-readable error codes returned by the
	 * SaaS API (or the ApiClient wrapper) into a merchant-friendly,
	 * localised message. Unknown codes fall through as-is so admins
	 * can still copy-paste them into a support ticket.
	 */
	private static function translate_saas_error( string $code ) : string {
		$map = [
			'no-domain'              => __( 'Domaine local introuvable.', 'pay4all-pro' ),
			'installation-not-attached' => __( 'Cette installation n\'est pas encore rattachée à un domaine dans votre compte Pay4All. Terminez d\'abord la souscription depuis pay4all.net.', 'pay4all-pro' ),
			'installation_uuid-taken' => __( 'Cet identifiant d\'installation est déjà enregistré. Désinstallez puis réinstallez le plugin pour générer un nouvel identifiant.', 'pay4all-pro' ),
			'server-exception'       => __( 'Erreur serveur interne côté Pay4All. Réessayez dans quelques minutes ou contactez le support.', 'pay4all-pro' ),
			'no-subscription'        => __( 'Aucun abonnement actif rattaché à ce domaine.', 'pay4all-pro' ),
			'no-stripe-sub'          => __( 'Cet abonnement n\'a pas de facturation Stripe active — rien à annuler.', 'pay4all-pro' ),
			'lifetime-cannot-cancel' => __( 'Cet abonnement est « Gratuit à vie » — il n\'y a pas de facturation à annuler.', 'pay4all-pro' ),
			'lifetime-nothing-to-reactivate' => __( 'Cet abonnement est « Gratuit à vie » — rien à réactiver.', 'pay4all-pro' ),
			'stripe-failed'          => __( 'La demande d\'annulation Stripe a échoué. Réessayez dans quelques minutes ou contactez le support.', 'pay4all-pro' ),
			'not-registered'         => __( 'Cette installation n\'est pas encore enregistrée auprès de Pay4All.', 'pay4all-pro' ),
			'auth'                   => __( 'Authentification refusée par Pay4All. Reconnectez-vous puis réessayez.', 'pay4all-pro' ),
			'rate-limited'           => __( 'Trop de tentatives — patientez quelques minutes avant de réessayer.', 'pay4all-pro' ),
			'invalid domain'         => __( 'Domaine invalide.', 'pay4all-pro' ),
		];
		return $map[ $code ] ?? $code;
	}

	private static function fmt( int $ts ) : string {
		return wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $ts );
	}

	/**
	 * Localised login URL for the merchant's Pay4All.net account,
	 * matching the two-letter code of the current WordPress admin
	 * locale (French fallback when the locale is anything else).
	 */
	private static function login_url() : string {
		$lang = function_exists( 'determine_locale' ) ? determine_locale() : get_locale();
		$lang = strtolower( substr( (string) $lang, 0, 2 ) );
		if ( ! in_array( $lang, [ 'fr', 'de', 'it', 'en' ], true ) ) {
			$lang = 'fr';
		}
		return 'https://pay4all.net/login?lang=' . $lang;
	}

	/**
	 * Localised sign-up URL for a merchant who wants to add a paid
	 * subscription. Same locale detection as login_url().
	 *
	 * Also forwards the current installation_uuid so the SaaS can
	 * pre-fill the « Identifiant d'installation » field on the domain
	 * add form — the merchant no longer has to copy/paste it manually.
	 */
	private static function register_url() : string {
		$lang = function_exists( 'determine_locale' ) ? determine_locale() : get_locale();
		$lang = strtolower( substr( (string) $lang, 0, 2 ) );
		if ( ! in_array( $lang, [ 'fr', 'de', 'it', 'en' ], true ) ) {
			$lang = 'fr';
		}
		$query = [
			'lang'            => $lang,
			'installation_id' => Store::installation_uuid(),
		];
		// Also forward the current normalized domain so the SaaS can
		// pre-fill both the « Domaine » field AND the « Identifiant
		// d'installation » field on the add-domain form. The domain is
		// resolved via LicenseManager to reuse the same normalisation
		// (WWW/staging/https trimming) as /domains/check.
		$normalized = ( LicenseManager::instance() )->current_domain();
		if ( is_string( $normalized ) && $normalized !== '' ) {
			$query['domain'] = $normalized;
		}
		return 'https://pay4all.net/register?' . http_build_query( $query );
	}

	/**
	 * Localised label for the subscription type, based on the mirror
	 * fields the SaaS returns in /domains/check. Returns an empty
	 * string when no paid/lifetime sub is attached to this domain —
	 * the AdminPage skips the row in that case.
	 *
	 * Formats :
	 *   « Gratuit à vie »
	 *   « Mensuel — 9.90 CHF »   (amount only shown when known)
	 *   « Annuel »
	 */
	private static function plan_type_label() : string {
		if ( Store::is_lifetime() ) {
			return __( 'Gratuit à vie', 'pay4all-pro' );
		}
		$interval = Store::plan_interval();
		if ( $interval === '' ) {
			return '';
		}
		$intervalLabels = [
			'month' => __( 'Mensuel', 'pay4all-pro' ),
			'year'  => __( 'Annuel', 'pay4all-pro' ),
			'week'  => __( 'Hebdomadaire', 'pay4all-pro' ),
			'day'   => __( 'Quotidien', 'pay4all-pro' ),
		];
		$label = $intervalLabels[ $interval ] ?? ucfirst( $interval );

		$cents    = Store::plan_amount_cents();
		$currency = strtoupper( Store::plan_currency() );
		if ( $cents > 0 && $currency !== '' ) {
			$label .= ' — ' . number_format_i18n( $cents / 100, 2 ) . ' ' . $currency;
		}
		return $label;
	}
}
