<?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';
	const TEAM_ACCESS_ACTION       = 'team_access';
	const TEAM_ACCESS_NONCE_ACTION = 'subin_update_team_access_';
	const TEAM_ACCESS_NONCE_NAME   = 'subin_team_access_nonce';
	const TEAM_ACCESS_PER_PAGE     = 20;
	const FLASH_TTL_SECONDS        = 300;

	/**
	 * 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 ( ! $this->can_access_settings_page() ) {
			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();
		$this->restore_settings_notices();

		SUBIN_View::page_header(
			__( 'Settings', 'submission-inbox' ),
			__( 'Review and manage the InboxMend configuration areas available to your account.', 'submission-inbox' ),
			array( 'wrapper_class' => 'subin-settings-page' )
		);

		SUBIN_AdminNotices::render_settings_errors( 'subin_messages' );
		?>
		<div class="subin-settings-workspace">
		<?php $this->render_tabs( $active_tab, $settings ); ?>
		<div class="subin-settings-panel" id="subin-settings-panel" tabindex="-1">
			<?php
			if ( 'overview' === $active_tab ) {
				$this->render_overview_tab( $settings );
			} elseif ( 'sending' === $active_tab ) {
				$this->smtp_page->render_embedded();
			} elseif ( self::TEAM_ACCESS_ACTION === $active_tab ) {
				$this->render_team_access_tab();
			} elseif ( 'integrations' === $active_tab ) {
				$this->render_integrations_tab( $settings );
			} else {
				$this->render_settings_form( $active_tab, $settings );
			}
			?>
		</div>
		</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 ( ! $this->can_access_settings_page() ) {
			wp_die( esc_html__( 'You do not have permission to perform this action.', 'submission-inbox' ) );
		}

		if ( $this->maybe_handle_team_access_post() ) {
			$this->redirect_after_post();
			return;
		}

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

	/**
	 * Complete state-changing requests with POST/Redirect/GET.
	 *
	 * This prevents browser refresh from repeating verification email, Cloud,
	 * Team access, or settings mutations.
	 *
	 * @return void
	 */
	private function redirect_after_post() {
		$request_method = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ?? '' ) );
		if ( 'POST' !== strtoupper( $request_method ) ) {
			return;
		}

		$this->persist_settings_notices();
		$tab = $this->active_tab();
		if ( isset( $_POST['subin_smtp_action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Routing only; the SMTP handler owns its action nonce.
			$tab = 'sending';
		} else {
			$posted_tab = sanitize_key( (string) $this->post_scalar( 'subin_settings_action' ) );
			if ( array_key_exists( $posted_tab, $this->tabs() ) ) {
				$tab = $posted_tab;
			}
		}

		$url = $this->tab_url( $tab );
		if ( self::TEAM_ACCESS_ACTION === $tab ) {
			$search = SUBIN_Request::get_text( 'subin_team_search' );
			$paged  = SUBIN_Request::get_absint( 'subin_team_paged', 1 );
			$args   = array();
			if ( '' !== $search ) {
				$args['subin_team_search'] = $search;
			}
			if ( $paged > 1 ) {
				$args['subin_team_paged'] = $paged;
			}
			$url = add_query_arg( $args, $url );
		}

		wp_safe_redirect( $url, 303 );
		exit;
	}

	/** Store current-request notices for the redirect target. */
	private function persist_settings_notices() {
		$notices = array();
		foreach ( get_settings_errors( 'subin_messages' ) as $notice ) {
			$notices[] = array(
				'code'    => sanitize_key( (string) ( $notice['code'] ?? 'subin_settings_notice' ) ),
				'message' => sanitize_textarea_field( (string) ( $notice['message'] ?? '' ) ),
				'type'    => in_array( $notice['type'] ?? '', array( 'error', 'warning', 'success', 'updated', 'info' ), true ) ? $notice['type'] : 'updated',
			);
		}

		if ( ! empty( $notices ) ) {
			set_transient( $this->settings_flash_key(), $notices, self::FLASH_TTL_SECONDS );
		}
	}

	/** Restore and consume notices after POST/Redirect/GET. */
	private function restore_settings_notices() {
		$notices = get_transient( $this->settings_flash_key() );
		delete_transient( $this->settings_flash_key() );
		if ( ! is_array( $notices ) ) {
			return;
		}

		foreach ( $notices as $notice ) {
			if ( ! is_array( $notice ) || empty( $notice['message'] ) ) {
				continue;
			}
			$type = in_array( $notice['type'] ?? '', array( 'error', 'warning', 'success', 'updated', 'info' ), true ) ? $notice['type'] : 'updated';
			add_settings_error( 'subin_messages', sanitize_key( (string) ( $notice['code'] ?? 'subin_settings_notice' ) ), sanitize_textarea_field( (string) $notice['message'] ), $type );
		}
	}

	/** Current-user Settings flash key. */
	private function settings_flash_key() {
		return 'subin_settings_flash_' . absint( get_current_user_id() );
	}

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

	/**
	 * Settings tabs.
	 *
	 * @return array<string,string>
	 */
	private function tabs() {
		$tabs = array( 'overview' => __( 'Overview', 'submission-inbox' ) );

		if ( \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::MANAGE_SENDING ) ) {
			$tabs['sending'] = __( 'Sending', 'submission-inbox' );
		}

		if ( \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::MANAGE_SETTINGS ) ) {
			$tabs['lead_storage'] = __( 'Lead storage', 'submission-inbox' );
		}

		if ( $this->can_manage_team_access() ) {
			$tabs[ self::TEAM_ACCESS_ACTION ] = __( 'Team access', 'submission-inbox' );
		}

		if ( \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::MANAGE_SETTINGS ) ) {
			$tabs['privacy']         = __( 'Privacy', 'submission-inbox' );
			$tabs['spam_protection'] = __( 'Spam protection', 'submission-inbox' );
			$tabs['integrations']    = __( 'Integrations', 'submission-inbox' );
		}

		if ( \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::MANAGE_CLOUD ) ) {
			$tabs['cloud_sync'] = __( 'Cloud Sync', 'submission-inbox' );
		}

		if ( \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::MANAGE_SETTINGS ) ) {
			$tabs['uninstall'] = __( 'Uninstall', 'submission-inbox' );
		}

		return $tabs;
	}

	/** Whether the current user can reach at least one Settings area. */
	private function can_access_settings_page() {
		return \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::MANAGE_SETTINGS )
			|| \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::MANAGE_SENDING )
			|| \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::MANAGE_CLOUD );
	}

	/**
	 * Render tabs.
	 *
	 * @param string              $active_tab Active tab.
	 * @param array<string,mixed> $settings Current settings.
	 * @return void
	 */
	private function render_tabs( $active_tab, array $settings ) {
		?>
		<nav class="subin-settings-tabs" aria-label="<?php echo esc_attr__( 'Settings sections', 'submission-inbox' ); ?>" data-subin-settings-navigation>
			<?php foreach ( $this->tabs() as $key => $label ) : ?>
				<?php $meta = $this->tab_meta( $key, $settings ); ?>
				<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"' : ''; ?>>
					<span class="dashicons <?php echo esc_attr( $meta['icon'] ); ?>" aria-hidden="true"></span>
					<span class="subin-settings-tab__copy">
						<strong><?php echo esc_html( $label ); ?></strong>
						<small><?php echo esc_html( $meta['description'] ); ?></small>
					</span>
					<?php if ( '' !== $meta['status'] ) : ?>
						<span class="subin-settings-tab__status subin-settings-tab__status--<?php echo esc_attr( $meta['tone'] ); ?>"><?php echo esc_html( $meta['status'] ); ?></span>
					<?php endif; ?>
				</a>
			<?php endforeach; ?>
		</nav>
		<?php
	}

	/**
	 * Navigation metadata and compact configuration state.
	 *
	 * Settings only reports configuration here. Delivery evidence and detailed
	 * diagnostics remain owned by Email Health and Email Log.
	 *
	 * @param string              $tab Tab key.
	 * @param array<string,mixed> $settings Current settings.
	 * @return array{icon:string,description:string,status:string,tone:string}
	 */
	private function tab_meta( $tab, array $settings ) {
		$meta = array(
			'overview'        => array( 'dashicons-dashboard', __( 'Readiness and shortcuts', 'submission-inbox' ) ),
			'sending'         => array( 'dashicons-email-alt', __( 'Sender, transport, and verification', 'submission-inbox' ) ),
			'lead_storage'    => array( 'dashicons-database', __( 'Capture and retention', 'submission-inbox' ) ),
			self::TEAM_ACCESS_ACTION => array( 'dashicons-groups', __( 'Roles and export access', 'submission-inbox' ) ),
			'privacy'         => array( 'dashicons-privacy', __( 'Redaction and Email Log history', 'submission-inbox' ) ),
			'spam_protection' => array( 'dashicons-shield', __( 'Built-in form protection', 'submission-inbox' ) ),
			'integrations'    => array( 'dashicons-admin-plugins', __( 'Connected form plugins', 'submission-inbox' ) ),
			'cloud_sync'      => array( 'dashicons-cloud', __( 'Connection and data sharing', 'submission-inbox' ) ),
			'uninstall'       => array( 'dashicons-trash', __( 'Plugin data removal', 'submission-inbox' ) ),
		);

		$definition = $meta[ $tab ] ?? array( 'dashicons-admin-generic', '' );
		$status     = '';
		$tone       = 'muted';

		if ( 'sending' === $tab ) {
			if ( empty( $settings['enable_smtp'] ) ) {
				$status = __( 'WordPress mail', 'submission-inbox' );
			} elseif ( '' === trim( (string) ( $settings['smtp_host'] ?? '' ) ) || ( ! empty( $settings['smtp_auth'] ) && ( '' === trim( (string) ( $settings['smtp_username'] ?? '' ) ) || '' === (string) ( $settings['smtp_password'] ?? '' ) ) ) ) {
				$status = __( 'Needs setup', 'submission-inbox' );
				$tone   = 'warning';
			} else {
				$status = __( 'SMTP ready', 'submission-inbox' );
				$tone   = 'success';
			}
		} elseif ( 'lead_storage' === $tab ) {
			$status = ! empty( $settings['store_leads'] ) ? __( 'Capturing', 'submission-inbox' ) : __( 'Paused', 'submission-inbox' );
			$tone   = ! empty( $settings['store_leads'] ) ? 'success' : 'warning';
		} elseif ( 'privacy' === $tab ) {
			$days   = SUBIN_SettingsRepository::resolve_mail_log_retention_days( $settings );
			/* translators: %d: number of days Email Log entries are retained. */
			$status = 0 === $days ? __( 'Log: forever', 'submission-inbox' ) : sprintf( __( 'Log: %d days', 'submission-inbox' ), $days );
		} elseif ( 'spam_protection' === $tab ) {
			$enabled = ! empty( $settings['recaptcha_enabled'] );
			$ready   = '' !== trim( (string) ( $settings['recaptcha_site_key'] ?? '' ) ) && '' !== trim( (string) ( $settings['recaptcha_secret_key'] ?? '' ) );
			$status  = $enabled && $ready ? __( 'Protected', 'submission-inbox' ) : ( $enabled ? __( 'Needs setup', 'submission-inbox' ) : __( 'Optional', 'submission-inbox' ) );
			$tone    = $enabled && $ready ? 'success' : ( $enabled ? 'warning' : 'muted' );
		} elseif ( 'integrations' === $tab ) {
			$count = 0;
			$count += \SUBIN\Forms\ContactForm7Integration::is_available() && ! empty( $settings['enable_cf7_integration'] ) ? 1 : 0;
			$count += \SUBIN\Forms\WPFormsIntegration::is_available() && ! empty( $settings['enable_wpforms_integration'] ) ? 1 : 0;
			$count += \SUBIN\Forms\FluentFormsIntegration::is_available() && ! empty( $settings['enable_fluent_forms_integration'] ) ? 1 : 0;
			$count += \SUBIN\Forms\ForminatorIntegration::is_available() && ! empty( $settings['enable_forminator_integration'] ) ? 1 : 0;
			$count += \SUBIN\Forms\NinjaFormsIntegration::is_available() && ! empty( $settings['enable_ninja_forms_integration'] ) ? 1 : 0;
			/* translators: %d: number of active form integrations. */
			$status = sprintf( _n( '%d active', '%d active', $count, 'submission-inbox' ), $count );
			$tone   = $count > 0 && ! empty( $settings['store_leads'] ) ? 'success' : 'muted';
		} elseif ( 'cloud_sync' === $tab ) {
			$cloud_status = sanitize_key( (string) ( $settings['cloud_sync_connection_status'] ?? 'disconnected' ) );
			$status       = 'connected' === $cloud_status && ! empty( $settings['cloud_sync_site_token'] ) ? __( 'Connected', 'submission-inbox' ) : ( 'error' === $cloud_status ? __( 'Needs attention', 'submission-inbox' ) : __( 'Not connected', 'submission-inbox' ) );
			$tone         = 'connected' === $cloud_status && ! empty( $settings['cloud_sync_site_token'] ) ? 'success' : ( 'error' === $cloud_status ? 'warning' : 'muted' );
		} elseif ( 'uninstall' === $tab ) {
			$status = ! empty( $settings['delete_data_on_uninstall'] ) ? __( 'Deletion enabled', 'submission-inbox' ) : __( 'Data preserved', 'submission-inbox' );
			$tone   = ! empty( $settings['delete_data_on_uninstall'] ) ? 'danger' : 'success';
		}

		return array(
			'icon'        => $definition[0],
			'description' => $definition[1],
			'status'      => $status,
			'tone'        => $tone,
		);
	}

	/**
	 * Productive Settings landing page.
	 *
	 * @param array<string,mixed> $settings Current settings.
	 * @return void
	 */
	private function render_overview_tab( array $settings ) {
		$can_manage_settings = \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::MANAGE_SETTINGS );
		$can_manage_sending  = \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::MANAGE_SENDING );
		$can_manage_cloud    = \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::MANAGE_CLOUD );
		$can_view_health     = \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::VIEW_EMAIL_HEALTH );
		$sending             = $this->tab_meta( 'sending', $settings );
		$storage             = $this->tab_meta( 'lead_storage', $settings );
		$spam                = $this->tab_meta( 'spam_protection', $settings );
		$cloud               = $this->tab_meta( 'cloud_sync', $settings );
		$needs_attention     = ( $can_manage_sending && 'warning' === $sending['tone'] )
			|| ( $can_manage_settings && ( 'warning' === $spam['tone'] || empty( $settings['store_leads'] ) ) )
			|| ( $can_manage_cloud && 'warning' === $cloud['tone'] );
		$has_secondary       = $can_manage_settings || $this->can_manage_team_access();
		$sending_actions     = array( array( __( 'Configure sending', 'submission-inbox' ), $this->tab_url( 'sending' ), true ) );
		if ( $can_view_health ) {
			$sending_actions[] = array( __( 'Open Email Health', 'submission-inbox' ), admin_url( 'admin.php?page=subin-email-status' ), false );
		}
		?>
		<section class="subin-settings-overview" aria-labelledby="subin-settings-overview-title">
			<header class="subin-settings-overview__hero">
				<div>
					<p class="subin-settings-overview__eyebrow"><?php esc_html_e( 'Site configuration', 'submission-inbox' ); ?></p>
					<h2 id="subin-settings-overview-title"><?php esc_html_e( 'Settings overview', 'submission-inbox' ); ?></h2>
					<p><?php esc_html_e( 'Review the configuration available to your account. Delivery diagnostics and individual event history remain separate from Settings.', 'submission-inbox' ); ?></p>
				</div>
				<?php SUBIN_View::badge( $needs_attention ? __( 'Review recommended', 'submission-inbox' ) : __( 'Configuration ready', 'submission-inbox' ), $needs_attention ? 'warning' : 'success' ); ?>
			</header>

			<div class="subin-settings-overview__grid">
				<?php if ( $can_manage_sending ) : ?>
					<?php $this->render_overview_card(
					'dashicons-email-alt',
					__( 'Email delivery', 'submission-inbox' ),
					$sending['status'],
					$sending['tone'],
					empty( $settings['enable_smtp'] ) ? __( 'WordPress handles transport. InboxMend still records the handoff outcome.', 'submission-inbox' ) : __( 'InboxMend uses the saved SMTP transport for WordPress email.', 'submission-inbox' ),
					$sending_actions
					); ?>
				<?php endif; ?>
				<?php if ( $can_manage_settings ) : ?>
					<?php $this->render_overview_card(
					'dashicons-database',
					__( 'Lead capture', 'submission-inbox' ),
					$storage['status'],
					$storage['tone'],
					/* translators: %s: human-readable lead retention period; %d: lead retention period in days. */
					! empty( $settings['store_leads'] ) ? sprintf( __( 'Saved leads are kept for %s.', 'submission-inbox' ), 0 === absint( $settings['retention_days'] ) ? __( 'as long as the plugin data exists', 'submission-inbox' ) : sprintf( __( '%d days', 'submission-inbox' ), absint( $settings['retention_days'] ) ) ) : __( 'New submissions are not being added to Inbox. Existing data is preserved.', 'submission-inbox' ),
					array( array( __( 'Review storage', 'submission-inbox' ), $this->tab_url( 'lead_storage' ), true ) )
					); ?>
					<?php $this->render_overview_card(
					'dashicons-shield',
					__( 'Spam protection', 'submission-inbox' ),
					$spam['status'],
					$spam['tone'],
					__( 'Rate limiting and honeypot checks are built in. reCAPTCHA v3 is optional for InboxMend forms.', 'submission-inbox' ),
					array( array( __( 'Review protection', 'submission-inbox' ), $this->tab_url( 'spam_protection' ), true ) )
					); ?>
				<?php endif; ?>
				<?php if ( $can_manage_cloud ) : ?>
					<?php $this->render_overview_card(
					'dashicons-cloud',
					__( 'InboxMend Cloud', 'submission-inbox' ),
					$cloud['status'],
					$cloud['tone'],
					__( 'Cloud is optional. Local Inbox, Email Log, and Email Health continue to work without it.', 'submission-inbox' ),
					array( array( __( 'Manage connection', 'submission-inbox' ), $this->tab_url( 'cloud_sync' ), true ) )
					); ?>
				<?php endif; ?>
			</div>

			<?php if ( $has_secondary ) : ?>
				<div class="subin-settings-overview__secondary">
					<?php if ( $can_manage_settings ) : ?>
						<a href="<?php echo esc_url( $this->tab_url( 'privacy' ) ); ?>"><span class="dashicons dashicons-privacy" aria-hidden="true"></span><span><strong><?php esc_html_e( 'Privacy & retention', 'submission-inbox' ); ?></strong><small><?php esc_html_e( 'Control redaction and Email Log history.', 'submission-inbox' ); ?></small></span></a>
						<a href="<?php echo esc_url( $this->tab_url( 'integrations' ) ); ?>"><span class="dashicons dashicons-admin-plugins" aria-hidden="true"></span><span><strong><?php esc_html_e( 'Form integrations', 'submission-inbox' ); ?></strong><small><?php esc_html_e( 'Choose which installed form plugins feed Inbox.', 'submission-inbox' ); ?></small></span></a>
					<?php endif; ?>
					<?php if ( $this->can_manage_team_access() ) : ?>
						<a href="<?php echo esc_url( $this->tab_url( self::TEAM_ACCESS_ACTION ) ); ?>"><span class="dashicons dashicons-groups" aria-hidden="true"></span><span><strong><?php esc_html_e( 'Team access', 'submission-inbox' ); ?></strong><small><?php esc_html_e( 'Grant Inbox roles and optional export access.', 'submission-inbox' ); ?></small></span></a>
					<?php endif; ?>
				</div>
			<?php endif; ?>
		</section>
		<?php
	}

	/** Render one overview status card. */
	private function render_overview_card( $icon, $title, $status, $tone, $description, array $actions ) {
		?>
		<article class="subin-settings-overview-card">
			<div class="subin-settings-overview-card__top">
				<span class="subin-settings-overview-card__icon dashicons <?php echo esc_attr( $icon ); ?>" aria-hidden="true"></span>
				<span class="subin-settings-tab__status subin-settings-tab__status--<?php echo esc_attr( $tone ); ?>"><?php echo esc_html( $status ); ?></span>
			</div>
			<h3><?php echo esc_html( $title ); ?></h3>
			<p><?php echo esc_html( $description ); ?></p>
			<div class="subin-settings-overview-card__actions">
				<?php foreach ( $actions as $action ) : ?>
					<a class="<?php echo ! empty( $action[2] ) ? 'button button-primary' : 'button button-secondary'; ?>" href="<?php echo esc_url( $action[1] ); ?>"><?php echo esc_html( $action[0] ); ?></a>
				<?php endforeach; ?>
			</div>
		</article>
		<?php
	}

	/** Render a consistent, dirty-state aware save bar. */
	private function render_settings_savebar() {
		?>
		<div class="subin-settings-savebar" data-subin-settings-savebar data-subin-dirty-label="<?php echo esc_attr__( 'Unsaved changes', 'submission-inbox' ); ?>" data-subin-clean-label="<?php echo esc_attr__( 'No unsaved changes', 'submission-inbox' ); ?>">
			<div class="subin-settings-savebar__state">
				<span class="dashicons dashicons-saved" aria-hidden="true"></span>
				<span data-subin-settings-state aria-live="polite"><?php esc_html_e( 'No unsaved changes', 'submission-inbox' ); ?></span>
			</div>
			<div class="subin-settings-savebar__actions">
				<button type="button" class="button button-secondary" data-subin-settings-discard hidden><?php esc_html_e( 'Discard changes', 'submission-inbox' ); ?></button>
				<?php submit_button( __( 'Save changes', 'submission-inbox' ), 'primary', 'submit', false ); ?>
			</div>
		</div>
		<?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="" class="subin-settings-form" data-subin-settings-form>
			<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 );
			}
			$this->render_settings_savebar();
			?>
		</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 data-subin-show-when-not="store_leads">
						<td colspan="2">
							<div class="subin-settings-callout subin-settings-callout--warning" role="note">
								<span class="dashicons dashicons-warning" aria-hidden="true"></span>
								<div>
									<strong><?php esc_html_e( 'Lead capture is paused', 'submission-inbox' ); ?></strong>
									<p><?php esc_html_e( 'New submissions will not be added to Inbox. Provider linking, lead-based workflows, and related auto-reply context are paused; existing leads remain available.', 'submission-inbox' ); ?></p>
								</div>
							</div>
						</td>
					</tr>
					<tr data-subin-show-when="store_leads">
						<th scope="row">
							<label for="subin-retention-days"><?php esc_html_e( 'Lead 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 data-subin-show-when="store_leads">
						<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
	}

	/**
	 * Whether the current administrator may grant access to other users.
	 *
	 * @return bool
	 */
	private function can_manage_team_access() {
		return current_user_can( SUBIN_AdminMenu::CAPABILITY ) && current_user_can( 'promote_users' );
	}

	/**
	 * Render the Team access user directory and per-user controls.
	 *
	 * @return void
	 */
	private function render_team_access_tab() {
		if ( ! $this->can_manage_team_access() ) {
			wp_die( esc_html__( 'You do not have permission to manage team access.', 'submission-inbox' ) );
		}

		$directory   = $this->team_access_user_directory();
		$users       = $directory['users'];
		$search      = $directory['search'];
		$paged       = $directory['paged'];
		$total       = $directory['total'];
		$return_args = array();
		if ( '' !== $search ) {
			$return_args['subin_team_search'] = $search;
		}
		if ( $paged > 1 ) {
			$return_args['subin_team_paged'] = $paged;
		}
		$form_action = add_query_arg(
			$return_args,
			$this->tab_url( self::TEAM_ACCESS_ACTION )
		);
		?>
		<div class="subin-settings-section subin-team-access">
			<div class="subin-team-access__header">
				<div>
					<h2><?php esc_html_e( 'Team access', 'submission-inbox' ); ?></h2>
					<p><?php esc_html_e( 'Give existing WordPress users a simple Inbox role without changing their WordPress role.', 'submission-inbox' ); ?></p>
				</div>
				<?php SUBIN_View::badge( __( 'Site administrators only', 'submission-inbox' ), 'muted' ); ?>
			</div>

			<div class="notice notice-warning inline subin-team-access__warning" role="note">
				<p><strong><?php esc_html_e( 'Inbox access includes personal data.', 'submission-inbox' ); ?></strong></p>
				<p><?php esc_html_e( 'Viewers and managers can open every saved lead on this site, including names, email addresses, phone numbers, messages, source URLs, and other submitted fields. Assignment organizes work; it does not limit visibility.', 'submission-inbox' ); ?></p>
			</div>

			<div class="subin-team-access__presets" aria-label="<?php echo esc_attr__( 'Team access presets', 'submission-inbox' ); ?>">
				<div class="subin-team-access__preset">
					<?php SUBIN_View::badge( __( 'No access', 'submission-inbox' ), 'muted' ); ?>
					<p><?php esc_html_e( 'The user cannot open Inbox or view saved leads.', 'submission-inbox' ); ?></p>
				</div>
				<div class="subin-team-access__preset">
					<?php SUBIN_View::badge( __( 'Viewer', 'submission-inbox' ), 'info' ); ?>
					<p><?php esc_html_e( 'Can search and read every saved lead, but cannot change lead status, workflow, assignments, or notes.', 'submission-inbox' ); ?></p>
				</div>
				<div class="subin-team-access__preset">
					<?php SUBIN_View::badge( __( 'Manager', 'submission-inbox' ), 'success' ); ?>
					<p><?php esc_html_e( 'Can read every lead, manage workflow and notes, assign teammates, and retry failed notifications. Permanent deletion is not included.', 'submission-inbox' ); ?></p>
				</div>
			</div>

			<p class="subin-team-access__export-warning">
				<span class="dashicons dashicons-warning" aria-hidden="true"></span>
				<?php esc_html_e( 'CSV export is optional for Viewer and Manager. Export files can contain personal data from every lead matching the selected filters.', 'submission-inbox' ); ?>
			</p>

			<div class="subin-team-access__toolbar">
				<form method="get" action="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>" class="subin-team-access__search">
					<input type="hidden" name="page" value="subin-settings" />
					<input type="hidden" name="subin_settings_tab" value="<?php echo esc_attr( self::TEAM_ACCESS_ACTION ); ?>" />
					<label class="screen-reader-text" for="subin-team-search"><?php esc_html_e( 'Search WordPress users', 'submission-inbox' ); ?></label>
					<input id="subin-team-search" type="search" name="subin_team_search" value="<?php echo esc_attr( $search ); ?>" placeholder="<?php echo esc_attr__( 'Search users…', 'submission-inbox' ); ?>" />
					<button type="submit" class="button button-secondary"><?php esc_html_e( 'Search', 'submission-inbox' ); ?></button>
					<?php if ( '' !== $search ) : ?>
						<a class="button button-link" href="<?php echo esc_url( $this->tab_url( self::TEAM_ACCESS_ACTION ) ); ?>"><?php esc_html_e( 'Clear', 'submission-inbox' ); ?></a>
					<?php endif; ?>
				</form>
				<p class="description"><?php esc_html_e( 'Administrators always retain full access through WordPress and are not listed here.', 'submission-inbox' ); ?></p>
			</div>

			<?php if ( empty( $users ) ) : ?>
				<?php SUBIN_View::empty_state( __( 'No users found', 'submission-inbox' ), '' !== $search ? __( 'Try a different name, username, or email address.', 'submission-inbox' ) : __( 'Add a non-administrator WordPress user before assigning Inbox access.', 'submission-inbox' ) ); ?>
			<?php else : ?>
				<div class="subin-team-access__table-wrap">
					<table class="widefat striped subin-team-access__table">
						<thead>
							<tr>
								<th scope="col"><?php esc_html_e( 'User', 'submission-inbox' ); ?></th>
								<th scope="col"><?php esc_html_e( 'WordPress role', 'submission-inbox' ); ?></th>
								<th scope="col"><?php esc_html_e( 'Inbox access', 'submission-inbox' ); ?></th>
							</tr>
						</thead>
						<tbody>
							<?php foreach ( $users as $user ) : ?>
								<?php $this->render_team_access_user_row( $user, $form_action ); ?>
							<?php endforeach; ?>
						</tbody>
					</table>
				</div>
			<?php endif; ?>
			<?php $this->render_team_access_pagination( $total, $paged, $search ); ?>
		</div>
		<?php
	}

	/**
	 * Query one page of editable, non-administrator site users.
	 *
	 * @return array{users:array<int,WP_User>,search:string,paged:int,total:int}
	 */
	private function team_access_user_directory() {
		$search         = SUBIN_Request::get_text( 'subin_team_search' );
		$requested_page = max( 1, SUBIN_Request::get_absint( 'subin_team_paged', 1 ) );
		$protected_ids  = array();
		if ( is_multisite() && function_exists( 'get_super_admins' ) ) {
			foreach ( get_super_admins() as $login ) {
				$protected_user = get_user_by( 'login', $login );
				if ( $protected_user instanceof WP_User ) {
					$protected_ids[] = absint( $protected_user->ID );
				}
			}
		}

		$args = array(
			'blog_id'           => get_current_blog_id(),
			'number'            => self::TEAM_ACCESS_PER_PAGE,
			'offset'            => ( $requested_page - 1 ) * self::TEAM_ACCESS_PER_PAGE,
			'orderby'           => 'display_name',
			'order'             => 'ASC',
			'count_total'       => true,
			'capability__not_in' => array( 'manage_options' ),
			'exclude'           => array_values( array_unique( array_filter( $protected_ids ) ) ),
		);

		if ( '' !== $search ) {
			$args['search']         = '*' . $search . '*';
			$args['search_columns'] = array( 'user_login', 'user_email', 'display_name' );
		}

		$query       = new WP_User_Query( $args );
		$total       = absint( $query->get_total() );
		$total_pages = max( 1, (int) ceil( $total / self::TEAM_ACCESS_PER_PAGE ) );
		$paged       = min( $requested_page, $total_pages );
		if ( $paged !== $requested_page ) {
			$args['offset'] = ( $paged - 1 ) * self::TEAM_ACCESS_PER_PAGE;
			$query          = new WP_User_Query( $args );
		}
		$users = array_values(
			array_filter(
				$query->get_results(),
				static function ( $user ) {
					return $user instanceof WP_User
						&& ! user_can( $user->ID, 'manage_options' )
						&& current_user_can( 'promote_user', $user->ID );
				}
			)
		);

		return array(
			'users'  => $users,
			'search' => $search,
			'paged'  => $paged,
			'total'  => $total,
		);
	}

	/**
	 * Render one user's access controls.
	 *
	 * @param WP_User $user        WordPress user.
	 * @param string  $form_action Form target URL.
	 * @return void
	 */
	private function render_team_access_user_row( WP_User $user, $form_action ) {
		$state           = \SUBIN\Security\TeamAccessService::state_for_user( $user->ID );
		$preset          = is_array( $state ) ? sanitize_key( (string) ( $state['preset'] ?? '' ) ) : '';
		$export          = is_array( $state ) && ! empty( $state['export'] );
		$allowed_presets = array(
			\SUBIN\Security\TeamAccessService::PRESET_NONE,
			\SUBIN\Security\TeamAccessService::PRESET_VIEWER,
			\SUBIN\Security\TeamAccessService::PRESET_MANAGER,
			\SUBIN\Security\TeamAccessService::PRESET_CUSTOM,
		);
		if ( ! in_array( $preset, $allowed_presets, true ) ) {
			$preset = \SUBIN\Security\TeamAccessService::PRESET_CUSTOM;
		}

		$is_custom      = \SUBIN\Security\TeamAccessService::PRESET_CUSTOM === $preset;
		$is_none        = \SUBIN\Security\TeamAccessService::PRESET_NONE === $preset;
		$select_id      = 'subin-team-preset-' . $user->ID;
		$export_id      = 'subin-team-export-' . $user->ID;
		$description_id = 'subin-team-export-description-' . $user->ID;
		?>
		<tr>
			<td class="subin-team-access__identity">
				<?php echo get_avatar( $user->ID, 36, '', '', array( 'class' => 'subin-team-access__avatar' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Core returns safe avatar HTML. ?>
				<span>
					<strong><?php echo esc_html( $user->display_name ); ?></strong>
					<small><?php echo esc_html( '@' . $user->user_login ); ?></small>
				</span>
			</td>
			<td><?php echo esc_html( $this->team_access_role_labels( $user ) ); ?></td>
			<td>
				<form method="post" action="<?php echo esc_url( $form_action ); ?>" class="subin-team-access__form" data-subin-team-access-form>
					<input type="hidden" name="subin_settings_action" value="<?php echo esc_attr( self::TEAM_ACCESS_ACTION ); ?>" />
					<input type="hidden" name="subin_team_user_id" value="<?php echo esc_attr( (string) $user->ID ); ?>" />
					<?php wp_nonce_field( self::TEAM_ACCESS_NONCE_ACTION . $user->ID, self::TEAM_ACCESS_NONCE_NAME ); ?>
					<div class="subin-team-access__control">
						<label for="<?php echo esc_attr( $select_id ); ?>"><?php esc_html_e( 'Preset', 'submission-inbox' ); ?></label>
						<select id="<?php echo esc_attr( $select_id ); ?>" name="subin_team_preset" required data-subin-team-preset>
							<?php if ( $is_custom ) : ?>
								<option value="" selected disabled><?php esc_html_e( 'Custom — choose a preset', 'submission-inbox' ); ?></option>
							<?php endif; ?>
							<option value="<?php echo esc_attr( \SUBIN\Security\TeamAccessService::PRESET_NONE ); ?>" <?php selected( $preset, \SUBIN\Security\TeamAccessService::PRESET_NONE ); ?>><?php esc_html_e( 'No access', 'submission-inbox' ); ?></option>
							<option value="<?php echo esc_attr( \SUBIN\Security\TeamAccessService::PRESET_VIEWER ); ?>" <?php selected( $preset, \SUBIN\Security\TeamAccessService::PRESET_VIEWER ); ?>><?php esc_html_e( 'Viewer', 'submission-inbox' ); ?></option>
							<option value="<?php echo esc_attr( \SUBIN\Security\TeamAccessService::PRESET_MANAGER ); ?>" <?php selected( $preset, \SUBIN\Security\TeamAccessService::PRESET_MANAGER ); ?>><?php esc_html_e( 'Manager', 'submission-inbox' ); ?></option>
						</select>
					</div>
					<label class="subin-team-access__export" for="<?php echo esc_attr( $export_id ); ?>">
						<input id="<?php echo esc_attr( $export_id ); ?>" type="checkbox" name="subin_team_export" value="1" <?php checked( $export && ! $is_none ); ?> <?php disabled( $is_none || $is_custom ); ?> aria-describedby="<?php echo esc_attr( $description_id ); ?>" data-subin-team-export />
						<span><?php esc_html_e( 'Allow CSV export', 'submission-inbox' ); ?></span>
					</label>
					<span id="<?php echo esc_attr( $description_id ); ?>" class="screen-reader-text"><?php esc_html_e( 'CSV files can contain personal data from all matching leads.', 'submission-inbox' ); ?></span>
					<button type="submit" class="button button-secondary" data-subin-team-submit><?php esc_html_e( 'Save access', 'submission-inbox' ); ?></button>
				</form>
			</td>
		</tr>
		<?php
	}

	/**
	 * Human-readable role labels for one user.
	 *
	 * @param WP_User $user WordPress user.
	 * @return string
	 */
	private function team_access_role_labels( WP_User $user ) {
		$roles  = wp_roles();
		$labels = array();
		foreach ( (array) $user->roles as $role_key ) {
			$label = $roles && isset( $roles->roles[ $role_key ]['name'] ) ? $roles->roles[ $role_key ]['name'] : $role_key;
			$labels[] = translate_user_role( $label );
		}

		return ! empty( $labels ) ? implode( ', ', $labels ) : __( 'No role', 'submission-inbox' );
	}

	/**
	 * Render Team access pagination.
	 *
	 * @param int    $total  Total matching users.
	 * @param int    $paged  Current page.
	 * @param string $search Search query.
	 * @return void
	 */
	private function render_team_access_pagination( $total, $paged, $search ) {
		$total_pages = max( 1, (int) ceil( absint( $total ) / self::TEAM_ACCESS_PER_PAGE ) );
		if ( $total_pages <= 1 ) {
			return;
		}

		$placeholder = 999999999;
		$base_args = array(
			'page'               => 'subin-settings',
			'subin_settings_tab' => self::TEAM_ACCESS_ACTION,
			'subin_team_paged'   => $placeholder,
		);
		if ( '' !== $search ) {
			$base_args['subin_team_search'] = $search;
		}
		$base  = add_query_arg( $base_args, admin_url( 'admin.php' ) );
		$base  = str_replace( (string) $placeholder, '%#%', $base );
		$links = paginate_links(
			array(
				'base'      => $base,
				'format'    => '',
				'current'   => min( max( 1, absint( $paged ) ), $total_pages ),
				'total'     => $total_pages,
				'type'      => 'array',
				'prev_text' => __( 'Previous', 'submission-inbox' ),
				'next_text' => __( 'Next', 'submission-inbox' ),
			)
		);
		if ( empty( $links ) ) {
			return;
		}
		?>
		<nav class="subin-pagination subin-team-access__pagination" aria-label="<?php echo esc_attr__( 'Team users pagination', 'submission-inbox' ); ?>">
			<?php /* translators: %1$d: current page number, %2$d: total number of pages. */ ?>
			<span class="subin-pagination__info"><?php echo esc_html( sprintf( __( 'Page %1$d of %2$d', 'submission-inbox' ), absint( $paged ), $total_pages ) ); ?></span>
			<div class="subin-pagination__links">
				<?php foreach ( $links as $link ) : ?>
					<?php echo wp_kses_post( $link ); ?>
				<?php endforeach; ?>
			</div>
		</nav>
		<?php
	}

	/**
	 * Privacy tab.
	 *
	 * @param array<string,mixed> $settings Settings.
	 * @return void
	 */
	private function render_privacy_tab( array $settings ) {
		$mail_log_retention_days = SUBIN_SettingsRepository::sanitize_mail_log_retention_days(
			$settings['mail_log_retention_days'] ?? SUBIN_SettingsRepository::DEFAULT_MAIL_LOG_RETENTION_DAYS
		);
		?>
		<div class="subin-settings-section">
			<h2>
				<?php esc_html_e( 'Sensitive data handling', 'submission-inbox' ); ?>
				<?php SUBIN_View::help_tooltip( __( 'Controls redaction before a new lead is written to the WordPress database.', 'submission-inbox' ) ); ?>
			</h2>
			<p><?php esc_html_e( 'Password, card, captcha, and security-token fields are excluded. Keep additional sensitive-value redaction enabled unless you have a documented reason to store those future values.', 'submission-inbox' ); ?></p>
			<table class="form-table" role="presentation">
				<tbody>
					<tr>
						<th scope="row"><?php esc_html_e( 'Redact sensitive values', 'submission-inbox' ); ?></th>
						<td>
							<input type="hidden" name="mask_sensitive_fields" value="0" />
							<?php SUBIN_View::checkbox_field( 'mask_sensitive_fields', __( 'Replace potentially sensitive values before storing new leads', 'submission-inbox' ), (bool) $settings['mask_sensitive_fields'] ); ?>
							<p class="description"><?php esc_html_e( 'This applies before database storage, affects future leads only, and does not rewrite existing records.', 'submission-inbox' ); ?></p>
						</td>
					</tr>
					<tr data-subin-show-when-not="mask_sensitive_fields">
						<td colspan="2">
							<div class="subin-settings-callout subin-settings-callout--danger" role="alert">
								<span class="dashicons dashicons-shield-alt" aria-hidden="true"></span>
								<div>
									<strong><?php esc_html_e( 'Future values may be stored in plain text', 'submission-inbox' ); ?></strong>
									<p><?php esc_html_e( 'Only disable redaction when your forms and privacy policy explicitly allow those values to be retained.', 'submission-inbox' ); ?></p>
									<label><input type="checkbox" name="subin_confirm_unmasked_storage" value="1" /> <?php esc_html_e( 'I understand this changes what future leads may store.', 'submission-inbox' ); ?></label>
								</div>
							</div>
						</td>
					</tr>
				</tbody>
			</table>
		</div>
		<div class="subin-settings-section">
			<h2>
				<?php esc_html_e( 'Email Log retention', 'submission-inbox' ); ?>
				<?php SUBIN_View::help_tooltip( __( 'Controls Email Log event history only. Saved leads use the separate Lead storage retention policy.', 'submission-inbox' ) ); ?>
			</h2>
			<p><?php esc_html_e( 'Choose how long InboxMend keeps email event metadata for investigation and troubleshooting. This does not change how long saved leads are retained.', 'submission-inbox' ); ?></p>
			<table class="form-table" role="presentation">
				<tbody>
					<tr>
						<th scope="row">
							<label for="subin-mail-log-retention-days"><?php esc_html_e( 'Keep Email Log events for', 'submission-inbox' ); ?></label>
						</th>
						<td>
							<select id="subin-mail-log-retention-days" name="mail_log_retention_days">
								<?php foreach ( SUBIN_SettingsRepository::mail_log_retention_presets() as $days ) : ?>
									<?php if ( 0 === $days ) : ?>
										<option value="0" <?php selected( $mail_log_retention_days, 0 ); ?>><?php esc_html_e( 'Forever', 'submission-inbox' ); ?></option>
									<?php else : ?>
										<option value="<?php echo esc_attr( (string) $days ); ?>" <?php selected( $mail_log_retention_days, $days ); ?>>
											<?php
											printf(
												/* translators: %d: number of retention days. */
												esc_html__( '%d days', 'submission-inbox' ),
												absint( $days )
											);
											?>
										</option>
									<?php endif; ?>
								<?php endforeach; ?>
							</select>
							<p class="description"><?php esc_html_e( 'Scheduled cleanup permanently removes older Email Log events. Forever disables this cleanup. Email Log stores delivery metadata and a one-way message fingerprint, not message bodies or attachments.', 'submission-inbox' ); ?></p>
							<p class="description"><strong><?php esc_html_e( 'Lead retention is configured separately under Lead storage.', 'submission-inbox' ); ?></strong></p>
						</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="" class="subin-settings-form" data-subin-settings-form>
			<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( 'Form integrations', 'submission-inbox' ); ?></h2>
				<p><?php esc_html_e( 'Choose which installed form plugins may create linked Inbox leads and use InboxMend workflows.', 'submission-inbox' ); ?></p>
				<?php if ( empty( $settings['store_leads'] ) ) : ?>
					<div class="subin-settings-callout subin-settings-callout--warning" role="note">
						<span class="dashicons dashicons-warning" aria-hidden="true"></span>
						<div>
							<strong><?php esc_html_e( 'All captures are currently paused', 'submission-inbox' ); ?></strong>
							<p><?php esc_html_e( 'Enabled integrations remain selected, but they cannot add leads while global Lead storage is off.', 'submission-inbox' ); ?> <a href="<?php echo esc_url( $this->tab_url( 'lead_storage' ) ); ?>"><?php esc_html_e( 'Review Lead storage', 'submission-inbox' ); ?></a></p>
						</div>
					</div>
				<?php endif; ?>
				<div class="subin-integration-list">
					<?php
					$this->render_integration_card(
						__( 'Contact Form 7', 'submission-inbox' ),
						$cf7_active,
						'enable_cf7_integration',
						(bool) $settings['enable_cf7_integration'],
						$cf7_active ? __( 'Create linked Inbox leads from successful Contact Form 7 submissions.', 'submission-inbox' ) : __( 'Install and activate Contact Form 7 to make this connection available.', 'submission-inbox' ),
						(bool) $settings['store_leads']
					);
					$this->render_integration_card(
						__( 'WPForms', 'submission-inbox' ),
						$wpforms_active,
						'enable_wpforms_integration',
						(bool) $settings['enable_wpforms_integration'],
						$wpforms_active ? __( 'Create linked Inbox leads from successful WPForms submissions.', 'submission-inbox' ) : __( 'Install and activate WPForms to make this connection available.', 'submission-inbox' ),
						(bool) $settings['store_leads']
					);
					$this->render_integration_card(
						__( 'Fluent Forms', 'submission-inbox' ),
						$fluent_active,
						'enable_fluent_forms_integration',
						(bool) $settings['enable_fluent_forms_integration'],
						$fluent_active ? __( 'Create linked Inbox leads from successful Fluent Forms submissions.', 'submission-inbox' ) : __( 'Install and activate Fluent Forms to make this connection available.', 'submission-inbox' ),
						(bool) $settings['store_leads']
					);
					$this->render_integration_card(
						__( 'Forminator', 'submission-inbox' ),
						$forminator_active,
						'enable_forminator_integration',
						(bool) $settings['enable_forminator_integration'],
						$forminator_active ? __( 'Create linked Inbox leads from successful Forminator submissions.', 'submission-inbox' ) : __( 'Install and activate Forminator to make this connection available.', 'submission-inbox' ),
						(bool) $settings['store_leads']
					);
					$this->render_integration_card(
						__( 'Ninja Forms', 'submission-inbox' ),
						$ninja_forms_active,
						'enable_ninja_forms_integration',
						(bool) $settings['enable_ninja_forms_integration'],
						$ninja_forms_active ? __( 'Create linked Inbox leads from successful Ninja Forms submissions.', 'submission-inbox' ) : __( 'Install and activate Ninja Forms to make this connection available.', 'submission-inbox' ),
						(bool) $settings['store_leads']
					);
					?>
				</div>
			</div>
			<?php $this->render_settings_savebar(); ?>
		</form>
		<?php
	}

	/**
	 * Spam protection tab.
	 *
	 * @param array<string,mixed> $settings Settings.
	 * @return void
	 */
	private function render_spam_protection_tab( array $settings ) {
		$has_recaptcha_secret = '' !== trim( (string) $settings['recaptcha_secret_key'] );
		?>
		<div class="subin-settings-section subin-spam-protection">
			<div class="subin-settings-section__header">
				<div>
					<h2><?php esc_html_e( 'Spam protection', 'submission-inbox' ); ?></h2>
					<p><?php esc_html_e( 'InboxMend forms always use server-side rate limiting and a honeypot. Google reCAPTCHA v3 is an optional additional signal.', 'submission-inbox' ); ?></p>
				</div>
				<?php SUBIN_View::badge( __( 'Core protection active', 'submission-inbox' ), 'success' ); ?>
			</div>
			<?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( 'Google reCAPTCHA v3', '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'] ); ?>
							<?php if ( $has_recaptcha_secret ) : ?>
								<label class="subin-settings-inline-check"><input type="checkbox" name="recaptcha_clear_secret" value="1" /> <?php esc_html_e( 'Remove the saved Secret Key', 'submission-inbox' ); ?></label>
							<?php endif; ?>
						</td>
					</tr>
					<tr data-subin-show-when="recaptcha_enabled">
						<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 in the browser to request a score for built-in InboxMend forms only.', 'submission-inbox' ); ?> <a href="https://www.google.com/recaptcha/admin/create" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Create reCAPTCHA v3 keys', 'submission-inbox' ); ?></a></p>
						</td>
					</tr>
					<tr data-subin-show-when="recaptcha_enabled">
						<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="" autocomplete="new-password" placeholder="<?php echo esc_attr( $has_recaptcha_secret ? __( 'Saved — enter a new key to replace it', 'submission-inbox' ) : '' ); ?>" />
							<p class="description"><?php esc_html_e( 'Used server-side to verify the reCAPTCHA token with Google. Leave blank to keep the saved key.', 'submission-inbox' ); ?></p>
						</td>
					</tr>
					<tr data-subin-show-when="recaptcha_enabled">
						<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>
					<tr data-subin-show-when="recaptcha_enabled">
						<th scope="row"><?php esc_html_e( 'Visitor IP disclosure', 'submission-inbox' ); ?></th>
						<td>
							<input type="hidden" name="store_ip" value="0" />
							<?php SUBIN_View::checkbox_field( 'store_ip', __( 'Send the visitor IP address to Google during reCAPTCHA verification', 'submission-inbox' ), (bool) $settings['store_ip'] ); ?>
							<p class="description"><?php esc_html_e( 'InboxMend does not store the raw IP in the lead. This option only adds it to the verification request sent to Google.', '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>
					<?php /* translators: %1$d: number of pending Cloud Sync events. */ ?>
					<strong><?php echo esc_html( sprintf( __( '%1$d pending', 'submission-inbox' ), absint( $queue_counts['pending'] ?? 0 ) ) ); ?></strong>
					<?php /* translators: %1$d: failed Cloud Sync events, %2$d: sent Cloud Sync events. */ ?>
					<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" data-subin-requires-saved-settings><?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" data-subin-requires-saved-settings data-subin-settings-confirm="<?php echo esc_attr__( 'Rotate the Cloud token? The previous token will stop working.', 'submission-inbox' ); ?>"><?php esc_html_e( 'Rotate token', 'submission-inbox' ); ?></button>
							<button type="submit" name="subin_cloud_action" value="revoke" class="button button-link-delete" data-subin-requires-saved-settings data-subin-settings-confirm="<?php echo esc_attr__( 'Disconnect this site from InboxMend Cloud?', 'submission-inbox' ); ?>"><?php esc_html_e( 'Disconnect', 'submission-inbox' ); ?></button>
							<?php if ( $push_transport ) : ?>
								<button type="submit" name="subin_cloud_action" value="heartbeat" class="button" data-subin-requires-saved-settings><?php esc_html_e( 'Send heartbeat', 'submission-inbox' ); ?></button>
								<button type="submit" name="subin_cloud_action" value="sync" class="button" data-subin-requires-saved-settings><?php esc_html_e( 'Send pending now', 'submission-inbox' ); ?></button>
								<button type="submit" name="subin_cloud_action" value="retry_failed" class="button" data-subin-requires-saved-settings<?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>
						<p class="description"><?php esc_html_e( 'Health only shares technical identifiers, delivery and workflow status, and hashed email diagnostics. Lead summary also shares form and timestamp context, a masked name initial, and masked or site-keyed hashed email indicators. Full lead data may additionally include direct contact details, messages, fields, source URLs, and notes, and requires the consent below.', 'submission-inbox' ); ?></p>
						<?php if ( ! empty( $settings['cloud_sync_allowed_mode'] ) ) : ?>
							<?php /* translators: %s: maximum Cloud Sync data-sharing mode allowed by the server. */ ?>
							<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>

				</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( SUBIN_PLUGIN_VERSION ); ?></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 /* translators: %d: number of pending Cloud Sync events. */ ?>
						<?php SUBIN_View::badge( sprintf( __( 'Pending: %d', 'submission-inbox' ), absint( $queue_counts['pending'] ?? 0 ) ), 'info' ); ?>
						<?php /* translators: %d: number of failed Cloud Sync events. */ ?>
						<?php SUBIN_View::badge( sprintf( __( 'Failed: %d', 'submission-inbox' ), absint( $queue_counts['failed'] ?? 0 ) ), ! empty( $queue_counts['failed'] ) ? 'danger' : 'muted' ); ?>
						<?php /* translators: %d: number of sent Cloud Sync events. */ ?>
						<?php SUBIN_View::badge( sprintf( __( 'Sent: %d', 'submission-inbox' ), absint( $queue_counts['sent'] ?? 0 ) ), 'success' ); ?>
						<?php /* translators: %d: number of skipped Cloud Sync events. */ ?>
						<?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.
	 * @param bool   $storage_enabled Whether global lead capture is active.
	 * @return void
	 */
	private function render_integration_card( $title, $detected, $setting_key, $enabled, $description, $storage_enabled ) {
		?>
		<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
				$status = ! $detected ? __( 'Not installed', 'submission-inbox' ) : ( $enabled && $storage_enabled ? __( 'Capturing', 'submission-inbox' ) : ( $enabled ? __( 'Paused', 'submission-inbox' ) : __( 'Off', 'submission-inbox' ) ) );
				$tone   = $detected && $enabled && $storage_enabled ? 'success' : ( $detected && $enabled ? 'warning' : 'muted' );
				SUBIN_View::badge( $status, $tone );
				?>
				<?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, InboxMend does not create linked leads or provider-specific workflow context. Email Log may still record WordPress mail events generated by the form plugin.', 'submission-inbox' ), __( 'Integration capture', '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 subin-settings-danger-zone">
			<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( 'Deactivation always keeps data. If deletion is enabled, uninstalling InboxMend permanently removes its forms, leads, notes, email events, rules, saved designs, settings, and local sync history.', '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( 'This setting does not delete anything now. It changes what happens later if the plugin is deleted from WordPress.', 'submission-inbox' ); ?></p>
						</td>
					</tr>
					<tr data-subin-show-when="delete_data_on_uninstall">
						<td colspan="2">
							<div class="subin-settings-callout subin-settings-callout--danger" role="alert">
								<span class="dashicons dashicons-trash" aria-hidden="true"></span>
								<div>
									<strong><?php esc_html_e( 'Permanent deletion on uninstall', 'submission-inbox' ); ?></strong>
									<p><?php esc_html_e( 'Export anything you need before uninstalling. This cannot be undone from InboxMend.', 'submission-inbox' ); ?></p>
									<label><input type="checkbox" name="subin_confirm_delete_on_uninstall" value="1" /> <?php esc_html_e( 'I understand what will be permanently removed.', 'submission-inbox' ); ?></label>
								</div>
							</div>
						</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' )
		);
	}

	/**
	 * Handle one Team access update and consume the request.
	 *
	 * Returning true prevents the generic settings saver from treating the
	 * dedicated Team access nonce as an invalid settings nonce.
	 *
	 * @return bool Whether this was a Team access request.
	 */
	private function maybe_handle_team_access_post() {
		$request_method = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ?? '' ) );
		$raw_action     = $_POST['subin_settings_action'] ?? ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- This only routes to the nonce-protected handler below.
		$action         = is_string( $raw_action ) ? sanitize_key( wp_unslash( $raw_action ) ) : '';
		if ( 'POST' !== strtoupper( $request_method ) || self::TEAM_ACCESS_ACTION !== $action ) {
			return false;
		}

		if ( ! $this->can_manage_team_access() ) {
			wp_die( esc_html__( 'You do not have permission to manage team access.', 'submission-inbox' ) );
		}

		$raw_user_id = $_POST['subin_team_user_id'] ?? 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Needed to select the user-bound nonce action; verified below before lookup or mutation.
		$user_id     = is_scalar( $raw_user_id ) ? absint( wp_unslash( $raw_user_id ) ) : 0;
		$raw_nonce   = $_POST[ self::TEAM_ACCESS_NONCE_NAME ] ?? ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified immediately below.
		if ( $user_id <= 0 || ! is_string( $raw_nonce ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $raw_nonce ) ), self::TEAM_ACCESS_NONCE_ACTION . $user_id ) ) {
			add_settings_error(
				'subin_messages',
				'subin_team_security_check_failed',
				__( 'Security check failed. Reload Team access and try again.', 'submission-inbox' ),
				'error'
			);
			return true;
		}

		$user = get_userdata( $user_id );
		if ( ! $user || ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
			add_settings_error(
				'subin_messages',
				'subin_team_user_invalid',
				__( 'The selected WordPress user is not available on this site.', 'submission-inbox' ),
				'error'
			);
			return true;
		}

		if ( user_can( $user_id, 'manage_options' ) || ! current_user_can( 'promote_user', $user_id ) ) {
			wp_die( esc_html__( 'You do not have permission to change access for this user.', 'submission-inbox' ) );
		}

		$raw_preset = $_POST['subin_team_preset'] ?? ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified above.
		$preset     = is_string( $raw_preset ) ? sanitize_key( wp_unslash( $raw_preset ) ) : '';
		if ( ! in_array(
			$preset,
			array(
				\SUBIN\Security\TeamAccessService::PRESET_NONE,
				\SUBIN\Security\TeamAccessService::PRESET_VIEWER,
				\SUBIN\Security\TeamAccessService::PRESET_MANAGER,
			),
			true
		) ) {
			add_settings_error(
				'subin_messages',
				'subin_team_preset_invalid',
				__( 'Choose No access, Viewer, or Manager before saving.', 'submission-inbox' ),
				'error'
			);
			return true;
		}

		$raw_export = $_POST['subin_team_export'] ?? false; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified above.
		$export     = is_scalar( $raw_export ) && rest_sanitize_boolean( wp_unslash( $raw_export ) );
		if ( \SUBIN\Security\TeamAccessService::PRESET_NONE === $preset ) {
			$export = false;
		}

		$result = \SUBIN\Security\TeamAccessService::update_user( $user_id, $preset, $export, get_current_user_id() );
		if ( is_wp_error( $result ) ) {
			add_settings_error(
				'subin_messages',
				'subin_team_access_update_failed',
				$result->get_error_message(),
				'error'
			);
			return true;
		}

		add_settings_error(
			'subin_messages',
			'subin_team_access_updated',
			sprintf(
				/* translators: %s: WordPress user's display name. */
				__( 'Inbox access updated for %s.', 'submission-inbox' ),
				$user->display_name
			),
			'updated'
		);

		return true;
	}

	/**
	 * 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;
		}

		$raw_action = $_POST['subin_settings_action'] ?? ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- The allowlisted action selects a nonce- and capability-protected handler below.
		$action     = is_scalar( $raw_action ) ? sanitize_key( wp_unslash( $raw_action ) ) : '';
		$capability = $this->settings_action_capability( $action );
		if ( '' === $capability ) {
			return;
		}
		if ( ! \SUBIN\Security\Capabilities::can_current_user( $capability ) ) {
			wp_die( esc_html__( 'You do not have permission to change these settings.', 'submission-inbox' ) );
		}

		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;
		}

		$changes = array();
		$current = $this->settings->all();

		if ( 'lead_storage' === $action ) {
			$changes['store_leads']    = rest_sanitize_boolean( wp_unslash( $_POST['store_leads'] ?? false ) );
			$changes['retention_days'] = $this->sanitize_retention_days( sanitize_text_field( wp_unslash( $_POST['retention_days'] ?? 90 ) ) );
			$changes['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() ) {
				$changes['enable_cf7_integration'] = rest_sanitize_boolean( wp_unslash( $_POST['enable_cf7_integration'] ?? false ) );
			}
			if ( \SUBIN\Forms\WPFormsIntegration::is_available() ) {
				$changes['enable_wpforms_integration'] = rest_sanitize_boolean( wp_unslash( $_POST['enable_wpforms_integration'] ?? false ) );
			}
			if ( \SUBIN\Forms\FluentFormsIntegration::is_available() ) {
				$changes['enable_fluent_forms_integration'] = rest_sanitize_boolean( wp_unslash( $_POST['enable_fluent_forms_integration'] ?? false ) );
			}
			if ( \SUBIN\Forms\ForminatorIntegration::is_available() ) {
				$changes['enable_forminator_integration'] = rest_sanitize_boolean( wp_unslash( $_POST['enable_forminator_integration'] ?? false ) );
			}
			if ( \SUBIN\Forms\NinjaFormsIntegration::is_available() ) {
				$changes['enable_ninja_forms_integration'] = rest_sanitize_boolean( wp_unslash( $_POST['enable_ninja_forms_integration'] ?? false ) );
			}
		} elseif ( 'privacy' === $action ) {
			$mask_sensitive = rest_sanitize_boolean( $this->post_scalar( 'mask_sensitive_fields', false ) );
			$confirmed      = rest_sanitize_boolean( $this->post_scalar( 'subin_confirm_unmasked_storage', false ) );
			if ( ! empty( $current['mask_sensitive_fields'] ) && ! $mask_sensitive && ! $confirmed ) {
				add_settings_error(
					'subin_messages',
					'subin_redaction_confirmation_required',
					__( 'Confirm that you understand the storage impact before disabling sensitive-value redaction.', 'submission-inbox' ),
					'error'
				);
				return;
			}
			$changes['mask_sensitive_fields']    = $mask_sensitive;
			$changes['mail_log_retention_days']  = SUBIN_SettingsRepository::sanitize_mail_log_retention_days(
				$this->post_scalar( 'mail_log_retention_days', SUBIN_SettingsRepository::DEFAULT_MAIL_LOG_RETENTION_DAYS )
			);
		} elseif ( 'spam_protection' === $action ) {
			$enabled              = rest_sanitize_boolean( $this->post_scalar( 'recaptcha_enabled', false ) );
			$site_key             = sanitize_text_field( trim( (string) $this->post_scalar( 'recaptcha_site_key' ) ) );
			$submitted_secret     = $this->post_scalar( 'recaptcha_secret_key' );
			$clear_secret         = rest_sanitize_boolean( $this->post_scalar( 'recaptcha_clear_secret', false ) );
			$effective_secret     = $clear_secret ? '' : (string) ( $current['recaptcha_secret_key'] ?? '' );
			if ( '' !== (string) $submitted_secret ) {
				$normalized_secret = SUBIN_SettingsRepository::normalize_opaque_secret( $submitted_secret );
				if ( is_wp_error( $normalized_secret ) ) {
					add_settings_error( 'subin_messages', $normalized_secret->get_error_code(), $normalized_secret->get_error_message(), 'error' );
					return;
				}
				$effective_secret = $normalized_secret;
			}
			if ( $enabled && ( '' === $site_key || '' === $effective_secret ) ) {
				add_settings_error(
					'subin_messages',
					'subin_recaptcha_credentials_required',
					__( 'Enter both a Site Key and Secret Key before enabling Google reCAPTCHA.', 'submission-inbox' ),
					'error'
				);
				return;
			}
			$changes['recaptcha_enabled']         = $enabled;
			$changes['recaptcha_site_key']        = $site_key;
			$changes['store_ip']                  = rest_sanitize_boolean( $this->post_scalar( 'store_ip', false ) );
			$changes['recaptcha_score_threshold'] = $this->sanitize_recaptcha_score_threshold( $this->post_scalar( 'recaptcha_score_threshold', 0.5 ) );
			if ( $clear_secret || '' !== (string) $submitted_secret ) {
				$changes['recaptcha_secret_key'] = $effective_secret;
			}
		} elseif ( 'cloud_sync' === $action ) {
			$this->handle_cloud_sync_submit();
			return;
		} elseif ( 'uninstall' === $action ) {
			$delete_data = rest_sanitize_boolean( $this->post_scalar( 'delete_data_on_uninstall', false ) );
			$confirmed   = rest_sanitize_boolean( $this->post_scalar( 'subin_confirm_delete_on_uninstall', false ) );
			if ( empty( $current['delete_data_on_uninstall'] ) && $delete_data && ! $confirmed ) {
				add_settings_error(
					'subin_messages',
					'subin_uninstall_confirmation_required',
					__( 'Confirm that you understand what will be permanently removed before enabling uninstall deletion.', 'submission-inbox' ),
					'error'
				);
				return;
			}
			$changes['delete_data_on_uninstall'] = $delete_data;
		} else {
			return;
		}

		$result = $this->settings->patch( $changes );
		if ( is_wp_error( $result ) ) {
			add_settings_error(
				'subin_messages',
				'subin_settings_save_failed',
				$result->get_error_message(),
				'error'
			);
			return;
		}

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

	/**
	 * Resolve the exact capability required by a settings mutation.
	 *
	 * The Settings page uses one nonce for its non-SMTP forms, so the action
	 * still needs an independent authorization boundary before dispatch.
	 *
	 * @param string $action Allowlisted settings action.
	 * @return string Empty for an unsupported action.
	 */
	private function settings_action_capability( $action ) {
		if ( 'cloud_sync' === $action ) {
			return \SUBIN\Security\Capabilities::MANAGE_CLOUD;
		}

		if ( in_array( $action, array( 'lead_storage', 'privacy', 'spam_protection', 'integrations', 'uninstall' ), true ) ) {
			return \SUBIN\Security\Capabilities::MANAGE_SETTINGS;
		}

		return '';
	}

	/**
	 * Read one scalar POST value after the caller has verified the form nonce.
	 *
	 * @param string $key POST key.
	 * @param mixed  $fallback Fallback value.
	 * @return mixed
	 */
	private function post_scalar( $key, $fallback = '' ) {
		if ( ! array_key_exists( $key, $_POST ) || ! is_scalar( $_POST[ $key ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Every caller runs after the settings nonce boundary.
			return $fallback;
		}

		return wp_unslash( $_POST[ $key ] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Every caller runs after the settings nonce boundary.
	}

	/**
	 * 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.
	 *
	 * @return void
	 */
	private function handle_cloud_sync_submit() {
		$cloud_action = sanitize_key( (string) $this->post_scalar( 'subin_cloud_action', 'save' ) );
		$allowed      = array( 'save', 'connect', 'validate', 'heartbeat', 'sync', 'retry_failed', 'rotate', 'revoke' );
		if ( ! in_array( $cloud_action, $allowed, true ) ) {
			add_settings_error( 'subin_messages', 'subin_cloud_action_invalid', __( 'Choose a supported Cloud action and try again.', 'submission-inbox' ), 'error' );
			return;
		}

		if ( in_array( $cloud_action, array( 'rotate', 'revoke' ), true ) && $cloud_action !== sanitize_key( (string) $this->post_scalar( 'subin_cloud_confirm' ) ) ) {
			add_settings_error( 'subin_messages', 'subin_cloud_confirmation_required', __( 'Confirm the Cloud connection change before continuing.', 'submission-inbox' ), 'error' );
			return;
		}

		$site_key  = sanitize_text_field( trim( (string) $this->post_scalar( 'cloud_sync_site_key' ) ) );
		$data_mode = $this->sanitize_cloud_sync_data_mode( $this->post_scalar( 'cloud_sync_data_mode', 'lead_summary' ) );
		$consent   = rest_sanitize_boolean( $this->post_scalar( 'cloud_sync_full_data_consent', false ) );

		if ( in_array( $cloud_action, array( 'save', 'connect' ), true ) ) {
			$result = $this->settings->patch(
				array(
					'cloud_sync_site_key'          => $site_key,
					'cloud_sync_license_key'       => $site_key,
					'cloud_sync_data_mode'         => $data_mode,
					'cloud_sync_full_data_consent' => $consent,
				)
			);
			if ( is_wp_error( $result ) ) {
				$this->add_cloud_action_notice( $result, '' );
				return;
			}
		}

		if ( 'save' === $cloud_action ) {
			add_settings_error( 'subin_messages', 'subin_settings_saved', __( 'Settings saved.', 'submission-inbox' ), 'updated' );
			return;
		}

		$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 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() );
		delete_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 ) );
	}
}
