<?php
/**
 * Shared Papy 3D administration hub.
 *
 * Every compatible Papy3D plugin ships the same guarded class. The first active
 * compatible copy creates the shared menu, while the persistent registry lets
 * another compatible plugin take over when the original creator is removed.
 *
 * @package Papy3D_Admin_Hub
 */

declare(strict_types=1);

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

if ( ! class_exists( 'Papy3D_Admin_Hub', false ) ) {
	final class Papy3D_Admin_Hub {

		private const OPTION_NAME = 'papy3d_admin_hub_plugins';
		private const MENU_SLUG   = 'papy3d';
		private const CAPABILITY  = 'manage_options';
		private const HUB_VERSION = '1.2.3';

		/** @var array<string, array<string, mixed>> */
		private static array $runtime_plugins = array();

		/** @var string[] */
		private static array $page_hooks = array();

		private static bool $hooks_registered = false;

		/** Returns the stable top-level menu slug. */
		public static function menu_slug(): string {
			return self::MENU_SLUG;
		}

		/** Returns the shared implementation version. */
		public static function version(): string {
			return self::HUB_VERSION;
		}

		/**
		 * Registers one active plugin with translated runtime metadata.
		 *
		 * @param array<string, mixed> $plugin Plugin metadata.
		 */
		public static function register_plugin( array $plugin ): void {
			$plugin = self::normalize_plugin( $plugin, true );
			if ( array() === $plugin ) {
				return;
			}

			self::$runtime_plugins[ $plugin['slug'] ] = $plugin;
			self::register_hooks();
		}

		/**
		 * Persists only locale-neutral routing metadata.
		 *
		 * @param array<string, mixed> $plugin Plugin metadata.
		 */
		public static function persist_plugin( array $plugin ): void {
			$plugin = self::normalize_plugin( $plugin, false );
			if ( array() === $plugin ) {
				return;
			}

			$plugins                     = self::stored_plugins();
			$plugins[ $plugin['slug'] ] = self::persistent_plugin( $plugin );
			self::write_plugins( $plugins );
		}

		/** Removes only one plugin from the shared registry. */
		public static function unregister_plugin( string $slug ): void {
			$slug = sanitize_key( $slug );
			if ( '' === $slug ) {
				return;
			}

			unset( self::$runtime_plugins[ $slug ] );

			$plugins = self::stored_plugins();
			unset( $plugins[ $slug ] );
			self::write_plugins( $plugins );
		}

		/** Synchronizes neutral metadata from active compatible plugins. */
		public static function sync_runtime_plugins(): void {
			if ( array() === self::$runtime_plugins ) {
				return;
			}

			$plugins = self::stored_plugins();
			foreach ( self::$runtime_plugins as $slug => $plugin ) {
				$plugins[ $slug ] = self::persistent_plugin( $plugin );
			}
			self::write_plugins( $plugins );
		}

		/** Registers shared WordPress hooks once. */
		private static function register_hooks(): void {
			if ( self::$hooks_registered ) {
				return;
			}

			self::$hooks_registered = true;
			add_action( 'admin_init', array( __CLASS__, 'sync_runtime_plugins' ), 5 );
			add_action( 'admin_menu', array( __CLASS__, 'admin_menu' ), 20 );
			add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
		}

		/** Creates the stable Papy 3D menu and its neutral home page. */
		public static function admin_menu(): void {
			if ( array() === self::$runtime_plugins ) {
				return;
			}

			$labels = self::hub_labels();

			$parent_hook = add_menu_page(
				$labels['menu_title'],
				$labels['menu_title'],
				self::CAPABILITY,
				self::MENU_SLUG,
				array( __CLASS__, 'render_home' ),
				'dashicons-admin-plugins',
				58
			);

			$home_hook = add_submenu_page(
				self::MENU_SLUG,
				$labels['home_page_title'],
				$labels['home_menu_title'],
				self::CAPABILITY,
				self::MENU_SLUG,
				array( __CLASS__, 'render_home' )
			);

			self::$page_hooks = array_values(
				array_unique(
					array_filter(
						array( $parent_hook, $home_hook ),
						static function ( mixed $hook ): bool {
							return is_string( $hook ) && '' !== $hook;
						}
					)
				)
			);
		}

		/** Loads the readable inline stylesheet only on the hub home page. */
		public static function enqueue_assets( string $hook_suffix ): void {
			if ( ! in_array( $hook_suffix, self::$page_hooks, true ) ) {
				return;
			}

			wp_register_style( 'papy3d-admin-hub', false, array(), self::HUB_VERSION );
			wp_enqueue_style( 'papy3d-admin-hub' );
			wp_add_inline_style( 'papy3d-admin-hub', self::stylesheet() );
		}

		/** Renders the shared home page. */
		public static function render_home(): void {
			$labels = self::hub_labels();

			if ( ! current_user_can( self::CAPABILITY ) ) {
				wp_die( esc_html( $labels['permission_denied'] ) );
			}

			$plugins = self::registered_plugins();
			uasort(
				$plugins,
				static function ( array $left, array $right ): int {
					return strnatcasecmp(
						(string) ( $left['name'] ?? '' ),
						(string) ( $right['name'] ?? '' )
					);
				}
			);

			$count       = count( $plugins );
			$count_label = self::count_label( $labels, $count );
			?>
			<div class="wrap papy3d-hub-wrap">
				<div class="papy3d-hub-header">
					<div>
						<h1><?php echo esc_html( $labels['menu_title'] ); ?></h1>
						<p><?php echo esc_html( $labels['home_description'] ); ?></p>
					</div>
					<span class="papy3d-hub-count"><?php echo esc_html( $count_label ); ?></span>
				</div>

				<?php if ( array() === $plugins ) : ?>
					<div class="notice notice-info inline"><p><?php echo esc_html( $labels['empty_message'] ); ?></p></div>
				<?php else : ?>
					<div class="papy3d-hub-grid">
						<?php foreach ( $plugins as $plugin ) : ?>
							<?php
							$is_active    = self::is_plugin_active( $plugin['plugin_file'] );
							$page_url     = self::plugin_page_url( $plugin );
							$version_text = '' !== $plugin['version']
								? str_replace( '%s', $plugin['version'], $labels['version_format'] )
								: '';
							?>
							<article class="papy3d-hub-card">
								<div class="papy3d-hub-card-head">
									<?php if ( '' !== $plugin['icon_url'] ) : ?>
										<img src="<?php echo esc_url( $plugin['icon_url'] ); ?>" alt="" width="64" height="64" />
									<?php else : ?>
										<span class="dashicons dashicons-admin-plugins papy3d-hub-fallback-icon" aria-hidden="true"></span>
									<?php endif; ?>
									<div>
										<h2><?php echo esc_html( $plugin['name'] ); ?></h2>
										<?php if ( '' !== $version_text ) : ?>
											<p class="papy3d-hub-version"><?php echo esc_html( $version_text ); ?></p>
										<?php endif; ?>
									</div>
									<span class="papy3d-hub-status <?php echo esc_attr( $is_active ? 'is-active' : 'is-inactive' ); ?>">
										<?php echo esc_html( $is_active ? $labels['active'] : $labels['inactive'] ); ?>
									</span>
								</div>
								<?php if ( '' !== $plugin['description'] ) : ?>
									<p class="papy3d-hub-description"><?php echo esc_html( $plugin['description'] ); ?></p>
								<?php endif; ?>
								<div class="papy3d-hub-card-actions">
									<?php if ( $is_active && '' !== $page_url ) : ?>
										<a class="button button-primary" href="<?php echo esc_url( $page_url ); ?>"><?php echo esc_html( $labels['open'] ); ?></a>
									<?php else : ?>
										<span class="description"><?php echo esc_html( $labels['inactive_help'] ); ?></span>
									<?php endif; ?>
								</div>
							</article>
						<?php endforeach; ?>
					</div>
				<?php endif; ?>
			</div>
			<?php
		}

		/** Returns labels supplied at runtime by an active plugin. */
		private static function hub_labels(): array {
			foreach ( self::$runtime_plugins as $plugin ) {
				if ( isset( $plugin['labels'] ) && is_array( $plugin['labels'] ) && array() !== $plugin['labels'] ) {
					return $plugin['labels'];
				}
			}

			return self::default_labels();
		}

		/**
		 * Provides locale-neutral emergency labels.
		 *
		 * Compatible plugins supply translated labels at runtime. These English
		 * fallbacks keep the shared component independent from every plugin text domain.
		 */
		private static function default_labels(): array {
			return array(
				'menu_title'        => 'Papy 3D',
				'home_page_title'   => 'Papy 3D Home',
				'home_menu_title'   => 'Home',
				'home_description'  => 'Manage the Papy3D plugins installed on this site.',
				'count_singular'    => '%d plugin',
				'count_plural'      => '%d plugins',
				'empty_message'     => 'No Papy3D plugin is currently registered.',
				'version_format'    => 'Version %s',
				'active'            => 'Active',
				'inactive'          => 'Inactive',
				'open'              => 'Open',
				'inactive_help'     => 'Activate the plugin to access its settings.',
				'permission_denied' => 'Sorry, you are not allowed to access this page.',
			);
		}

		/** Creates a locale-aware count label when a plugin supplied one. */
		private static function count_label( array $labels, int $count ): string {
			if ( isset( $labels['count_nooped'] ) && is_array( $labels['count_nooped'] ) ) {
				$format = translate_nooped_plural( $labels['count_nooped'], $count );
			} else {
				$format = 1 === $count ? $labels['count_singular'] : $labels['count_plural'];
			}

			return sprintf( $format, $count );
		}

		/** Returns persisted entries merged with active runtime metadata. */
		private static function registered_plugins(): array {
			$plugins = self::stored_plugins();
			foreach ( self::$runtime_plugins as $slug => $plugin ) {
				$plugins[ $slug ] = $plugin;
			}

			foreach ( $plugins as $slug => $plugin ) {
				$plugins[ $slug ] = self::hydrate_plugin( $plugin );
			}

			return $plugins;
		}

		/** Reads and migrates the persistent registry. */
		private static function stored_plugins(): array {
			$stored  = get_option( self::OPTION_NAME, array() );
			$plugins = array();
			if ( ! is_array( $stored ) ) {
				return $plugins;
			}

			foreach ( $stored as $plugin ) {
				if ( ! is_array( $plugin ) ) {
					continue;
				}

				$plugin = self::normalize_plugin( $plugin, false );
				if ( array() !== $plugin ) {
					$plugins[ $plugin['slug'] ] = self::persistent_plugin( $plugin );
				}
			}

			return $plugins;
		}

		/** Stores the registry without autoloading it on public requests. */
		private static function write_plugins( array $plugins ): void {
			if ( array() === $plugins ) {
				delete_option( self::OPTION_NAME );
				return;
			}

			ksort( $plugins, SORT_NATURAL | SORT_FLAG_CASE );
			if ( false === get_option( self::OPTION_NAME, false ) ) {
				add_option( self::OPTION_NAME, $plugins, '', false );
				return;
			}

			update_option( self::OPTION_NAME, $plugins, false );
		}

		/** Normalizes runtime or persistent public metadata. */
		private static function normalize_plugin( array $plugin, bool $runtime ): array {
			$slug = isset( $plugin['slug'] ) && is_scalar( $plugin['slug'] ) ? sanitize_key( (string) $plugin['slug'] ) : '';
			if ( '' === $slug ) {
				return array();
			}

			$labels = $runtime && isset( $plugin['labels'] ) && is_array( $plugin['labels'] )
				? self::normalize_labels( $plugin['labels'] )
				: array();

			return array(
				'slug'        => $slug,
				'name'        => $runtime && isset( $plugin['name'] ) && is_scalar( $plugin['name'] ) ? sanitize_text_field( (string) $plugin['name'] ) : '',
				'description' => $runtime && isset( $plugin['description'] ) && is_scalar( $plugin['description'] ) ? sanitize_text_field( (string) $plugin['description'] ) : '',
				'version'     => $runtime && isset( $plugin['version'] ) && is_scalar( $plugin['version'] ) ? sanitize_text_field( (string) $plugin['version'] ) : '',
				'plugin_file' => isset( $plugin['plugin_file'] ) && is_scalar( $plugin['plugin_file'] ) ? sanitize_text_field( (string) $plugin['plugin_file'] ) : '',
				'page_slug'   => isset( $plugin['page_slug'] ) && is_scalar( $plugin['page_slug'] ) ? sanitize_key( (string) $plugin['page_slug'] ) : '',
				'icon_url'    => isset( $plugin['icon_url'] ) && is_scalar( $plugin['icon_url'] ) ? esc_url_raw( (string) $plugin['icon_url'] ) : '',
				'labels'      => $labels,
			);
		}

		/** Removes translated fields before writing the persistent registry. */
		private static function persistent_plugin( array $plugin ): array {
			return array(
				'slug'        => $plugin['slug'],
				'plugin_file' => $plugin['plugin_file'],
				'page_slug'   => $plugin['page_slug'],
				'icon_url'    => $plugin['icon_url'],
			);
		}

		/** Reads inactive plugin headers at render time instead of persisting translations. */
		private static function hydrate_plugin( array $plugin ): array {
			$plugin = array_merge(
				array(
					'slug'        => '',
					'name'        => '',
					'description' => '',
					'version'     => '',
					'plugin_file' => '',
					'page_slug'   => '',
					'icon_url'    => '',
					'labels'      => array(),
				),
				$plugin
			);

			$plugin['slug']        = is_scalar( $plugin['slug'] ) ? sanitize_key( (string) $plugin['slug'] ) : '';
			$plugin['name']        = is_scalar( $plugin['name'] ) ? sanitize_text_field( (string) $plugin['name'] ) : '';
			$plugin['description'] = is_scalar( $plugin['description'] ) ? sanitize_text_field( (string) $plugin['description'] ) : '';
			$plugin['version']     = is_scalar( $plugin['version'] ) ? sanitize_text_field( (string) $plugin['version'] ) : '';
			$plugin['plugin_file'] = is_scalar( $plugin['plugin_file'] ) ? sanitize_text_field( (string) $plugin['plugin_file'] ) : '';
			$plugin['page_slug']   = is_scalar( $plugin['page_slug'] ) ? sanitize_key( (string) $plugin['page_slug'] ) : '';
			$plugin['icon_url']    = is_scalar( $plugin['icon_url'] ) ? esc_url_raw( (string) $plugin['icon_url'] ) : '';
			$plugin['labels']      = is_array( $plugin['labels'] ) ? $plugin['labels'] : array();

			if ( '' !== $plugin['name'] ) {
				return $plugin;
			}

			$plugin['name'] = '' !== $plugin['slug'] ? $plugin['slug'] : $plugin['plugin_file'];
			if ( '' === $plugin['plugin_file'] || 0 !== validate_file( $plugin['plugin_file'] ) ) {
				return $plugin;
			}

			$absolute_file = wp_normalize_path( WP_PLUGIN_DIR . '/' . $plugin['plugin_file'] );
			$plugin_root   = trailingslashit( wp_normalize_path( WP_PLUGIN_DIR ) );
			if ( 0 !== strpos( $absolute_file, $plugin_root ) || ! is_readable( $absolute_file ) ) {
				return $plugin;
			}

			require_once ABSPATH . 'wp-admin/includes/plugin.php';
			$data = get_plugin_data( $absolute_file, false, true );
			if ( ! is_array( $data ) ) {
				return $plugin;
			}

			$plugin['name']        = ! empty( $data['Name'] ) ? sanitize_text_field( $data['Name'] ) : $plugin['slug'];
			$plugin['description'] = ! empty( $data['Description'] ) ? wp_strip_all_tags( $data['Description'] ) : '';
			$plugin['version']     = ! empty( $data['Version'] ) ? sanitize_text_field( $data['Version'] ) : '';

			return $plugin;
		}

		/** Normalizes labels and preserves a nooped plural translation structure. */
		private static function normalize_labels( array $labels ): array {
			$normalized = self::default_labels();
			$string_keys = array(
				'menu_title',
				'home_page_title',
				'home_menu_title',
				'home_description',
				'empty_message',
				'version_format',
				'active',
				'inactive',
				'open',
				'inactive_help',
				'permission_denied',
			);

			foreach ( $string_keys as $key ) {
				if ( isset( $labels[ $key ] ) && is_scalar( $labels[ $key ] ) ) {
					$value = sanitize_text_field( (string) $labels[ $key ] );
					if ( '' !== $value ) {
						$normalized[ $key ] = $value;
					}
				}
			}

			if ( isset( $labels['count_nooped'] ) && is_array( $labels['count_nooped'] ) ) {
				$candidate = $labels['count_nooped'];
				if (
					isset( $candidate['singular'], $candidate['plural'], $candidate['domain'] )
					&& is_string( $candidate['singular'] )
					&& is_string( $candidate['plural'] )
					&& is_string( $candidate['domain'] )
					&& false !== strpos( $candidate['singular'], '%d' )
					&& false !== strpos( $candidate['plural'], '%d' )
				) {
					$normalized['count_nooped'] = $candidate;
				}
			} elseif ( isset( $labels['count_singular'], $labels['count_plural'] ) ) {
				$singular = is_scalar( $labels['count_singular'] ) ? sanitize_text_field( (string) $labels['count_singular'] ) : '';
				$plural   = is_scalar( $labels['count_plural'] ) ? sanitize_text_field( (string) $labels['count_plural'] ) : '';
				if ( false !== strpos( $singular, '%d' ) && false !== strpos( $plural, '%d' ) ) {
					$normalized['count_singular'] = $singular;
					$normalized['count_plural']   = $plural;
				}
			}

			if ( false === strpos( $normalized['version_format'], '%s' ) ) {
				$normalized['version_format'] = 'Version %s';
			}

			return $normalized;
		}

		/** Determines whether a registered plugin is active. */
		private static function is_plugin_active( string $plugin_file ): bool {
			if ( '' === $plugin_file ) {
				return false;
			}

			if ( ! function_exists( 'is_plugin_active' ) || ! function_exists( 'is_plugin_active_for_network' ) ) {
				require_once ABSPATH . 'wp-admin/includes/plugin.php';
			}

			return is_plugin_active( $plugin_file ) || is_plugin_active_for_network( $plugin_file );
		}

		/** Builds the administration URL declared by one plugin. */
		private static function plugin_page_url( array $plugin ): string {
			if ( '' === $plugin['page_slug'] ) {
				return '';
			}

			return add_query_arg( 'page', $plugin['page_slug'], admin_url( 'admin.php' ) );
		}

		/** Returns readable, WPCS-formatted shared styling. */
		private static function stylesheet(): string {
			return <<<'CSS'
.papy3d-hub-wrap {
	max-width: 1180px;
}

.papy3d-hub-header {
	align-items: center;
	display: flex;
	gap: 20px;
	justify-content: space-between;
	margin: 24px 0;
}

.papy3d-hub-header h1 {
	font-size: 30px;
	margin: 0 0 6px;
}

.papy3d-hub-header p {
	color: #50575e;
	font-size: 15px;
	margin: 0;
}

.papy3d-hub-count {
	align-items: center;
	background: #fff;
	border: 1px solid #c3c4c7;
	border-radius: 999px;
	display: inline-flex;
	font-weight: 600;
	min-height: 32px;
	padding: 0 14px;
}

.papy3d-hub-grid {
	display: grid;
	gap: 18px;
	grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}

.papy3d-hub-card {
	background: #fff;
	border: 1px solid #c3c4c7;
	border-radius: 10px;
	box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
	display: flex;
	flex-direction: column;
	min-height: 210px;
	padding: 22px;
}

.papy3d-hub-card-head {
	align-items: center;
	display: grid;
	gap: 16px;
	grid-template-columns: 64px 1fr auto;
}

.papy3d-hub-card-head img {
	border-radius: 12px;
	display: block;
}

.papy3d-hub-fallback-icon {
	align-items: center;
	background: #f0f0f1;
	border-radius: 12px;
	display: flex;
	font-size: 34px;
	height: 64px;
	justify-content: center;
	width: 64px;
}

.papy3d-hub-card h2 {
	font-size: 18px;
	margin: 0 0 4px;
}

.papy3d-hub-version {
	color: #646970;
	margin: 0;
}

.papy3d-hub-status {
	border-radius: 999px;
	font-size: 12px;
	font-weight: 700;
	padding: 4px 9px;
}

.papy3d-hub-status.is-active {
	background: #edfaef;
	color: #0a6b1f;
}

.papy3d-hub-status.is-inactive {
	background: #f0f0f1;
	color: #50575e;
}

.papy3d-hub-description {
	color: #3c434a;
	flex: 1;
	line-height: 1.55;
	margin: 18px 0;
}

.papy3d-hub-card-actions {
	align-items: center;
	display: flex;
	min-height: 32px;
}

@media (max-width: 600px) {
	.papy3d-hub-header {
		align-items: flex-start;
		flex-direction: column;
	}

	.papy3d-hub-grid {
		grid-template-columns: 1fr;
	}

	.papy3d-hub-card-head {
		grid-template-columns: 56px 1fr;
	}

	.papy3d-hub-card-head img,
	.papy3d-hub-fallback-icon {
		height: 56px;
		width: 56px;
	}

	.papy3d-hub-status {
		grid-column: 2;
		justify-self: start;
	}
}
CSS;
		}
	}
}
