<?php
/**
 * Plugin settings page.
 *
 * @package SubmissionInbox
 */

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

/**
 * Renders and saves plugin settings.
 */
class SUBIN_SettingsPage {
	const NONCE_ACTION = 'subin_save_privacy_settings';
	const NONCE_NAME   = 'subin_privacy_settings_nonce';

	/**
	 * Settings repository.
	 *
	 * @var SUBIN_SettingsRepository
	 */
	private $settings;

	/**
	 * SMTP settings page.
	 *
	 * @var SUBIN_SmtpSettingsPage
	 */
	private $smtp_page;

	/**
	 * Whether state-changing request handling has already run.
	 *
	 * @var bool
	 */
	private $request_handled = false;

	/**
	 * Constructor.
	 *
	 * @param SUBIN_SettingsRepository $settings Settings repository.
	 */
	public function __construct( SUBIN_SettingsRepository $settings ) {
		$this->settings  = $settings;
		$this->smtp_page = new SUBIN_SmtpSettingsPage( $settings );
	}

	/**
	 * Render page.
	 *
	 * @return void
	 */
	public function render() {
		if ( ! current_user_can( SUBIN_AdminMenu::CAPABILITY ) ) {
			wp_die( esc_html__( 'You do not have permission to perform this action.', 'submission-inbox' ) );
		}

		$active_tab = $this->active_tab();
		$this->handle_request();
		$settings = $this->settings->all();

		SUBIN_View::page_header(
			__( 'Settings', 'submission-inbox' ),
			__( 'Manage sending, lead storage, privacy, integrations, and uninstall behavior.', 'submission-inbox' ),
			array( 'wrapper_class' => 'subin-settings-page' )
		);

		SUBIN_AdminNotices::render_settings_errors( 'subin_messages' );
		$this->render_tabs( $active_tab );
		?>
		<div class="subin-settings-panel">
			<?php
			if ( 'sending' === $active_tab ) {
				$this->smtp_page->render_embedded();
			} elseif ( 'integrations' === $active_tab ) {
				$this->render_integrations_tab( $settings );
			} else {
				$this->render_settings_form( $active_tab, $settings );
			}
			?>
		</div>
		<?php
		SUBIN_View::page_footer();
	}

	/**
	 * Handle settings submissions before admin HTML is sent.
	 *
	 * @return void
	 */
	public function handle_request() {
		if ( $this->request_handled ) {
			return;
		}

		$this->request_handled = true;

		if ( ! current_user_can( SUBIN_AdminMenu::CAPABILITY ) ) {
			wp_die( esc_html__( 'You do not have permission to perform this action.', 'submission-inbox' ) );
		}

		$this->smtp_page->maybe_handle_post();
		$this->maybe_save();
	}

	/**
	 * Current settings tab.
	 *
	 * @return string
	 */
	private function active_tab() {
		$tab = SUBIN_Request::get_key( 'subin_settings_tab', 'sending' );
		return array_key_exists( $tab, $this->tabs() ) ? $tab : 'sending';
	}

	/**
	 * Settings tabs.
	 *
	 * @return array<string,string>
	 */
	private function tabs() {
		return array(
			'sending'       => __( 'Sending', 'submission-inbox' ),
			'lead_storage'  => __( 'Lead storage', 'submission-inbox' ),
			'privacy'       => __( 'Privacy', 'submission-inbox' ),
			'spam_protection' => __( 'Spam protection', 'submission-inbox' ),
			'integrations'  => __( 'Integrations', 'submission-inbox' ),
			'cloud_sync'    => __( 'Cloud Sync', 'submission-inbox' ),
			'uninstall'     => __( 'Uninstall', 'submission-inbox' ),
		);
	}

	/**
	 * Render tabs.
	 *
	 * @param string $active_tab Active tab.
	 * @return void
	 */
	private function render_tabs( $active_tab ) {
		?>
		<nav class="subin-settings-tabs" aria-label="<?php echo esc_attr__( 'Settings sections', 'submission-inbox' ); ?>">
			<?php foreach ( $this->tabs() as $key => $label ) : ?>
				<a class="subin-settings-tab<?php echo $key === $active_tab ? ' subin-settings-tab--active' : ''; ?>" href="<?php echo esc_url( $this->tab_url( $key ) ); ?>"<?php echo $key === $active_tab ? ' aria-current="page"' : ''; ?>>
					<?php echo esc_html( $label ); ?>
				</a>
			<?php endforeach; ?>
		</nav>
		<?php
	}

	/**
	 * Render non-SMTP settings form for one tab.
	 *
	 * @param string              $tab Active tab.
	 * @param array<string,mixed> $settings Settings.
	 * @return void
	 */
	private function render_settings_form( $tab, array $settings ) {
		?>
		<form method="post" action="">
			<input type="hidden" name="subin_settings_action" value="<?php echo esc_attr( $tab ); ?>" />
			<?php wp_nonce_field( self::NONCE_ACTION, self::NONCE_NAME ); ?>
			<?php
			if ( 'lead_storage' === $tab ) {
				$this->render_lead_storage_tab( $settings );
			} elseif ( 'privacy' === $tab ) {
				$this->render_privacy_tab( $settings );
			} elseif ( 'spam_protection' === $tab ) {
				$this->render_spam_protection_tab( $settings );
			} elseif ( 'cloud_sync' === $tab ) {
				$this->render_cloud_sync_tab( $settings );
			} elseif ( 'uninstall' === $tab ) {
				$this->render_uninstall_tab( $settings );
			}
			if ( 'cloud_sync' !== $tab ) {
				submit_button( __( 'Save settings', 'submission-inbox' ) );
			}
			?>
		</form>
		<?php
	}

