<?php

declare(strict_types=1);

namespace SecurWP\Admin;

use SecurWP\Includes\Capabilities;
use SecurWP\Modules\Firewall\FirewallConfigService;
use SecurWP\Modules\Hardening\Hardening;

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

/**
 * Hosts the wp-admin menu shell and React admin application.
 */
class Admin {

	/**
	 * Plugin slug used for handles and page IDs.
	 */
	private string $plugin_name;

	/**
	 * Current plugin version.
	 */
	private string $version;

	/**
	 * Parsed build metadata for the admin app.
	 *
	 * @var array<string, mixed>|null
	 */
	private ?array $admin_build_meta = null;

	/**
	 * @param string $plugin_name Plugin slug.
	 * @param string $version     Plugin version.
	 */
	public function __construct( string $plugin_name, string $version ) {
		$this->plugin_name = $plugin_name;
		$this->version     = $version;
	}

	/**
	 * Load the admin build metadata when available.
	 *
	 * @return array<string, mixed>
	 */
	private function get_admin_build_meta(): array {
		if ( $this->admin_build_meta !== null ) {
			return $this->admin_build_meta;
		}

		$build_meta_path = SECURWP_PLUGIN_DIR . 'assets/admin-app/build-meta.json';
		if ( ! file_exists( $build_meta_path ) ) {
			$this->admin_build_meta = [];
			return $this->admin_build_meta;
		}

		$build_meta_raw = file_get_contents( $build_meta_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- trusted local build artifact
		if ( ! is_string( $build_meta_raw ) || $build_meta_raw === '' ) {
			$this->admin_build_meta = [];
			return $this->admin_build_meta;
		}

		$decoded = json_decode( $build_meta_raw, true );
		$this->admin_build_meta = is_array( $decoded ) ? $decoded : [];

		return $this->admin_build_meta;
	}

	/**
	 * Resolve the current SecurWP admin page slug.
	 *
	 * @return string
	 */
	private function get_current_admin_page_slug(): string {
		$page = filter_input( INPUT_GET, 'page', FILTER_UNSAFE_RAW );

		if ( ! is_string( $page ) || $page === '' ) {
			return '';
		}

		return sanitize_key( wp_unslash( $page ) );
	}

	/**
	 * Determine whether the current request is for a SecurWP admin screen.
	 *
	 * @return bool
	 */
	private function is_securwp_admin_page(): bool {
		$current_page = $this->get_current_admin_page_slug();

		return $current_page === $this->plugin_name || str_starts_with( $current_page, $this->plugin_name . '-' );
	}

	/**
	 * Resolve the current admin page to a page entry key.
	 *
	 * @param array<string, mixed> $build_meta The parsed build metadata.
	 * @param string               $current_page The current SecurWP page slug.
	 * @return string
	 */
	private function resolve_admin_entry_key( array $build_meta, string $current_page ): string {
		$default_entry = isset( $build_meta['defaultEntry'] ) && is_string( $build_meta['defaultEntry'] )
			? $build_meta['defaultEntry']
			: 'dashboard';
		$network_entry = isset( $build_meta['networkEntry'] ) && is_string( $build_meta['networkEntry'] )
			? $build_meta['networkEntry']
			: $default_entry;
		$page_map      = isset( $build_meta['pageMap'] ) && is_array( $build_meta['pageMap'] )
			? $build_meta['pageMap']
			: [];

		if ( is_network_admin() ) {
			return $network_entry;
		}

		if ( isset( $page_map[ $current_page ] ) && is_string( $page_map[ $current_page ] ) ) {
			return $page_map[ $current_page ];
		}

		return $default_entry;
	}

	/**
	 * Resolve the current admin app entry bundle and CSS asset.
	 *
	 * @return array{script: string|null, style: string|null, version: string}
	 */
		private function get_admin_app_assets(): array {
			$build_meta   = $this->get_admin_build_meta();
			$current_page = $this->get_current_admin_page_slug();
			$entry_key    = $this->resolve_admin_entry_key( $build_meta, $current_page );
			$entries      = isset( $build_meta['entries'] ) && is_array( $build_meta['entries'] ) ? $build_meta['entries'] : [];
		$script_file  = isset( $entries[ $entry_key ] ) && is_string( $entries[ $entry_key ] ) ? $entries[ $entry_key ] : null;
		$style_file   = isset( $build_meta['style'] ) && is_string( $build_meta['style'] ) ? $build_meta['style'] : null;
			$build_ver    = isset( $build_meta['buildVersion'] ) && is_scalar( $build_meta['buildVersion'] )
				? (string) $build_meta['buildVersion']
				: $this->version;
			$build_meta_path = SECURWP_PLUGIN_DIR . 'assets/admin-app/build-meta.json';
			$build_stamp     = file_exists( $build_meta_path ) ? (int) filemtime( $build_meta_path ) : 0;
			$asset_version   = $build_stamp > 0 ? $build_ver . '.' . $build_stamp : $build_ver;

			return [
				'script'  => $script_file,
				'style'   => $style_file,
				'version' => $asset_version,
			];
		}

	/**
	 * Register admin app styles.
	 *
	 * @param string $hook_suffix Current admin page hook.
	 */
	public function enqueue_styles( string $hook_suffix ): void {
		if ( strpos( $hook_suffix, $this->plugin_name ) === false ) {
			return;
		}

		$admin_assets = $this->get_admin_app_assets();
		$style_file   = $admin_assets['style'];
		$build_ver    = $admin_assets['version'];

		if ( is_string( $style_file ) && $style_file !== '' ) {
			$style_path = SECURWP_PLUGIN_DIR . 'assets/admin-app/' . ltrim( $style_file, '/' );
			if ( file_exists( $style_path ) ) {
				wp_enqueue_style(
					$this->plugin_name . '-admin-styles',
					SECURWP_PLUGIN_URL . 'assets/admin-app/' . ltrim( $style_file, '/' ),
					array(),
					$build_ver
				);
			}
		}
	}

	/**
	 * Register admin app scripts.
	 *
	 * @param string $hook_suffix Current admin page hook.
	 */
	public function enqueue_scripts( string $hook_suffix ): void {
		if ( strpos( $hook_suffix, $this->plugin_name ) === false ) {
			return;
		}

		$admin_assets = $this->get_admin_app_assets();
		$script_file  = $admin_assets['script'];

		if ( ! is_string( $script_file ) || $script_file === '' ) {
			return;
		}

		$script_path = SECURWP_PLUGIN_DIR . 'assets/admin-app/' . ltrim( $script_file, '/' );
		$script_url  = SECURWP_PLUGIN_URL . 'assets/admin-app/' . ltrim( $script_file, '/' );
		$build_ver   = $admin_assets['version'];

		if ( ! file_exists( $script_path ) ) {
			return;
		}

		// Ensure WP dependencies are available before the module loads.
		wp_enqueue_script( 'wp-i18n' );
		wp_enqueue_script( 'wp-api-fetch' );

		// Register the page entry module with a build version.  The emitted
		// module graph uses the same version in all relative imports, so the
		// browser resolves one coherent build while WordPress retains cache
		// control over the entry URL.
		wp_register_script(
			$this->plugin_name . '-admin-script',
			$script_url,
			array( 'wp-i18n', 'wp-api-fetch' ),
			$build_ver,
			true
		);
		wp_enqueue_script( $this->plugin_name . '-admin-script' );

		$current_page  = $this->get_current_admin_page_slug();
		$localize_data = [
			'apiUrl'          => rest_url( $this->plugin_name . '/v1/' ),
			'apiRoot'         => rest_url(),
			'nonce'           => wp_create_nonce( 'wp_rest' ),
			'secure_nonce'    => wp_create_nonce( 'securwp_nonce' ),
			'pluginName'      => $this->plugin_name,
			'version'         => $this->version,
			'assetsUrl'       => SECURWP_PLUGIN_URL . 'assets/admin-app/',
			'adminUrl'        => admin_url(),
			'ajaxUrl'         => admin_url( 'admin-ajax.php' ),
			'networkAdminUrl' => is_network_admin() ? network_admin_url() : admin_url(),
			'isNetworkAdmin'  => is_network_admin(),
			'currentPage'     => $current_page,
			'branding'        => $this->get_branding_data(),
		];

		// Call get_all() once; license and key-health checks need the same cloud snapshot.
		$cloud_all = \SecurWP\Modules\Cloud\Service\CloudSettings::get_all();
		$key_error = $cloud_all['_key_error'] ?? null;
		if ( ! $key_error ) {
			try {
				\SecurWP\Includes\MasterKey::get();
			} catch ( \RuntimeException $e ) {
				$key_error = \SecurWP\Includes\MasterKey::get_last_error();
			}
		}

		if ( ! $key_error ) {
			$key_error = \SecurWP\Includes\SecretManager::detect_store_key_issue();
		}

		if ( $key_error ) {
			$key_path = is_array( $key_error ) && ! empty( $key_error['path'] )
				? (string) $key_error['path']
				: \SecurWP\Includes\MasterKey::get_key_file_path();
			$key_dir                       = dirname( $key_path );
			$writable_probe                = is_dir( $key_dir ) ? $key_dir : dirname( $key_dir );
			$key_error['key_source']       = \SecurWP\Includes\MasterKey::get_source();
			$key_error['storage_writable'] = (
				\SecurWP\Includes\MasterKey::get_source() !== 'constant'
				&& $writable_probe !== ''
				&& wp_is_writable( $writable_probe )
			);
			$localize_data['keyError']     = $key_error;
		}

		// Console restore uses site_uuid first, with license_key as the recovery path.
		$raw_cloud    = \SecurWP\Modules\Cloud\Service\CloudSettings::get_raw();
		$has_identity = ( $raw_cloud['site_uuid'] ?? '' ) !== ''
			|| ( $raw_cloud['license_key'] ?? '' ) !== ''
			|| \SecurWP\Includes\SettingsStore::get_field( \SecurWP\Includes\SettingsStore::COL_STATE, 'last_license_key', '' ) !== '';
		$localize_data['keyBackupAvailable'] = $has_identity
			&& \SecurWP\Includes\SettingsStore::get_field( \SecurWP\Includes\SettingsStore::COL_STATE, 'key_backup_available', '' ) === '1';

		$fw_settings             = FirewallConfigService::load_effective_settings();
		$harden_settings         = \SecurWP\Includes\SettingsStore::get( \SecurWP\Includes\SettingsStore::COL_HARDENING );
		$firewall_enabled        = ! empty( $fw_settings['enabled'] );
		$firewall_origin_mode    = FirewallConfigService::get_origin_enforcement_mode( $fw_settings );
		$recommended_hardening   = Hardening::get_recommended_hardening_keys();
		$hardening_count         = is_array( $harden_settings )
			? Hardening::count_effective_hardening_controls( $harden_settings, $recommended_hardening )
			: 0;
		$cloud_has_creds         = ! empty( $cloud_all['site_token'] ) && ! empty( $cloud_all['site_secret'] );
		$effective_license_status = (string) ( $cloud_all['license_status'] ?? '' );
		if ( $effective_license_status === 'active' && ! $cloud_has_creds ) {
			$effective_license_status = '';
		}

		$localize_data['setupStatus'] = [
			'connected'          => $cloud_has_creds && $effective_license_status !== '',
			'plan'               => (string) ( $cloud_all['license_plan'] ?? 'free' ),
			'firewall'           => $firewall_origin_mode === FirewallConfigService::MODE_ENFORCE,
			'firewallEnabled'    => $firewall_enabled,
			'firewallOriginMode' => $firewall_origin_mode,
			'hardening'          => $hardening_count >= Hardening::get_recommended_hardening_threshold(),
			'hardeningCount'     => $hardening_count,
			'loginProtection'    => ! empty( $harden_settings['limit_login_attempts_enabled'] ),
			'scanned'            => (int) \SecurWP\Includes\SettingsStore::get_field( \SecurWP\Includes\SettingsStore::COL_STATE, 'scans_completed', 0 ) > 0,
			'dismissed'          => \SecurWP\Includes\SettingsStore::get_field( \SecurWP\Includes\SettingsStore::COL_STATE, 'setup_dismissed', '' ) === '1',
		];
		$localize_data['licenseStatus'] = $effective_license_status;
		$localize_data['licensePlan']   = (string) ( $cloud_all['license_plan'] ?? 'free' );
		$localize_data['siteMigration'] = [
			'status'         => sanitize_text_field( (string) ( $cloud_all['migration_status'] ?? 'none' ) ),
			'oldUrl'         => esc_url_raw( (string) ( $cloud_all['linked_record_url'] ?? '' ) ),
			'pendingUrl'     => esc_url_raw( (string) ( $cloud_all['migration_pending_url'] ?? '' ) ),
			'replaceAllowed' => (bool) ( $cloud_all['migration_replace_allowed'] ?? true ),
			'replaceMessage' => sanitize_text_field( (string) ( $cloud_all['migration_replace_message'] ?? '' ) ),
		];

		wp_localize_script( $this->plugin_name . '-admin-script', 'securwpData', $localize_data );
		wp_set_script_translations( $this->plugin_name . '-admin-script', 'securwp', SECURWP_PLUGIN_DIR . 'languages' );
	}

	/**
	 * Add a body class for SecurWP admin pages.
	 *
	 * @param string $classes Existing admin body classes.
	 * @return string
	 */
	public function add_admin_body_class( string $classes ): string {
		if ( ! $this->is_securwp_admin_page() ) {
			return $classes;
		}

		return trim( $classes . ' securwp-admin-screen' );
	}

	/**
	 * Hide native wp-admin notices on SecurWP pages so the app owns the full page body.
	 *
	 * Keeps the standard WordPress admin lifecycle intact while suppressing third-party
	 * banners outside the SecurWP React root.
	 *
	 * @return void
	 */
	public function suppress_admin_notices_on_securwp_pages(): void {
		if ( ! $this->is_securwp_admin_page() ) {
			return;
		}

		?>
		<style id="securwp-admin-notice-suppression">
			body.securwp-admin-screen #wpbody-content > .notice,
			body.securwp-admin-screen #wpbody-content > .updated,
			body.securwp-admin-screen #wpbody-content > .error,
			body.securwp-admin-screen #wpbody-content > .update-nag,
			body.securwp-admin-screen #wpbody-content > .fs-notice,
			body.securwp-admin-screen #wpbody-content > #message,
			body.securwp-admin-screen #wpbody-content > div[class*="notice-"],
			body.securwp-admin-screen #wpbody-content > .wrap > .notice,
			body.securwp-admin-screen #wpbody-content > .wrap > .updated,
			body.securwp-admin-screen #wpbody-content > .wrap > .error,
			body.securwp-admin-screen #wpbody-content > .wrap > .update-nag,
			body.securwp-admin-screen #wpbody-content > .wrap > .fs-notice,
			body.securwp-admin-screen #wpbody-content > .wrap > #message,
			body.securwp-admin-screen #wpbody-content > .wrap > div[class*="notice-"],
			body.securwp-admin-screen .securwp-admin-notice-hidden {
				display: none !important;
			}
		</style>
		<script id="securwp-admin-notice-suppression-script">
		(function() {
			var rootId = 'securwp-admin-app';
			var hiddenClass = 'securwp-admin-notice-hidden';
			var noticeSelector = [
				'.notice',
				'.updated',
				'.error',
				'.update-nag',
				'.fs-notice',
				'#message',
				'[class*="notice-"]'
			].join(',');

			function isElement(value) {
				return value && value.nodeType === 1;
			}

			function isInsideSecurWPApp(node) {
				return !!(isElement(node) && node.closest('#' + rootId));
			}

			function isInsideScreenMeta(node) {
				return !!(isElement(node) && node.closest('#screen-meta, #screen-meta-links'));
			}

			function isSuppressibleNotice(node) {
				if (!isElement(node)) {
					return false;
				}

				if (node.id === rootId || isInsideSecurWPApp(node) || isInsideScreenMeta(node)) {
					return false;
				}

				if (!node.closest('#wpbody-content')) {
					return false;
				}

				return node.matches(noticeSelector);
			}

			function hideNotice(node) {
				if (!isSuppressibleNotice(node)) {
					return;
				}

				node.classList.add(hiddenClass);
				node.setAttribute('data-securwp-hidden-notice', '1');
				node.style.display = 'none';
			}

			function scan(container) {
				if (!isElement(container) && container !== document) {
					return;
				}

				var root = container === document ? document.getElementById('wpbody-content') : container;
				if (!root) {
					return;
				}

				if (isSuppressibleNotice(root)) {
					hideNotice(root);
				}

				var notices = root.querySelectorAll(noticeSelector);
				notices.forEach(hideNotice);
			}

			function boot() {
				scan(document);

				var target = document.getElementById('wpbody-content');
				if (!target || typeof MutationObserver === 'undefined') {
					return;
				}

				var observer = new MutationObserver(function(mutations) {
					mutations.forEach(function(mutation) {
						mutation.addedNodes.forEach(function(node) {
							if (!isElement(node)) {
								return;
							}

							hideNotice(node);
							scan(node);
						});
					});
				});

				observer.observe(target, { childList: true, subtree: true });
			}

			if (document.readyState === 'loading') {
				document.addEventListener('DOMContentLoaded', boot);
			} else {
				boot();
			}
		}());
		</script>
		<?php
	}

	/**
	 * Replace the enqueued script tag with a module script.
	 *
	 * The admin build appends the same build version to internal imports, so
	 * the entry URL can retain WordPress versioning safely.
	 *
	 * @param string $tag    Script tag.
	 * @param string $handle Script handle.
	 * @param string $src    Script URL.
	 * @return string Modified script tag.
	 */
	public function add_type_attribute( string $tag, string $handle, string $src ): string {
		if ( $handle !== $this->plugin_name . '-admin-script' ) {
			return $tag;
		}

		return '<script type="module" src="' . esc_url( $src ) . '"></script>' . "\n"; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
	}

	/**
	 * Add the admin menu and submenus.
	 */
	public function add_admin_menu(): void {
		$cloud_all       = \SecurWP\Modules\Cloud\Service\CloudSettings::get_all();
		$cloud_has_creds = ! empty( $cloud_all['site_token'] ) && ! empty( $cloud_all['site_secret'] );
		$license_status  = (string) ( $cloud_all['license_status'] ?? '' );
		if ( $license_status === 'active' && ! $cloud_has_creds ) {
			$license_status = '';
		}

		$setup_dismissed = \SecurWP\Includes\SettingsStore::get_field(
			\SecurWP\Includes\SettingsStore::COL_STATE,
			'setup_dismissed',
			''
		) === '1';
		$wizard_done     = $setup_dismissed || ( $cloud_has_creds && $license_status !== '' );

		$icon_path         = SECURWP_PLUGIN_DIR . 'assets/images/icon-light.svg';
		$icon_contents     = file_exists( $icon_path ) ? file_get_contents( $icon_path ) : false; // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- local trusted path
		$icon              = ( $icon_contents !== false && $icon_contents !== '' )
			? 'data:image/svg+xml;base64,' . base64_encode( $icon_contents ) // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
			: 'dashicons-shield-alt';
		$plugin_capability = Capabilities::admin_capability();

		add_menu_page(
			esc_html__( 'SecurWP Dashboard', 'securwp' ),
			esc_html__( 'SecurWP', 'securwp' ),
			$plugin_capability,
			$this->plugin_name,
			array( $this, 'display_react_admin_page' ),
			$icon,
			81
		);

		add_action( 'admin_head', array( $this, 'admin_menu_icon_styles' ) );

		$dashboard_label = $wizard_done
			? esc_html__( 'Dashboard', 'securwp' )
			: esc_html__( 'Setup', 'securwp' );

		add_submenu_page(
			$this->plugin_name,
			esc_html__( 'SecurWP Dashboard', 'securwp' ),
			$dashboard_label,
			$plugin_capability,
			$this->plugin_name,
			array( $this, 'display_react_admin_page' )
		);

		add_submenu_page(
			$this->plugin_name,
			esc_html__( 'Scan', 'securwp' ),
			esc_html__( 'Scan', 'securwp' ),
			$plugin_capability,
			$this->plugin_name . '-scanner',
			array( $this, 'display_react_admin_page' )
		);

		add_submenu_page(
			$this->plugin_name,
			esc_html__( 'Hardening', 'securwp' ),
			esc_html__( 'Hardening', 'securwp' ),
			$plugin_capability,
			$this->plugin_name . '-hardening',
			array( $this, 'display_react_admin_page' )
		);

		add_submenu_page(
			$this->plugin_name,
			esc_html__( 'Login Security', 'securwp' ),
			esc_html__( 'Login Security', 'securwp' ),
			$plugin_capability,
			$this->plugin_name . '-login-security',
			array( $this, 'display_react_admin_page' )
		);

		add_submenu_page(
			$this->plugin_name,
			esc_html__( 'Firewall', 'securwp' ),
			esc_html__( 'Firewall', 'securwp' ),
			$plugin_capability,
			$this->plugin_name . '-firewall',
			array( $this, 'display_react_admin_page' )
		);

		add_submenu_page(
			$this->plugin_name,
			esc_html__( 'Vulnerabilities', 'securwp' ),
			esc_html__( 'Vulnerabilities', 'securwp' ),
			$plugin_capability,
			$this->plugin_name . '-vulnerability',
			array( $this, 'display_react_admin_page' )
		);

		add_submenu_page(
			$this->plugin_name,
			esc_html__( 'Settings', 'securwp' ),
			esc_html__( 'Settings', 'securwp' ),
			$plugin_capability,
			$this->plugin_name . '-settings',
			array( $this, 'display_react_admin_page' )
		);

		add_submenu_page(
			$this->plugin_name,
			esc_html__( 'Security Tools', 'securwp' ),
			esc_html__( 'Tools', 'securwp' ),
			$plugin_capability,
			$this->plugin_name . '-tools',
			array( $this, 'display_react_admin_page' )
		);

		add_submenu_page(
			$this->plugin_name,
			esc_html__( 'Audit Log', 'securwp' ),
			esc_html__( 'Audit Log', 'securwp' ),
			$plugin_capability,
			$this->plugin_name . '-audit-log',
			array( $this, 'display_react_admin_page' )
		);

		$license_ok = $cloud_has_creds && in_array( $license_status, array( 'active', 'valid' ), true );
		if ( ! $license_ok ) {
			add_submenu_page(
				$this->plugin_name,
				esc_html__( 'License & Plan', 'securwp' ),
				esc_html__( 'License & Plan', 'securwp' ),
				$plugin_capability,
				$this->plugin_name . '-settings&tab=license',
				array( $this, 'display_react_admin_page' )
			);
		}

		$license_plan = (string) ( $cloud_all['license_plan'] ?? 'free' );
		$is_free_plan = ( $license_plan === 'free' );
		if ( $wizard_done && $is_free_plan && current_user_can( $plugin_capability ) ) {
			global $submenu;

			$submenu[ $this->plugin_name ][] = array(
				'<span class="securwp-upgrade-nav-link"><span class="dashicons dashicons-star-filled securwp-upgrade-icon"></span>' . esc_html__( 'Upgrade to Pro', 'securwp' ) . '<span class="dashicons dashicons-external securwp-upgrade-ext"></span></span>',
				$plugin_capability,
				'https://securewp.net/pricing/',
				esc_html__( 'Upgrade to Pro', 'securwp' ),
			);
		}
	}

	/**
	 * Output inline CSS for the admin menu icon to support dark and light admin color schemes.
	 */
	public function admin_menu_icon_styles(): void {
		$dark_icon = esc_url( SECURWP_PLUGIN_URL . 'assets/images/icon.svg' );
		?>
		<style>
		.admin-color-light #adminmenu #toplevel_page_securwp .wp-menu-image {
			background-image: url('<?php echo esc_url( $dark_icon ); ?>') !important;
		}
		#adminmenu .securwp-upgrade-nav-link {
			color: #818cf8 !important;
			font-weight: 500;
		}
		#adminmenu .securwp-upgrade-icon {
			font-size: 14px !important;
			width: 14px !important;
			height: 14px !important;
			margin-right: 5px;
			position: relative;
			top: 3px;
			color: #818cf8 !important;
		}
		#adminmenu .securwp-upgrade-ext {
			font-size: 12px !important;
			width: 12px !important;
			height: 12px !important;
			margin-left: 4px;
			position: relative;
			top: 2px;
			opacity: 0.55;
			color: #818cf8 !important;
		}
		#adminmenu a:hover .securwp-upgrade-nav-link,
		#adminmenu a:hover .securwp-upgrade-icon,
		#adminmenu a:hover .securwp-upgrade-ext {
			color: #a5b4fc !important;
		}
		</style>
		<script>
		(function() {
			function makeUpgradeLinkExternal() {
				var links = document.querySelectorAll('#adminmenu .securwp-upgrade-nav-link');
				links.forEach(function(span) {
					var anchor = span.closest('a');
					if (anchor) {
						anchor.target = '_blank';
						anchor.rel = 'noopener noreferrer';
					}
				});
			}
			if (document.readyState === 'loading') {
				document.addEventListener('DOMContentLoaded', makeUpgradeLinkExternal);
			} else {
				makeUpgradeLinkExternal();
			}
		}());
		</script>
		<?php
	}

	/**
	 * Display the admin page container for the React application.
	 */
	public function display_react_admin_page(): void {
		$branding = \SecurWP\Includes\SettingsStore::get( \SecurWP\Includes\SettingsStore::COL_BRANDING );
		if ( class_exists( '\\SecurWP\\Modules\\WhiteLabel\\WhiteLabel' ) ) {
			$branding = \SecurWP\Modules\WhiteLabel\WhiteLabel::normalize_branding( $branding );
		}
		$name = ! empty( $branding['plugin_name'] ) ? esc_html( sanitize_text_field( (string) $branding['plugin_name'] ) ) : 'SecurWP';

		echo '<div class="wrap"><div id="securwp-admin-app">' .
			'<div style="display:flex;align-items:center;justify-content:center;min-height:50vh">' .
				'<div style="display:flex;flex-direction:column;align-items:center;gap:16px">' .
					'<svg style="width:32px;height:32px;color:#cbd5e1;animation:spin 1s linear infinite" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">' .
						'<circle style="opacity:0.25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>' .
						'<path style="opacity:0.75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>' .
					'</svg>' .
					'<div style="color:#64748b;font-size:14px;font-weight:500;font-family:system-ui,-apple-system,sans-serif">Loading ' . esc_html( $name ) . '...</div>' .
				'</div>' .
			'</div>' .
			'<style>@keyframes spin{to{transform:rotate(360deg)}}</style>' .
		'</div></div>';
	}

	/**
	 * Render the setup notice banner on all admin pages when setup is incomplete.
	 *
	 * Shown to plugin administrators on non-SecurWP pages until the site is
	 * connected and licensed, or the user dismisses it.
	 */
	public function render_setup_notice(): void {
		if ( ! Capabilities::can_manage_plugin() ) {
			return;
		}

		// Skip on our own plugin pages - they already surface the setup widget.
		$current_page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( str_starts_with( $current_page, 'securwp' ) ) {
			return;
		}

		// Reuse the same wizard_done logic as add_admin_menu() so this banner
		// respects the existing setup_dismissed flag from the dashboard widget.
		$cloud_all       = \SecurWP\Modules\Cloud\Service\CloudSettings::get_all();
		$cloud_has_creds = ! empty( $cloud_all['site_token'] ) && ! empty( $cloud_all['site_secret'] );
		$license_status  = (string) ( $cloud_all['license_status'] ?? '' );
		if ( $license_status === 'active' && ! $cloud_has_creds ) {
			$license_status = '';
		}
		$setup_dismissed = \SecurWP\Includes\SettingsStore::get_field(
			\SecurWP\Includes\SettingsStore::COL_STATE,
			'setup_dismissed',
			''
		) === '1';
		$wizard_done = $setup_dismissed || ( $cloud_has_creds && $license_status !== '' );
		if ( $wizard_done ) {
			return;
		}

		// Per-user dismissal so each admin can independently snooze the notice.
		if ( get_user_meta( get_current_user_id(), 'securwp_setup_notice_dismissed', true ) === '1' ) {
			return;
		}

		$branding = \SecurWP\Includes\SettingsStore::get( \SecurWP\Includes\SettingsStore::COL_BRANDING );
		if ( class_exists( '\\SecurWP\\Modules\\WhiteLabel\\WhiteLabel' ) ) {
			$branding = \SecurWP\Modules\WhiteLabel\WhiteLabel::normalize_branding( $branding );
		}
		$plugin_name = ! empty( $branding['plugin_name'] ) ? sanitize_text_field( (string) $branding['plugin_name'] ) : 'SecurWP';
		$setup_url   = esc_url( admin_url( 'admin.php?page=securwp' ) );
		$nonce       = wp_create_nonce( 'securwp_dismiss_notice' );
		$ajax_url    = esc_url( admin_url( 'admin-ajax.php' ) );
		?>
		<div id="securwp-setup-notice"
			style="display:flex;align-items:center;gap:16px;padding:14px 20px;background:#fff;border-left:4px solid #1e3a5f;box-shadow:0 1px 3px rgba(0,0,0,.08);margin:8px 20px 0 2px;border-radius:0 4px 4px 0">

			<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
				style="width:20px;height:20px;flex-shrink:0;color:#1e3a5f">
				<path fill-rule="evenodd" d="M12 1.5a5.25 5.25 0 0 0-5.25 5.25v3a3 3 0 0 0-3 3v6.75a3 3 0 0 0 3 3h10.5a3 3 0 0 0 3-3v-6.75a3 3 0 0 0-3-3v-3c0-2.9-2.35-5.25-5.25-5.25Zm3.75 8.25v-3a3.75 3.75 0 1 0-7.5 0v3h7.5Z" clip-rule="evenodd" />
			</svg>

			<div style="flex:1;min-width:0">
				<span style="font-weight:600;color:#111827;font-size:13px"><?php echo esc_html( $plugin_name ); ?> is not yet configured</span>
				<span style="color:#6b7280;font-size:13px;margin-left:8px">Complete the setup to activate firewall protection, security hardening, and malware scanning.</span>
			</div>

			<a href="<?php echo esc_url( $setup_url ); ?>"
				style="flex-shrink:0;display:inline-flex;align-items:center;padding:7px 16px;background:#1e3a5f;color:#fff;font-size:13px;font-weight:500;border-radius:4px;text-decoration:none;line-height:1.4"
				onmouseover="this.style.background='#162d4a'"
				onmouseout="this.style.background='#1e3a5f'">
				Complete Setup
			</a>

			<button id="securwp-dismiss-notice"
				aria-label="Dismiss"
				style="flex-shrink:0;background:none;border:none;cursor:pointer;color:#9ca3af;padding:4px;line-height:1;border-radius:3px"
				onmouseover="this.style.color='#374151'"
				onmouseout="this.style.color='#9ca3af'">
				<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" style="width:16px;height:16px">
					<path d="M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94 6.28 5.22Z" />
				</svg>
			</button>
		</div>
		<script>
		(function() {
			var btn = document.getElementById('securwp-dismiss-notice');
			if (!btn) { return; }
			btn.addEventListener('click', function() {
				var notice = document.getElementById('securwp-setup-notice');
				if (notice) { notice.style.display = 'none'; }
				var fd = new FormData();
				fd.append('action', 'securwp_dismiss_setup_notice');
				fd.append('nonce', '<?php echo esc_js( $nonce ); ?>');
				fetch('<?php echo esc_url( $ajax_url ); ?>', { method: 'POST', body: fd, credentials: 'same-origin' });
			});
		}());
		</script>
		<?php
	}

	/**
	 * AJAX handler: dismiss the setup notice for the current user.
	 */
	public function dismiss_setup_notice_ajax(): void {
		check_ajax_referer( 'securwp_dismiss_notice', 'nonce' );
		if ( ! Capabilities::can_manage_plugin() ) {
			wp_die( -1 );
		}
		update_user_meta( get_current_user_id(), 'securwp_setup_notice_dismissed', '1' );
		wp_send_json_success();
	}

	/**
	 * Get sanitized branding data for the React frontend.
	 *
	 * @return array<string,mixed>
	 */
	private function get_branding_data(): array {
		$branding = \SecurWP\Includes\SettingsStore::get( \SecurWP\Includes\SettingsStore::COL_BRANDING );
		if ( class_exists( '\\SecurWP\\Modules\\WhiteLabel\\WhiteLabel' ) ) {
			$branding = \SecurWP\Modules\WhiteLabel\WhiteLabel::normalize_branding( $branding );
		}
		if ( empty( $branding ) ) {
			return [];
		}

		return [
			'pluginName'      => sanitize_text_field( (string) ( $branding['plugin_name'] ?? '' ) ),
			'companyName'     => sanitize_text_field( (string) ( $branding['company_name'] ?? '' ) ),
			'logoUrl'         => esc_url( $branding['logo_url'] ?? '', [ 'https' ] ),
			'supportUrl'      => esc_url( $branding['support_url'] ?? '', [ 'https' ] ),
			'emailFromName'   => sanitize_text_field( (string) ( $branding['email_from_name'] ?? '' ) ),
			'adminFooterText' => sanitize_text_field( (string) ( $branding['admin_footer_text'] ?? '' ) ),
			'hideAttribution' => ! empty( $branding['hide_attribution'] ),
		];
	}

}
