<?php
/**
 * Social Admin Page class.
 *
 * @package automattic/jetpack-publicize
 */

namespace Automattic\Jetpack\Publicize;

use Automattic\Jetpack\Admin_UI\Admin_Menu;
use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Current_Plan;
use Automattic\Jetpack\Publicize\Publicize_Utils as Utils;
use Automattic\Jetpack\Status\Host;

/**
 * The class to handle the Social Admin Page.
 */
class Social_Admin_Page {

	/**
	 * Nonce action used when refreshing plan data.
	 */
	public const REFRESH_PLAN_NONCE_ACTION = 'jetpack_social_refresh_plan_data';

	/**
	 * The instance of the class.
	 *
	 * @var Social_Admin_Page
	 */
	private static $instance;

	/**
	 * Initialize the class.
	 *
	 * @return Social_Admin_Page
	 */
	public static function init() {
		if ( ! isset( self::$instance ) ) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * The constructor.
	 */
	private function __construct() {
		// Load the wp-build dashboard at admin_menu priority 1 so its render
		// function is defined before `add_menu` (priority 10) registers the page.
		add_action( 'admin_menu', array( __CLASS__, 'maybe_load_wp_build' ), 1 );
		add_action( 'admin_menu', array( $this, 'add_menu' ) );
	}

	/**
	 * Load the wp-build dashboard on the Social admin request.
	 *
	 * Hooked to `admin_menu` priority 1 so the wp-build render function and
	 * enqueue hook are in place before `add_menu()` runs at the default priority.
	 *
	 * @return void
	 */
	public static function maybe_load_wp_build() {
		if ( ! self::is_social_admin_request() ) {
			return;
		}

		self::load_wp_build();

		// wp-build registers standalone modules (e.g. the init module) on
		// wp_default_scripts, which has already fired by admin_menu. Register them
		// directly so the init module makes it into the import map.
		if ( function_exists( 'jetpack_social_register_script_modules' ) ) {
			jetpack_social_register_script_modules(); // @phan-suppress-current-line PhanUndeclaredFunction -- Checked with function_exists(); defined in the generated build/modules.php, which Phan excludes.
		}

		add_action( 'current_screen', array( __CLASS__, 'alias_screen_id_for_wp_build' ) );
	}

	/**
	 * Add the admin menu.
	 */
	public function add_menu() {

		// Remove the old Social menu item, if it exists.
		Admin_Menu::remove_menu( 'jetpack-social' );

		// If this isn't an admin (or someone with the capability to change the module status )
		// and Publicize is inactive, then don't render the admin page.
		if ( ! current_user_can( 'manage_options' ) && ! Utils::is_publicize_active() ) {
			return;
		}

		// We don't need Jetpack connection on WP.com.
		$needs_site_connection = ! ( new Host() )->is_wpcom_platform() && ! ( new Connection_Manager() )->is_connected();

		/**
		 * If the Jetpack Social plugin is not active,
		 * we want to hide the menu if the site is not connected.
		 */
		if ( ! defined( 'JETPACK_SOCIAL_PLUGIN_DIR' ) && $needs_site_connection ) {
			return;
		}

		$page_suffix = Admin_Menu::add_menu(
			/** "Jetpack Social" is a product name, do not translate. */
			'Jetpack Social',
			'Social',
			'publish_posts',
			'jetpack-social',
			array( $this, 'render' ),
			4
		);

		add_action( 'load-' . $page_suffix, array( $this, 'admin_init' ) );
	}

	/**
	 * Initialize the admin resources.
	 */
	public function admin_init() {
		// Refresh data if coming from purchase to ensure it is up to date
		// without making API calls on every admin page load.
		if ( isset( $_GET['refresh_plan_data'] ) ) {
			check_admin_referer( self::REFRESH_PLAN_NONCE_ACTION );
			if ( apply_filters( 'jetpack_social_should_refresh_plan_data', true ) ) {
				Current_Plan::refresh_from_wpcom();
			}
		}

		/**
		 * Use priority 20 to ensure that we can dequeue the old Social assets.
		 */
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ), 20 );