	/**
	 * Lead storage tab.
	 *
	 * @param array<string,mixed> $settings Settings.
	 * @return void
	 */
	private function render_lead_storage_tab( array $settings ) {
		?>
		<div class="subin-settings-section">
			<h2>
				<?php esc_html_e( 'Lead storage', 'submission-inbox' ); ?>
				<?php SUBIN_View::help_tooltip( __( 'When enabled, supported form submissions are saved in InboxMend.', 'submission-inbox' ) ); ?>
			</h2>
			<p><?php esc_html_e( 'When enabled, InboxMend saves supported form submissions in the WordPress database.', 'submission-inbox' ); ?></p>
			<table class="form-table" role="presentation">
				<tbody>
					<tr>
						<th scope="row"><?php esc_html_e( 'Store form leads', 'submission-inbox' ); ?></th>
						<td>
							<input type="hidden" name="store_leads" value="0" />
							<?php SUBIN_View::checkbox_field( 'store_leads', __( 'Store captured form submissions in the leads inbox', 'submission-inbox' ), (bool) $settings['store_leads'] ); ?>
						</td>
					</tr>
					<tr>
						<th scope="row">
							<label for="subin-retention-days"><?php esc_html_e( 'Retention period', 'submission-inbox' ); ?></label>
							<?php SUBIN_View::help_tooltip( __( 'Automatically removes saved leads after the selected period.', 'submission-inbox' ) ); ?>
						</th>
						<td>
							<select id="subin-retention-days" name="retention_days">
								<?php foreach ( array( 30, 60, 90, 180, 365 ) as $days ) : ?>
									<option value="<?php echo esc_attr( (string) $days ); ?>" <?php selected( absint( $settings['retention_days'] ), $days ); ?>>
										<?php
										printf(
											/* translators: %d: number of retention days. */
											esc_html__( '%d days', 'submission-inbox' ),
											absint( $days )
										);
										?>
									</option>
								<?php endforeach; ?>
								<option value="0" <?php selected( absint( $settings['retention_days'] ), 0 ); ?>><?php esc_html_e( 'Forever', 'submission-inbox' ); ?></option>
							</select>
						</td>
					</tr>
					<tr>
						<th scope="row">
							<label for="subin-new-lead-hours"><?php esc_html_e( 'New lead period', 'submission-inbox' ); ?></label>
						</th>
						<td>
							<select id="subin-new-lead-hours" name="new_lead_hours">
								<?php foreach ( array( 6, 12, 24, 48, 72, 168 ) as $hours ) : ?>
									<option value="<?php echo esc_attr( (string) $hours ); ?>" <?php selected( absint( $settings['new_lead_hours'] ?? 24 ), $hours ); ?>>
										<?php
										if ( $hours < 24 ) {
											printf(
												/* translators: %d: number of hours. */
												esc_html__( '%d hours', 'submission-inbox' ),
												absint( $hours )
											);
										} else {
											printf(
												/* translators: %d: number of days. */
												esc_html( _n( '%d day', '%d days', (int) ( $hours / 24 ), 'submission-inbox' ) ),
												absint( $hours / 24 )
											);
										}
										?>
									</option>
								<?php endforeach; ?>
							</select>
							<p class="description"><?php esc_html_e( 'Inbox summary counts leads as new only inside this window. Seen/unseen status is tracked separately.', 'submission-inbox' ); ?></p>
						</td>
					</tr>
				</tbody>
			</table>
		</div>
		<?php
	}

	/**
	 * Privacy tab.
	 *
	 * @param array<string,mixed> $settings Settings.
	 * @return void
	 */
	private function render_privacy_tab( array $settings ) {
		?>
		<div class="subin-settings-section">
			<h2>
				<?php esc_html_e( 'Privacy', 'submission-inbox' ); ?>
				<?php SUBIN_View::help_tooltip( __( 'Controls whether optional request metadata is stored.', 'submission-inbox' ) ); ?>
			</h2>
			<p><?php esc_html_e( 'Sensitive fields such as passwords, card fields, captcha tokens, and security tokens are not stored by default.', 'submission-inbox' ); ?></p>
			<table class="form-table" role="presentation">
				<tbody>
					<tr>
						<th scope="row"><?php esc_html_e( 'Store IP address', 'submission-inbox' ); ?></th>
						<td>
							<input type="hidden" name="store_ip" value="0" />
							<?php SUBIN_View::checkbox_field( 'store_ip', __( 'Store IP-derived data when enabled', 'submission-inbox' ), (bool) $settings['store_ip'] ); ?>
						</td>
					</tr>
					<tr>
						<th scope="row"><?php esc_html_e( 'Store user agent', 'submission-inbox' ); ?></th>
						<td>
							<input type="hidden" name="store_user_agent" value="0" />
							<?php SUBIN_View::checkbox_field( 'store_user_agent', __( 'Store user-agent-derived data when enabled', 'submission-inbox' ), (bool) $settings['store_user_agent'] ); ?>
						</td>
					</tr>
					<tr>
						<th scope="row"><?php esc_html_e( 'Mask sensitive fields', 'submission-inbox' ); ?></th>
						<td>
							<input type="hidden" name="mask_sensitive_fields" value="0" />
							<?php SUBIN_View::checkbox_field( 'mask_sensitive_fields', __( 'Mask sensitive form fields in administrative views', 'submission-inbox' ), (bool) $settings['mask_sensitive_fields'] ); ?>
						</td>
					</tr>
				</tbody>
			</table>
		</div>
		<?php
	}

	/**
	 * Integrations tab.
	 *
	 * @return void
	 */
	private function render_integrations_tab( array $settings ) {
		$cf7_active     = \SUBIN\Forms\ContactForm7Integration::is_available();
		$wpforms_active = \SUBIN\Forms\WPFormsIntegration::is_available();
		$fluent_active  = \SUBIN\Forms\FluentFormsIntegration::is_available();
		$forminator_active = \SUBIN\Forms\ForminatorIntegration::is_available();
		$ninja_forms_active = \SUBIN\Forms\NinjaFormsIntegration::is_available();
		?>
		<form method="post" action="">
			<input type="hidden" name="subin_settings_action" value="integrations" />
			<?php wp_nonce_field( self::NONCE_ACTION, self::NONCE_NAME ); ?>
			<div class="subin-settings-section">
				<h2><?php esc_html_e( 'Integrations', 'submission-inbox' ); ?></h2>
				<p><?php esc_html_e( 'Enable compatibility only for the form plugins you want InboxMend to capture.', 'submission-inbox' ); ?></p>
				<div class="subin-integration-list">
					<?php
					$this->render_integration_card(
						__( 'Contact Form 7 compatibility', 'submission-inbox' ),
						$cf7_active,
						'enable_cf7_integration',
						(bool) $settings['enable_cf7_integration'],
						$cf7_active ? __( 'InboxMend can save successful Contact Form 7 submissions in InboxMend.', 'submission-inbox' ) : __( 'Install and activate Contact Form 7 to capture Contact Form 7 submissions.', 'submission-inbox' )
					);
					$this->render_integration_card(
						__( 'WPForms compatibility', 'submission-inbox' ),
						$wpforms_active,
						'enable_wpforms_integration',
						(bool) $settings['enable_wpforms_integration'],
						$wpforms_active ? __( 'InboxMend can save successful WPForms submissions in InboxMend.', 'submission-inbox' ) : __( 'Install and activate WPForms to capture WPForms submissions.', 'submission-inbox' )
					);
					$this->render_integration_card(
						__( 'Fluent Forms compatibility', 'submission-inbox' ),
						$fluent_active,
						'enable_fluent_forms_integration',
						(bool) $settings['enable_fluent_forms_integration'],
						$fluent_active ? __( 'InboxMend can save successful Fluent Forms submissions in InboxMend.', 'submission-inbox' ) : __( 'Install and activate Fluent Forms to capture Fluent Forms submissions.', 'submission-inbox' )
					);
					$this->render_integration_card(
						__( 'Forminator compatibility', 'submission-inbox' ),
						$forminator_active,
						'enable_forminator_integration',
						(bool) $settings['enable_forminator_integration'],
						$forminator_active ? __( 'InboxMend can save successful Forminator submissions in InboxMend.', 'submission-inbox' ) : __( 'Install and activate Forminator to capture Forminator submissions.', 'submission-inbox' )
					);
					$this->render_integration_card(
						__( 'Ninja Forms compatibility', 'submission-inbox' ),
						$ninja_forms_active,
						'enable_ninja_forms_integration',
						(bool) $settings['enable_ninja_forms_integration'],
						$ninja_forms_active ? __( 'InboxMend can save successful Ninja Forms submissions in InboxMend.', 'submission-inbox' ) : __( 'Install and activate Ninja Forms to capture Ninja Forms submissions.', 'submission-inbox' )
					);
					$this->render_planned_integration_card(
						__( 'Gravity Forms compatibility', 'submission-inbox' ),
						__( 'Gravity Forms compatibility is planned for a future update.', 'submission-inbox' )
					);
					?>
				</div>
			</div>
			<?php submit_button( __( 'Save settings', 'submission-inbox' ) ); ?>
		</form>
		<?php
	}

