<?php
/**
 * More WP Dude Plugins admin catalogue.
 *
 * Local, WordPress.org-safe cross-promotion. Copied from Dude Downloads / Dude Paywall
 * with Products page slug, asset constants, and text domain.
 *
 * @package WPDudeProducts
 */

declare(strict_types=1);

namespace WPDude\Products\Admin;

/**
 * Submenu page listing other WP Dude plugins from a bundled JSON catalogue.
 */
final class More_Plugins_Page {

	/**
	 * Admin page slug (under Menu::MENU_SLUG).
	 */
	public const PAGE_SLUG = 'dude-commerce-more-plugins';

	/**
	 * Relative path to bundled catalogue + images (from plugin root).
	 */
	private const ASSETS_DIR = 'assets/wp-dude-plugins';

	/**
	 * Style handle.
	 */
	private const STYLE_HANDLE = 'wpdude-dc-more-plugins';

	/**
	 * CSS relative path from plugin root.
	 */
	private const CSS_REL = 'assets/css/admin-more-plugins.css';

	/**
	 * Register hooks.
	 */
	public function register(): void {
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
	}

	/**
	 * Bind styles to the exact submenu page hook returned by add_submenu_page().
	 *
	 * More reliable than guessing the hook suffix (Freemius / parent menu differences).
	 *
	 * @param string $hook_suffix Hook from add_submenu_page().
	 */
	public static function bind_screen_hook( string $hook_suffix ): void {
		if ( '' === $hook_suffix ) {
			return;
		}

		add_action( "admin_print_styles-{$hook_suffix}", array( self::class, 'enqueue_styles' ) );
		add_action(
			'admin_enqueue_scripts',
			static function ( string $hook ) use ( $hook_suffix ): void {
				if ( $hook === $hook_suffix ) {
					self::enqueue_styles();
				}
			}
		);
	}

	/**
	 * Enqueue CSS only on this admin page.
	 *
	 * @param string $hook_suffix Current admin page hook.
	 */
	public function enqueue_assets( string $hook_suffix ): void {
		if ( ! $this->is_this_screen( $hook_suffix ) ) {
			return;
		}

		self::enqueue_styles();
	}

	/**
	 * Register the catalogue stylesheet.
	 */
	public static function enqueue_styles(): void {
		if ( wp_style_is( self::STYLE_HANDLE, 'enqueued' ) || wp_style_is( self::STYLE_HANDLE, 'done' ) ) {
			return;
		}

		$css_path = WPDUDE_DC_PATH . self::CSS_REL;
		$version  = is_readable( $css_path ) ? (string) filemtime( $css_path ) : WPDUDE_DC_VERSION;

		wp_enqueue_style(
			self::STYLE_HANDLE,
			plugins_url( self::CSS_REL, WPDUDE_DC_FILE ),
			array( 'dashicons' ),
			$version
		);
	}

	/**
	 * Whether the current admin request is this page.
	 *
	 * @param string $hook_suffix Current admin page hook.
	 */
	private function is_this_screen( string $hook_suffix ): bool {
		$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( (string) $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( self::PAGE_SLUG === $page ) {
			return true;
		}

		return false !== strpos( $hook_suffix, self::PAGE_SLUG );
	}

	/**
	 * Render the catalogue page.
	 */
	public static function render(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You do not have permission to access this page.', 'dude-commerce' ) );
		}

		$catalogue = self::load_catalogue();
		?>
		<div class="wrap wpdude-more-plugins">
			<h1><?php echo esc_html__( 'More from WP Dude', 'dude-commerce' ); ?></h1>
			<p class="wpdude-more-plugins__intro">
				<?php echo esc_html__( 'Simple WordPress plugins that do one job well.', 'dude-commerce' ); ?>
			</p>
			<p class="wpdude-more-plugins__subintro">
				<?php echo esc_html__( 'Explore other WP Dude plugins for selling content, products, events and more.', 'dude-commerce' ); ?>
			</p>

			<?php if ( null === $catalogue ) : ?>
				<div class="notice notice-warning inline wpdude-more-plugins__notice">
					<p><?php echo esc_html__( 'WP Dude plugin catalogue is currently unavailable.', 'dude-commerce' ); ?></p>
				</div>
			<?php else : ?>
				<ul class="wpdude-more-plugins__grid">
					<?php foreach ( $catalogue as $item ) : ?>
						<?php self::render_card( $item ); ?>
					<?php endforeach; ?>
				</ul>
			<?php endif; ?>
		</div>
		<?php
	}

	/**
	 * Render one plugin card.
	 *
	 * @param array{slug: string, name: string, description: string, display: string, image: string, url: string} $item Card data.
	 */
	private static function render_card( array $item ): void {
		$display    = $item['display'];
		$card_class = 'wpdude-more-plugins__card';
		if ( 'coming-soon' === $display ) {
			$card_class .= ' wpdude-more-plugins__card--coming-soon';
		}

		$image_url = self::resolve_image_url( $item['image'] );
		?>
		<li class="<?php echo esc_attr( $card_class ); ?>">
			<div class="wpdude-more-plugins__icon-wrap" aria-hidden="true">
				<?php if ( '' !== $image_url ) : ?>
					<img
						class="wpdude-more-plugins__icon"
						src="<?php echo esc_url( $image_url ); ?>"
						alt=""
						width="72"
						height="72"
						loading="lazy"
						decoding="async"
					/>
				<?php else : ?>
					<span class="wpdude-more-plugins__icon-fallback dashicons dashicons-admin-plugins"></span>
				<?php endif; ?>
			</div>
			<h2 class="wpdude-more-plugins__name"><?php echo esc_html( $item['name'] ); ?></h2>
			<p class="wpdude-more-plugins__desc"><?php echo esc_html( $item['description'] ); ?></p>
			<div class="wpdude-more-plugins__action">
				<?php if ( 'installed' === $display ) : ?>
					<span class="wpdude-more-plugins__status wpdude-more-plugins__status--installed">
						<?php echo esc_html__( 'Installed ✓', 'dude-commerce' ); ?>
					</span>
				<?php elseif ( 'available' === $display && '' !== $item['url'] ) : ?>
					<a
						class="button button-secondary"
						href="<?php echo esc_url( $item['url'] ); ?>"
						target="_blank"
						rel="noopener noreferrer"
					>
						<?php echo esc_html__( 'View on WordPress.org', 'dude-commerce' ); ?>
					</a>
				<?php else : ?>
					<span class="wpdude-more-plugins__status wpdude-more-plugins__status--coming-soon">
						<?php echo esc_html__( 'Coming Soon', 'dude-commerce' ); ?>
					</span>
				<?php endif; ?>
			</div>
		</li>
		<?php
	}