		// Initialize the media library for the social image generator.
		wp_enqueue_media();
	}

	/**
	 * Render the admin page by delegating to the wp-build dashboard.
	 *
	 * `maybe_load_wp_build()` defines the render function before this fires;
	 * render nothing on an unbuilt dev checkout rather than fatal.
	 */
	public function render() {
		if ( function_exists( 'jetpack_social_jetpack_social_dashboard_wp_admin_render_page' ) ) {
			// Generated by wp-build (build/pages/jetpack-social-dashboard/page-wp-admin.php), so phan can't see it.
			// @phan-suppress-next-line PhanUndeclaredFunction
			jetpack_social_jetpack_social_dashboard_wp_admin_render_page();
		}
	}

	/**
	 * Enqueue admin scripts and styles.
	 */
	public function enqueue_admin_scripts() {
		/*
		 * wp-build owns its own enqueue pipeline. The chassis reads connection
		 * state via `useConnection()`, which has no REST resolver, so hydrate it
		 * inline onto the prerequisites script that loads before the chassis module.
		 */
		if ( wp_script_is( 'jetpack-social-dashboard-wp-admin-prerequisites', 'registered' ) ) {
			Connection_Initial_State::render_script( 'jetpack-social-dashboard-wp-admin-prerequisites' );
		}

		// The i18n loader is registered on every admin page by jetpack-assets but
		// only enqueued when depended on; the esbuild bundles don't pull it in.
		if ( wp_script_is( 'wp-jp-i18n-loader', 'registered' ) ) {
			wp_enqueue_script( 'wp-jp-i18n-loader' );
		}
	}

	/**
	 * Load the wp-build entry file and register its polyfills.
	 *
	 * Only called on `?page=jetpack-social` admin requests. Keeps wp-build off
	 * every other request.
	 *
	 * @return void
	 */
	private static function load_wp_build() {
		$build_index = dirname( __DIR__ ) . '/build/build.php';

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

		require_once $build_index;

		// The wp-build dashboard (unlike the Social bundles) uses the full polyfill set:
		// the @wordpress/boot|route|a11y modules, wp-notices, wp-views, etc.
		if ( ! class_exists( '\Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills' ) ) {
			return;
		}

		\Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills::register(
			'jetpack-social',
			array_merge(
				\Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills::SCRIPT_HANDLES,
				\Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills::MODULE_IDS
			)
		);
	}

	/**
	 * Alias the current screen ID to satisfy wp-build's auto-generated enqueue check.
	 *
	 * The wp-build `<page>-wp-admin` enqueue callback fires only when the screen ID
	 * matches the wp-build page slug (`jetpack-social-dashboard`). Our wp-admin menu
	 * slug stays `jetpack-social`, so we mutate the screen object in place to make
	 * the check pass without changing the user-facing URL.
	 *
	 * Hooked only when we're on the Social admin page, so this never affects any
	 * other request.
	 *
	 * @param \WP_Screen|null $screen The current screen object (passed by WP).
	 * @return void
	 */
	public static function alias_screen_id_for_wp_build( $screen ) {
		if ( ! is_object( $screen ) ) {
			return;
		}

		$screen->id = 'jetpack-social-dashboard';
	}

	/**
	 * Returns true when the current request targets the Social admin page.
	 *
	 * Used to scope wp-build loading to the one page that needs it. The
	 * `$_GET['page']` value is populated by wp-admin/admin.php before any of
	 * our hooks fire, so this check is reliable from the constructor onwards.
	 *
	 * @return bool
	 */
	private static function is_social_admin_request() {
		if ( ! is_admin() || ! isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			return false;
		}

		return sanitize_text_field( wp_unslash( $_GET['page'] ) ) === 'jetpack-social'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
	}
}