	/**
	 * Spam protection tab.
	 *
	 * @param array<string,mixed> $settings Settings.
	 * @return void
	 */
	private function render_spam_protection_tab( array $settings ) {
		?>
		<div class="subin-settings-section">
			<h2><?php esc_html_e( 'Google reCAPTCHA', 'submission-inbox' ); ?></h2>
			<p><?php esc_html_e( 'Use Google reCAPTCHA v3 to verify built-in InboxMend form submissions before leads are saved.', 'submission-inbox' ); ?></p>
			<?php if ( ! empty( $settings['recaptcha_enabled'] ) && ( '' === trim( (string) $settings['recaptcha_site_key'] ) || '' === trim( (string) $settings['recaptcha_secret_key'] ) ) ) : ?>
				<div class="notice notice-warning inline subin-settings-warning">
					<p><?php esc_html_e( 'Google reCAPTCHA is enabled, but the Site Key or Secret Key is missing.', 'submission-inbox' ); ?></p>
				</div>
			<?php endif; ?>
			<table class="form-table" role="presentation">
				<tbody>
					<tr>
						<th scope="row"><?php esc_html_e( 'Enable Google reCAPTCHA', 'submission-inbox' ); ?></th>
						<td>
							<input type="hidden" name="recaptcha_enabled" value="0" />
							<?php SUBIN_View::checkbox_field( 'recaptcha_enabled', __( 'Verify built-in form submissions with Google reCAPTCHA v3 before saving leads.', 'submission-inbox' ), (bool) $settings['recaptcha_enabled'] ); ?>
						</td>
					</tr>
					<tr>
						<th scope="row">
							<label for="subin-recaptcha-site-key"><?php esc_html_e( 'Site Key', 'submission-inbox' ); ?></label>
						</th>
						<td>
							<input id="subin-recaptcha-site-key" class="regular-text" type="text" name="recaptcha_site_key" value="<?php echo esc_attr( $settings['recaptcha_site_key'] ); ?>" autocomplete="off" />
							<p class="description"><?php esc_html_e( 'Used on the frontend to request a reCAPTCHA token.', 'submission-inbox' ); ?></p>
						</td>
					</tr>
					<tr>
						<th scope="row">
							<label for="subin-recaptcha-secret-key"><?php esc_html_e( 'Secret Key', 'submission-inbox' ); ?></label>
						</th>
						<td>
							<input id="subin-recaptcha-secret-key" class="regular-text" type="password" name="recaptcha_secret_key" value="<?php echo esc_attr( $settings['recaptcha_secret_key'] ); ?>" autocomplete="off" />
							<p class="description"><?php esc_html_e( 'Used server-side to verify the reCAPTCHA token with Google.', 'submission-inbox' ); ?></p>
						</td>
					</tr>
					<tr>
						<th scope="row">
							<label for="subin-recaptcha-score-threshold"><?php esc_html_e( 'Score threshold', 'submission-inbox' ); ?></label>
						</th>
						<td>
							<input id="subin-recaptcha-score-threshold" type="number" name="recaptcha_score_threshold" value="<?php echo esc_attr( (string) $settings['recaptcha_score_threshold'] ); ?>" min="0" max="1" step="0.1" />
							<p class="description"><?php esc_html_e( 'Submissions below this score are blocked. Higher values are stricter.', 'submission-inbox' ); ?></p>
						</td>
					</tr>
				</tbody>
			</table>
		</div>
		<?php
	}