	/**
	 * Load and normalise catalogue entries. Returns null on failure.
	 *
	 * @return list<array{slug: string, name: string, description: string, display: string, image: string, url: string}>|null
	 */
	private static function load_catalogue(): ?array {
		$path = WPDUDE_DC_PATH . self::ASSETS_DIR . '/plugins.json';
		if ( ! is_readable( $path ) ) {
			return null;
		}

		$raw = file_get_contents( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- local bundled file.
		if ( false === $raw || '' === $raw ) {
			return null;
		}

		// UTF-8 BOM makes json_decode() return null.
		if ( str_starts_with( $raw, "\xEF\xBB\xBF" ) ) {
			$raw = substr( $raw, 3 );
		}

		$data = json_decode( $raw, true );
		if ( ! is_array( $data ) || ! isset( $data['plugins'] ) || ! is_array( $data['plugins'] ) ) {
			return null;
		}

		$installed_slugs = self::installed_wpdude_slugs();
		$out             = array();

		foreach ( $data['plugins'] as $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}

			$slug = isset( $row['slug'] ) ? sanitize_key( (string) $row['slug'] ) : '';
			$name = isset( $row['name'] ) ? sanitize_text_field( (string) $row['name'] ) : '';
			$desc = isset( $row['description'] ) ? sanitize_text_field( (string) $row['description'] ) : '';
			if ( '' === $slug || '' === $name ) {
				continue;
			}

			$status = isset( $row['status'] ) ? sanitize_key( (string) $row['status'] ) : '';
			if ( ! in_array( $status, array( 'available', 'coming-soon' ), true ) ) {
				$status = 'coming-soon';
			}

			$image = isset( $row['image'] ) ? sanitize_file_name( (string) $row['image'] ) : '';
			$url   = '';
			if ( isset( $row['url'] ) && is_string( $row['url'] ) && '' !== $row['url'] ) {
				$candidate = esc_url_raw( $row['url'] );
				if ( self::is_allowed_plugin_url( $candidate ) ) {
					$url = $candidate;
				}
			}

			$display = $status;
			if ( isset( $installed_slugs[ $slug ] ) ) {
				$display = 'installed';
			} elseif ( 'available' === $status && '' === $url ) {
				$display = 'coming-soon';
			}

			$out[] = array(
				'slug'        => $slug,
				'name'        => $name,
				'description' => $desc,
				'display'     => $display,
				'image'       => $image,
				'url'         => $url,
			);
		}

		return $out;
	}

	/**
	 * Map of installed WP Dude plugin slugs (directory basenames).
	 *
	 * @return array<string, true>
	 */
	private static function installed_wpdude_slugs(): array {
		if ( ! function_exists( 'get_plugins' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		$plugins = get_plugins();
		if ( ! is_array( $plugins ) ) {
			return array();
		}

		$found = array();
		foreach ( array_keys( $plugins ) as $basename ) {
			$basename = (string) $basename;
			$dir      = strtolower( strtok( $basename, '/' ) );
			if ( '' === $dir || 0 !== strpos( $dir, 'dude-' ) ) {
				continue;
			}
			// Normalise known folder aliases to catalogue slugs.
			if ( 'dude-downloads' === $dir ) {
				$dir = 'dude-download';
			}
			$found[ $dir ] = true;
		}

		return $found;
	}

	/**
	 * Only allow WordPress.org plugin directory links.
	 */
	private static function is_allowed_plugin_url( string $url ): bool {
		if ( '' === $url ) {
			return false;
		}

		$parts = wp_parse_url( $url );
		if ( ! is_array( $parts ) ) {
			return false;
		}

		$host = isset( $parts['host'] ) ? strtolower( (string) $parts['host'] ) : '';
		$path = isset( $parts['path'] ) ? (string) $parts['path'] : '';

		if ( ! in_array( $host, array( 'wordpress.org', 'www.wordpress.org' ), true ) ) {
			return false;
		}

		return (bool) preg_match( '#^/plugins/[a-z0-9-]+/?$#', $path );
	}

	/**
	 * Local image URL if the file exists under the assets directory.
	 */
	private static function resolve_image_url( string $filename ): string {
		if ( '' === $filename ) {
			return '';
		}

		// Reject path traversal / nested paths.
		if ( $filename !== basename( $filename ) ) {
			return '';
		}

		$full = WPDUDE_DC_PATH . self::ASSETS_DIR . '/' . $filename;
		if ( ! is_readable( $full ) ) {
			return '';
		}

		return plugins_url( self::ASSETS_DIR . '/' . $filename, WPDUDE_DC_FILE );
	}
}