	/**
	 * Cloud sync tab.
	 *
	 * @param array<string,mixed> $settings Settings.
	 * @return void
	 */
	private function render_cloud_sync_tab( array $settings ) {
		$identity = ( new \SUBIN\Cloud\SiteIdentity() )->get();
		$status   = sanitize_key( (string) ( $settings['cloud_sync_connection_status'] ?? $identity['connection_status'] ?? 'disconnected' ) );
		$has_token = ! empty( $settings['cloud_sync_site_token'] );
		$connected = $has_token && 'connected' === $status;
		$push_transport = 'push' === sanitize_key( (string) ( $settings['cloud_sync_transport'] ?? 'polling' ) );
		$site_url = home_url( '/' );
		$cloud_site_id = absint( $settings['cloud_sync_site_id'] ?? 0 );
		$view_cloud_url = $cloud_site_id > 0 ? $this->cloud_dashboard_site_url( $cloud_site_id ) : '';
		$sync_events = new \SUBIN\Sync\SyncEventRepository();
		$queue_counts = $sync_events->count_all_by_statuses();
		$recent_events = $sync_events->recent( 10 );
		$effective_mode = $settings['cloud_sync_allowed_mode'] ? $settings['cloud_sync_allowed_mode'] : $settings['cloud_sync_data_mode'];
		$last_seen = $settings['cloud_sync_last_heartbeat_at'] ? $settings['cloud_sync_last_heartbeat_at'] : __( 'Never', 'submission-inbox' );
		?>
		<div class="subin-settings-section subin-cloud-connector">
			<div class="subin-cloud-connector__header">
				<div>
					<p class="subin-cloud-connector__eyebrow"><?php esc_html_e( 'InboxMend Cloud', 'submission-inbox' ); ?></p>
					<h2><?php esc_html_e( 'Cloud sync', 'submission-inbox' ); ?></h2>
					<p><?php esc_html_e( 'Connect this WordPress site to InboxMend Cloud and control what data is shared.', 'submission-inbox' ); ?></p>
				</div>
				<?php SUBIN_View::badge( $connected ? __( 'Connected', 'submission-inbox' ) : $this->cloud_status_label( $status ), $connected ? 'success' : $this->cloud_status_badge_type( $status ) ); ?>
			</div>

			<div class="subin-cloud-status-row">
				<div class="subin-cloud-status-card">
					<span><?php esc_html_e( 'Connection', 'submission-inbox' ); ?></span>
					<strong><?php echo esc_html( $connected ? __( 'Active', 'submission-inbox' ) : $this->cloud_status_label( $status ) ); ?></strong>
					<small><?php echo esc_html( ! empty( $settings['cloud_sync_polling_enabled'] ) ? __( 'Cloud polling enabled', 'submission-inbox' ) : __( 'Waiting for Cloud polling', 'submission-inbox' ) ); ?></small>
				</div>
				<div class="subin-cloud-status-card">
					<span><?php esc_html_e( 'Sync mode', 'submission-inbox' ); ?></span>
					<strong><?php echo esc_html( $this->cloud_sync_mode_label( $effective_mode ) ); ?></strong>
					<small><?php esc_html_e( 'Sensitive fields are redacted before sync.', 'submission-inbox' ); ?></small>
				</div>
				<div class="subin-cloud-status-card">
					<span><?php esc_html_e( 'Queue', 'submission-inbox' ); ?></span>
					<strong><?php echo esc_html( sprintf( __( '%1$d pending', 'submission-inbox' ), absint( $queue_counts['pending'] ?? 0 ) ) ); ?></strong>
					<small><?php echo esc_html( sprintf( __( '%1$d failed, %2$d sent', 'submission-inbox' ), absint( $queue_counts['failed'] ?? 0 ), absint( $queue_counts['sent'] ?? 0 ) ) ); ?></small>
				</div>
				<div class="subin-cloud-status-card">
					<span><?php esc_html_e( 'Last check', 'submission-inbox' ); ?></span>
					<strong><?php echo esc_html( $last_seen ); ?></strong>
					<small><?php esc_html_e( 'Updated by heartbeat or polling.', 'submission-inbox' ); ?></small>
				</div>
			</div>

			<?php if ( ! empty( $settings['cloud_sync_last_sync_error'] ) ) : ?>
				<div class="subin-cloud-error-card">
					<strong><?php esc_html_e( 'Connection issue', 'submission-inbox' ); ?></strong>
					<span><?php echo esc_html( $this->cloud_friendly_error_message( (string) $settings['cloud_sync_last_sync_error'] ) ); ?></span>
				</div>
			<?php endif; ?>

			<?php $this->render_cloud_action_notice(); ?>

			<div class="subin-cloud-layout">
				<div class="subin-cloud-panel">
					<div class="subin-cloud-panel__header">
						<div>
							<h3><?php esc_html_e( 'Connection key', 'submission-inbox' ); ?></h3>
							<p><?php esc_html_e( 'Use the one-time key generated in InboxMend Cloud for this site.', 'submission-inbox' ); ?></p>
						</div>
					</div>

					<div class="subin-cloud-key-row">
						<label class="screen-reader-text" for="subin-cloud-sync-site-key"><?php esc_html_e( 'Site connection key', 'submission-inbox' ); ?></label>
						<input id="subin-cloud-sync-site-key" type="text" name="cloud_sync_site_key" value="<?php echo esc_attr( $settings['cloud_sync_site_key'] ? $settings['cloud_sync_site_key'] : $settings['cloud_sync_license_key'] ); ?>" autocomplete="off" placeholder="site_xxxxxxxxxxxxx" />
						<button type="submit" name="subin_cloud_action" value="connect" class="button button-primary"><?php echo esc_html( $connected ? __( 'Reconnect', 'submission-inbox' ) : __( 'Connect', 'submission-inbox' ) ); ?></button>
					</div>

					<?php if ( $has_token ) : ?>
						<div class="subin-cloud-actions">
							<button type="submit" name="subin_cloud_action" value="validate" class="button"><?php esc_html_e( 'Test connection', 'submission-inbox' ); ?></button>
							<?php if ( $view_cloud_url ) : ?>
								<a class="button" href="<?php echo esc_url( $view_cloud_url ); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Open Cloud', 'submission-inbox' ); ?></a>
							<?php endif; ?>
							<button type="submit" name="subin_cloud_action" value="rotate" class="button"><?php esc_html_e( 'Rotate token', 'submission-inbox' ); ?></button>
							<button type="submit" name="subin_cloud_action" value="revoke" class="button button-link-delete"><?php esc_html_e( 'Disconnect', 'submission-inbox' ); ?></button>
							<?php if ( $push_transport ) : ?>
								<button type="submit" name="subin_cloud_action" value="heartbeat" class="button"><?php esc_html_e( 'Send heartbeat', 'submission-inbox' ); ?></button>
								<button type="submit" name="subin_cloud_action" value="sync" class="button"><?php esc_html_e( 'Send pending now', 'submission-inbox' ); ?></button>
								<button type="submit" name="subin_cloud_action" value="retry_failed" class="button"<?php disabled( empty( $queue_counts['failed'] ) ); ?>><?php esc_html_e( 'Retry failed events', 'submission-inbox' ); ?></button>
							<?php endif; ?>
						</div>
					<?php endif; ?>
				</div>

				<div class="subin-cloud-panel">
					<div class="subin-cloud-panel__header">
						<div>
							<h3><?php esc_html_e( 'Data sharing', 'submission-inbox' ); ?></h3>
							<p><?php esc_html_e( 'Choose how much lead and email context Cloud may read from this site.', 'submission-inbox' ); ?></p>
						</div>
					</div>

					<div class="subin-cloud-field">
						<label for="subin-cloud-sync-data-mode"><?php esc_html_e( 'Requested sync mode', 'submission-inbox' ); ?></label>
						<select id="subin-cloud-sync-data-mode" name="cloud_sync_data_mode">
							<option value="health_only" <?php selected( $settings['cloud_sync_data_mode'], 'health_only' ); ?>><?php esc_html_e( 'Health only', 'submission-inbox' ); ?></option>
							<option value="lead_summary" <?php selected( $settings['cloud_sync_data_mode'], 'lead_summary' ); ?>><?php esc_html_e( 'Lead summary', 'submission-inbox' ); ?></option>
							<option value="full_lead_data" <?php selected( $settings['cloud_sync_data_mode'], 'full_lead_data' ); ?>><?php esc_html_e( 'Full lead data', 'submission-inbox' ); ?></option>
						</select>
						<?php if ( ! empty( $settings['cloud_sync_allowed_mode'] ) ) : ?>
							<p class="description"><?php printf( esc_html__( 'Cloud currently allows: %s', 'submission-inbox' ), esc_html( $this->cloud_sync_mode_label( $settings['cloud_sync_allowed_mode'] ) ) ); ?></p>
						<?php endif; ?>
					</div>

					<div class="subin-cloud-consent">
						<input type="hidden" name="cloud_sync_full_data_consent" value="0" />
						<?php SUBIN_View::checkbox_field( 'cloud_sync_full_data_consent', __( 'Allow Full lead data sync for this site', 'submission-inbox' ), (bool) $settings['cloud_sync_full_data_consent'] ); ?>
						<p class="description"><?php esc_html_e( 'Full lead data can include names, email addresses, phone numbers, message text, source URLs, workflow data, notes, and email event status. Sensitive fields are redacted before sync.', 'submission-inbox' ); ?></p>
					</div>

					<button type="submit" name="subin_cloud_action" value="save" class="button"><?php esc_html_e( 'Save sync preferences', 'submission-inbox' ); ?></button>
				</div>
			</div>

			<details class="subin-cloud-diagnostics">
				<summary><?php esc_html_e( 'Diagnostics', 'submission-inbox' ); ?></summary>
				<div class="subin-cloud-diagnostics__grid">
					<div>
						<span><?php esc_html_e( 'WordPress REST API', 'submission-inbox' ); ?></span>
						<?php SUBIN_View::badge( __( 'Available', 'submission-inbox' ), 'success' ); ?>
					</div>
					<div>
						<span><?php esc_html_e( 'Site URL', 'submission-inbox' ); ?></span>
						<code><?php echo esc_html( $site_url ); ?></code>
					</div>
					<div>
						<span><?php esc_html_e( 'Site UUID', 'submission-inbox' ); ?></span>
						<code><?php echo esc_html( $identity['site_uuid'] ); ?></code>
					</div>
					<div>
						<span><?php esc_html_e( 'Cloud site ID', 'submission-inbox' ); ?></span>
						<code><?php echo esc_html( $cloud_site_id ? (string) $cloud_site_id : __( 'Pending', 'submission-inbox' ) ); ?></code>
					</div>
					<div>
						<span><?php esc_html_e( 'Plugin version', 'submission-inbox' ); ?></span>
						<code><?php echo esc_html( defined( 'SUBIN_PLUGIN_VERSION' ) ? SUBIN_PLUGIN_VERSION : '1.1.3' ); ?></code>
					</div>
					<div>
						<span><?php esc_html_e( 'Last legacy push sync', 'submission-inbox' ); ?></span>
						<code><?php echo esc_html( $settings['cloud_sync_last_sync_at'] ? $settings['cloud_sync_last_sync_at'] : __( 'Never', 'submission-inbox' ) ); ?></code>
					</div>
				</div>
				<div class="subin-cloud-sync-queue">
					<div class="subin-cloud-sync-queue__counts">
						<?php SUBIN_View::badge( sprintf( __( 'Pending: %d', 'submission-inbox' ), absint( $queue_counts['pending'] ?? 0 ) ), 'info' ); ?>
						<?php SUBIN_View::badge( sprintf( __( 'Failed: %d', 'submission-inbox' ), absint( $queue_counts['failed'] ?? 0 ) ), ! empty( $queue_counts['failed'] ) ? 'danger' : 'muted' ); ?>
						<?php SUBIN_View::badge( sprintf( __( 'Sent: %d', 'submission-inbox' ), absint( $queue_counts['sent'] ?? 0 ) ), 'success' ); ?>
						<?php SUBIN_View::badge( sprintf( __( 'Skipped: %d', 'submission-inbox' ), absint( $queue_counts['skipped'] ?? 0 ) ), 'muted' ); ?>
					</div>
					<?php if ( empty( $recent_events ) ) : ?>
						<p class="description"><?php esc_html_e( 'No local changelog events have been recorded yet.', 'submission-inbox' ); ?></p>
					<?php else : ?>
						<table class="widefat striped subin-cloud-sync-events">
							<thead>
								<tr>
									<th><?php esc_html_e( 'ID', 'submission-inbox' ); ?></th>
									<th><?php esc_html_e( 'Event type', 'submission-inbox' ); ?></th>
									<th><?php esc_html_e( 'Object', 'submission-inbox' ); ?></th>
									<th><?php esc_html_e( 'Status', 'submission-inbox' ); ?></th>
									<th><?php esc_html_e( 'Attempts', 'submission-inbox' ); ?></th>
									<th><?php esc_html_e( 'Last error', 'submission-inbox' ); ?></th>
									<th><?php esc_html_e( 'Created', 'submission-inbox' ); ?></th>
									<th><?php esc_html_e( 'Sent', 'submission-inbox' ); ?></th>
								</tr>
							</thead>
							<tbody>
								<?php foreach ( $recent_events as $event ) : ?>
									<tr>
										<td><code><?php echo esc_html( (string) absint( $event->id ?? 0 ) ); ?></code></td>
										<td><?php echo esc_html( $event->event_type ?? '' ); ?></td>
										<td><?php echo esc_html( trim( (string) ( $event->object_type ?? '' ) . ' #' . (string) ( $event->object_id ?? '' ) ) ); ?></td>
										<td><?php SUBIN_View::badge( $this->cloud_sync_event_status_label( $event->status ?? '' ), $this->cloud_sync_event_status_badge_type( $event->status ?? '' ) ); ?></td>
										<td><?php echo esc_html( (string) absint( $event->attempts ?? 0 ) ); ?></td>
										<td><?php echo esc_html( wp_trim_words( (string) ( $event->last_error ?? '' ), 12 ) ); ?></td>
										<td><?php echo esc_html( $event->created_at ?? '' ); ?></td>
										<td><?php echo esc_html( $event->sent_at ?? '' ); ?></td>
									</tr>
								<?php endforeach; ?>
							</tbody>
						</table>
					<?php endif; ?>
				</div>
			</details>
		</div>
		<?php
	}

	/**
	 * Render one integration card.
	 *
	 * @param string $title Card title.
	 * @param bool   $detected Whether plugin is detected.
	 * @param string $setting_key Setting key.
	 * @param bool   $enabled Whether integration is enabled.
	 * @param string $description Card description.
	 * @return void
	 */
	private function render_integration_card( $title, $detected, $setting_key, $enabled, $description ) {
		?>
		<div class="subin-integration-card">
			<div>
				<h3><?php echo esc_html( $title ); ?></h3>
				<p><?php echo esc_html( $description ); ?></p>
			</div>
			<div class="subin-integration-card__controls">
				<?php SUBIN_View::badge( $detected ? __( 'Detected', 'submission-inbox' ) : __( 'Not detected', 'submission-inbox' ), $detected ? 'success' : 'muted' ); ?>
				<?php if ( $detected ) : ?>
					<input type="hidden" name="<?php echo esc_attr( $setting_key ); ?>" value="0" />
				<?php endif; ?>
				<div class="subin-integration-card__toggle">
					<?php SUBIN_View::checkbox_field( $setting_key, __( 'Enabled', 'submission-inbox' ), $detected && $enabled, '', ! $detected ); ?>
					<?php SUBIN_View::help_tooltip( __( 'When disabled, submissions from this integration are not saved to Inbox or Email Log.', 'submission-inbox' ), __( 'Integration storage', 'submission-inbox' ) ); ?>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Render one planned integration card.
	 *
	 * @param string $title Card title.
	 * @param string $description Card description.
	 * @return void
	 */
	private function render_planned_integration_card( $title, $description ) {
		?>
		<div class="subin-integration-card subin-integration-card--planned">
			<div>
				<h3><?php echo esc_html( $title ); ?></h3>
				<p><?php echo esc_html( $description ); ?></p>
			</div>
			<div class="subin-integration-card__controls">
				<?php SUBIN_View::badge( __( 'Info', 'submission-inbox' ), 'muted' ); ?>
			</div>
		</div>
		<?php
	}

	/**
	 * Uninstall tab.
	 *
	 * @param array<string,mixed> $settings Settings.
	 * @return void
	 */
	private function render_uninstall_tab( array $settings ) {
		?>
		<div class="subin-settings-section">
			<h2>
				<?php esc_html_e( 'Uninstall', 'submission-inbox' ); ?>
				<?php SUBIN_View::help_tooltip( __( 'If enabled, plugin tables and settings are deleted when the plugin is uninstalled.', 'submission-inbox' ) ); ?>
			</h2>
			<p><?php esc_html_e( 'If enabled, plugin tables and settings will be removed when the plugin is deleted.', 'submission-inbox' ); ?></p>
			<table class="form-table" role="presentation">
				<tbody>
					<tr>
						<th scope="row"><?php esc_html_e( 'Delete plugin data on uninstall', 'submission-inbox' ); ?></th>
						<td>
							<input type="hidden" name="delete_data_on_uninstall" value="0" />
							<?php SUBIN_View::checkbox_field( 'delete_data_on_uninstall', __( 'Remove InboxMend tables and settings when the plugin is uninstalled', 'submission-inbox' ), (bool) $settings['delete_data_on_uninstall'] ); ?>
							<p class="description"><?php esc_html_e( 'Deactivation never deletes data. This setting applies only when the plugin is uninstalled.', 'submission-inbox' ); ?></p>
						</td>
					</tr>
				</tbody>
			</table>
		</div>
		<?php
	}

	/**
	 * Tab URL.
	 *
	 * @param string $tab Tab key.
	 * @return string
	 */
	private function tab_url( $tab ) {
		return add_query_arg(
			array(
				'page'             => 'subin-settings',
				'subin_settings_tab' => sanitize_key( $tab ),
			),
			admin_url( 'admin.php' )
		);
	}

	/**
	 * Save settings when the form is submitted.
	 *
	 * @return void
	 */
	private function maybe_save() {
		$request_method = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ?? '' ) );
		if ( 'POST' !== strtoupper( $request_method ) || isset( $_POST['subin_smtp_action'] ) ) {
			return;
		}

		if ( ! isset( $_POST[ self::NONCE_NAME ] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST[ self::NONCE_NAME ] ) ), self::NONCE_ACTION ) ) {
			add_settings_error(
				'subin_messages',
				'subin_security_check_failed',
				__( 'Security check failed. Please try again.', 'submission-inbox' ),
				'error'
			);
			return;
		}

		$action   = sanitize_key( wp_unslash( $_POST['subin_settings_action'] ?? '' ) );
		$settings = $this->settings->all();

		if ( 'lead_storage' === $action ) {
			$settings['store_leads']     = rest_sanitize_boolean( wp_unslash( $_POST['store_leads'] ?? false ) );
			$settings['retention_days']  = $this->sanitize_retention_days( sanitize_text_field( wp_unslash( $_POST['retention_days'] ?? 90 ) ) );
			$settings['new_lead_hours']  = $this->sanitize_new_lead_hours( sanitize_text_field( wp_unslash( $_POST['new_lead_hours'] ?? 24 ) ) );
		} elseif ( 'integrations' === $action ) {
			if ( \SUBIN\Forms\ContactForm7Integration::is_available() ) {
				$settings['enable_cf7_integration'] = rest_sanitize_boolean( wp_unslash( $_POST['enable_cf7_integration'] ?? false ) );
			}
			if ( \SUBIN\Forms\WPFormsIntegration::is_available() ) {
				$settings['enable_wpforms_integration'] = rest_sanitize_boolean( wp_unslash( $_POST['enable_wpforms_integration'] ?? false ) );
			}
			if ( \SUBIN\Forms\FluentFormsIntegration::is_available() ) {
				$settings['enable_fluent_forms_integration'] = rest_sanitize_boolean( wp_unslash( $_POST['enable_fluent_forms_integration'] ?? false ) );
			}
			if ( \SUBIN\Forms\ForminatorIntegration::is_available() ) {
				$settings['enable_forminator_integration'] = rest_sanitize_boolean( wp_unslash( $_POST['enable_forminator_integration'] ?? false ) );
			}
			if ( \SUBIN\Forms\NinjaFormsIntegration::is_available() ) {
				$settings['enable_ninja_forms_integration'] = rest_sanitize_boolean( wp_unslash( $_POST['enable_ninja_forms_integration'] ?? false ) );
			}
		} elseif ( 'privacy' === $action ) {
			$settings['store_ip']              = rest_sanitize_boolean( wp_unslash( $_POST['store_ip'] ?? false ) );
			$settings['store_user_agent']      = rest_sanitize_boolean( wp_unslash( $_POST['store_user_agent'] ?? false ) );
			$settings['mask_sensitive_fields'] = rest_sanitize_boolean( wp_unslash( $_POST['mask_sensitive_fields'] ?? false ) );
		} elseif ( 'spam_protection' === $action ) {
			$settings['recaptcha_enabled']         = rest_sanitize_boolean( wp_unslash( $_POST['recaptcha_enabled'] ?? false ) );
			$settings['recaptcha_site_key']        = sanitize_text_field( trim( (string) wp_unslash( $_POST['recaptcha_site_key'] ?? '' ) ) );
			$settings['recaptcha_secret_key']      = sanitize_text_field( trim( (string) wp_unslash( $_POST['recaptcha_secret_key'] ?? '' ) ) );
			$settings['recaptcha_score_threshold'] = $this->sanitize_recaptcha_score_threshold( wp_unslash( $_POST['recaptcha_score_threshold'] ?? 0.5 ) );
		} elseif ( 'cloud_sync' === $action ) {
			$this->handle_cloud_sync_submit( $settings );
			return;
		} elseif ( 'uninstall' === $action ) {
			$settings['delete_data_on_uninstall'] = rest_sanitize_boolean( wp_unslash( $_POST['delete_data_on_uninstall'] ?? false ) );
		} else {
			return;
		}

		update_option( SUBIN_SettingsRepository::OPTION_KEY, $settings, false );

		add_settings_error(
			'subin_messages',
			'subin_settings_saved',
			__( 'Settings saved.', 'submission-inbox' ),
			'updated'
		);
	}

	/**
	 * Sanitize retention days.
	 *
	 * @param mixed $value Raw value.
	 * @return int
	 */
	private function sanitize_retention_days( $value ) {
		$value   = absint( $value );
		$allowed = array( 0, 30, 60, 90, 180, 365 );

		return in_array( $value, $allowed, true ) ? $value : 90;
	}

	/**
	 * Sanitize the unread lead freshness window.
	 *
	 * @param mixed $value Raw value.
	 * @return int
	 */
	private function sanitize_new_lead_hours( $value ) {
		$value   = absint( $value );
		$allowed = array( 6, 12, 24, 48, 72, 168 );

		return in_array( $value, $allowed, true ) ? $value : 24;
	}

	/**
	 * Sanitize Cloud data mode.
	 *
	 * @param mixed $value Raw value.
	 * @return string
	 */
	private function sanitize_cloud_sync_data_mode( $value ) {
		$value = sanitize_key( (string) $value );

		return in_array( $value, array( 'health_only', 'lead_summary', 'full_lead_data' ), true ) ? $value : 'lead_summary';
	}

	/**
	 * Handle Cloud Sync settings and actions.
	 *
	 * @param array<string,mixed> $settings Current settings.
	 * @return void
	 */
	private function handle_cloud_sync_submit( array $settings ) {
		$cloud_action = sanitize_key( wp_unslash( $_POST['subin_cloud_action'] ?? 'save' ) );
		$site_key     = sanitize_text_field( trim( (string) wp_unslash( $_POST['cloud_sync_site_key'] ?? '' ) ) );
		$data_mode    = $this->sanitize_cloud_sync_data_mode( wp_unslash( $_POST['cloud_sync_data_mode'] ?? 'lead_summary' ) );
		$consent      = rest_sanitize_boolean( wp_unslash( $_POST['cloud_sync_full_data_consent'] ?? false ) );

		$settings['cloud_sync_site_key']          = $site_key;
		$settings['cloud_sync_license_key']       = $site_key;
		$settings['cloud_sync_data_mode']         = $data_mode;
		$settings['cloud_sync_full_data_consent'] = $consent;
		$settings['cloud_sync_enabled']           = ! empty( $settings['cloud_sync_site_token'] );
		$settings['cloud_sync_transport']         = 'push' === sanitize_key( (string) ( $settings['cloud_sync_transport'] ?? 'polling' ) ) ? 'push' : 'polling';
		update_option( SUBIN_SettingsRepository::OPTION_KEY, $settings, false );

		$service = new \SUBIN\Cloud\CloudSyncService();

		if ( 'connect' === $cloud_action ) {
			$result = $service->connect( $site_key, $data_mode, $consent );
			$this->add_cloud_action_notice( $result, __( 'Cloud site connected.', 'submission-inbox' ) );
			return;
		}

		if ( 'validate' === $cloud_action ) {
			$result = $service->validate_connection();
			$this->add_cloud_action_notice( $result, __( 'Cloud connection checked.', 'submission-inbox' ) );
			return;
		}

		if ( 'heartbeat' === $cloud_action ) {
			$result = $service->send_heartbeat();
			$this->add_cloud_action_notice( $result, __( 'Cloud heartbeat sent.', 'submission-inbox' ) );
			return;
		}

		if ( 'sync' === $cloud_action ) {
			$result = $service->sync_pending_events();
			$this->add_cloud_sync_notice( $result );
			return;
		}

		if ( 'retry_failed' === $cloud_action ) {
			$result = ( new \SUBIN\Sync\SyncEventRepository() )->retry_failed();
			if ( false === $result ) {
				$this->add_cloud_action_notice( new WP_Error( 'subin_cloud_retry_failed', __( 'Failed sync events could not be reset.', 'submission-inbox' ) ), '' );
				return;
			}

			add_settings_error(
				'subin_messages',
				'subin_cloud_retry_failed_events',
				sprintf(
					/* translators: %d: number of reset sync events. */
					__( '%d failed sync events moved back to pending.', 'submission-inbox' ),
					absint( $result )
				),
				'updated'
			);
			return;
		}

		if ( 'rotate' === $cloud_action ) {
			$result = $service->rotate_token();
			$this->add_cloud_action_notice( $result, __( 'Cloud site token rotated.', 'submission-inbox' ) );
			return;
		}

		if ( 'revoke' === $cloud_action ) {
			$result = $service->revoke_token();
			$this->add_cloud_action_notice( $result, __( 'Cloud site disconnected.', 'submission-inbox' ) );
			return;
		}

		add_settings_error(
			'subin_messages',
			'subin_settings_saved',
			__( 'Settings saved.', 'submission-inbox' ),
			'updated'
		);
	}

	/**
	 * Add notice for a Cloud action.
	 *
	 * @param true|array<string,mixed>|\WP_Error $result Result.
	 * @param string         $success_message Success message.
	 * @return void
	 */
	private function add_cloud_action_notice( $result, $success_message ) {
		if ( is_wp_error( $result ) ) {
			$this->set_cloud_action_notice(
				$result->get_error_message(),
				'error'
			);
			return;
		}

		$this->set_cloud_action_notice(
			$success_message,
			'success'
		);
	}

	/**
	 * Add detailed notice for a manual Cloud sync run.
	 *
	 * @param true|array<string,mixed>|\WP_Error $result Result.
	 * @return void
	 */
	private function add_cloud_sync_notice( $result ) {
		if ( is_wp_error( $result ) ) {
			$this->add_cloud_action_notice( $result, '' );
			return;
		}

		if ( true === $result ) {
			$this->add_cloud_action_notice( $result, __( 'Pending Cloud sync events sent.', 'submission-inbox' ) );
			return;
		}

		$attempted = $this->sync_notice_count( $result, 'attempted' );
		if ( 0 === $attempted ) {
			$this->set_cloud_action_notice(
				__( 'No pending Cloud sync events found.', 'submission-inbox' ),
				'success'
			);
			return;
		}

		$has_issues = $this->sync_notice_count( $result, 'normalization_failures' ) > 0
			|| $this->sync_notice_count( $result, 'local_failed' ) > 0
			|| $this->sync_notice_count( $result, 'local_skipped' ) > 0;

		$this->set_cloud_action_notice(
			sprintf(
				/* translators: 1: attempted, 2: received, 3: duplicates, 4: normalized, 5: normalization failures, 6: local sent, 7: local failed, 8: local skipped. */
				__( 'Cloud sync finished: %1$d attempted, %2$d received, %3$d duplicates, %4$d normalized, %5$d normalization failures. Local queue: %6$d sent, %7$d failed, %8$d skipped.', 'submission-inbox' ),
				$attempted,
				$this->sync_notice_count( $result, 'received' ),
				$this->sync_notice_count( $result, 'duplicates' ),
				$this->sync_notice_count( $result, 'normalized' ),
				$this->sync_notice_count( $result, 'normalization_failures' ),
				$this->sync_notice_count( $result, 'local_sent' ),
				$this->sync_notice_count( $result, 'local_failed' ),
				$this->sync_notice_count( $result, 'local_skipped' )
			),
			$has_issues ? 'warning' : 'success'
		);
	}

	/**
	 * Store a persistent Cloud action notice for the current admin user.
	 *
	 * @param string $message Message.
	 * @param string $type Notice type.
	 * @return void
	 */
	private function set_cloud_action_notice( $message, $type ) {
		$message = trim( sanitize_textarea_field( (string) $message ) );
		if ( '' === $message ) {
			return;
		}

		set_transient(
			$this->cloud_action_notice_transient_key(),
			array(
				'message' => $message,
				'type'    => in_array( $type, array( 'success', 'warning', 'error' ), true ) ? $type : 'success',
			),
			10 * MINUTE_IN_SECONDS
		);
	}

	/**
	 * Render the latest Cloud action notice.
	 *
	 * @return void
	 */
	private function render_cloud_action_notice() {
		$notice = get_transient( $this->cloud_action_notice_transient_key() );
		if ( ! is_array( $notice ) || empty( $notice['message'] ) ) {
			return;
		}

		$type = sanitize_key( (string) ( $notice['type'] ?? 'success' ) );
		if ( ! in_array( $type, array( 'success', 'warning', 'error' ), true ) ) {
			$type = 'success';
		}
		?>
		<div class="subin-cloud-action-notice subin-cloud-action-notice--<?php echo esc_attr( $type ); ?>">
			<strong><?php echo esc_html( 'error' === $type ? __( 'Cloud action failed', 'submission-inbox' ) : __( 'Cloud action complete', 'submission-inbox' ) ); ?></strong>
			<span><?php echo esc_html( (string) $notice['message'] ); ?></span>
		</div>
		<?php
	}

	/**
	 * Current user's Cloud action notice transient key.
	 *
	 * @return string
	 */
	private function cloud_action_notice_transient_key() {
		return 'subin_cloud_action_notice_' . absint( get_current_user_id() );
	}

	/**
	 * Read an integer value from a sync notice payload.
	 *
	 * @param array<string,mixed> $result Result.
	 * @param string              $key Key.
	 * @return int
	 */
	private function sync_notice_count( array $result, $key ) {
		return absint( $result[ $key ] ?? 0 );
	}

	/**
	 * Build a Cloud dashboard site URL.
	 *
	 * @param int $site_id Cloud site id.
	 * @return string
	 */
	private function cloud_dashboard_site_url( $site_id ) {
		if ( $site_id <= 0 ) {
			return '';
		}

		$base_url = defined( 'SUBIN_CLOUD_DASHBOARD_URL' ) ? SUBIN_CLOUD_DASHBOARD_URL : 'https://inboxmend.com';
		$base_url = untrailingslashit( esc_url_raw( (string) $base_url ) );

		return $base_url . '/sites/' . absint( $site_id );
	}

	/**
	 * Convert a Cloud API error into user-facing connector guidance.
	 *
	 * @param string $message Raw message.
	 * @return string
	 */
	private function cloud_friendly_error_message( $message ) {
		$message = trim( (string) $message );
		$lower   = strtolower( $message );

		if ( false !== strpos( $lower, 'already connected' ) ) {
			return __( 'This WordPress site is already connected to another Cloud site. Open the existing site in Cloud, or revoke the old connection before claiming a new key.', 'submission-inbox' );
		}

		if ( false !== strpos( $lower, 'site key was not found' ) || false !== strpos( $lower, 'invalid_site_key' ) ) {
			return __( 'This key was not found. Copy the site connection key from the Cloud site record and try again.', 'submission-inbox' );
		}

		if ( false !== strpos( $lower, 'revoked' ) ) {
			return __( 'This key has been revoked. Generate a new site connection key in Cloud.', 'submission-inbox' );
		}

		if ( false !== strpos( $lower, 'url does not match' ) || false !== strpos( $lower, 'site_url_mismatch' ) ) {
			return __( 'This key belongs to another site URL. Check the site URL in Cloud or copy the key for this WordPress site.', 'submission-inbox' );
		}

		if ( false !== strpos( $lower, 'site limit' ) ) {
			return __( 'Your Cloud plan site limit has been reached. Release a site seat or upgrade the plan in Cloud.', 'submission-inbox' );
		}

		if ( false !== strpos( $lower, 'subscription' ) ) {
			return __( 'The Cloud subscription is not active. Check billing or license status in Cloud.', 'submission-inbox' );
		}

		return '' !== $message ? $message : __( 'Cloud could not connect this site. Check the site key and try again.', 'submission-inbox' );
	}

	/**
	 * Cloud connection status label.
	 *
	 * @param string $status Status.
	 * @return string
	 */
	private function cloud_status_label( $status ) {
		$labels = array(
			'connected'    => __( 'Connected', 'submission-inbox' ),
			'disconnected' => __( 'Disconnected', 'submission-inbox' ),
			'error'        => __( 'Error', 'submission-inbox' ),
		);

		$status = sanitize_key( (string) $status );

		return $labels[ $status ] ?? $labels['disconnected'];
	}

	/**
	 * Cloud connection status badge type.
	 *
	 * @param string $status Status.
	 * @return string
	 */
	private function cloud_status_badge_type( $status ) {
		$status = sanitize_key( (string) $status );
		if ( 'connected' === $status ) {
			return 'success';
		}
		if ( 'error' === $status ) {
			return 'danger';
		}

		return 'muted';
	}

	/**
	 * Human-readable Cloud sync mode label.
	 *
	 * @param string $mode Sync mode.
	 * @return string
	 */
	private function cloud_sync_mode_label( $mode ) {
		$labels = array(
			'health_only'    => __( 'Health only', 'submission-inbox' ),
			'lead_summary'   => __( 'Lead summary', 'submission-inbox' ),
			'full_lead_data' => __( 'Full lead data', 'submission-inbox' ),
		);

		$mode = sanitize_key( (string) $mode );

		return $labels[ $mode ] ?? $labels['lead_summary'];
	}

	/**
	 * Sync event status label.
	 *
	 * @param string $status Status.
	 * @return string
	 */
	private function cloud_sync_event_status_label( $status ) {
		$labels = array(
			'pending' => __( 'Pending', 'submission-inbox' ),
			'failed'  => __( 'Failed', 'submission-inbox' ),
			'sent'    => __( 'Sent', 'submission-inbox' ),
			'skipped' => __( 'Skipped', 'submission-inbox' ),
		);

		$status = sanitize_key( (string) $status );

		return $labels[ $status ] ?? __( 'Unknown', 'submission-inbox' );
	}

	/**
	 * Sync event badge type.
	 *
	 * @param string $status Status.
	 * @return string
	 */
	private function cloud_sync_event_status_badge_type( $status ) {
		$status = sanitize_key( (string) $status );
		if ( 'sent' === $status ) {
			return 'success';
		}
		if ( 'failed' === $status ) {
			return 'danger';
		}
		if ( 'pending' === $status ) {
			return 'info';
		}

		return 'muted';
	}

	/**
	 * Sanitize reCAPTCHA score threshold.
	 *
	 * @param mixed $value Raw value.
	 * @return float
	 */
	private function sanitize_recaptcha_score_threshold( $value ) {
		$value = is_numeric( $value ) ? (float) $value : 0.5;

		return max( 0.0, min( 1.0, $value ) );
	}
}
