<?php
/**
 * Dashboard registration — admin menu page, asset enqueue, boot data,
 * body class. Owns the chassis that hosts the React SPA shipped by P2.
 *
 * SPEC §13.1. Pro never touches this file — it extends via the
 * `blocksify_dashboard_*` hooks fired here.
 *
 * Phase 1 note: the React bundle (`build/dashboard.{js,css}`) is shipped
 * by Phase 2. enqueue_assets() gracefully no-ops when those files don't
 * exist yet, so the admin page can render its mount node ahead of the JS
 * bundle. The Pro action `blocksify_dashboard_enqueue_assets` fires on
 * every dashboard page load regardless — Pro's enqueue chain is live
 * from day one (FOUNDATION §8.5).
 *
 * @package Blocksify
 */

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

/**
 * Dashboard chassis. Instance-less; methods are static.
 */
class Blocksify_Dashboard {

	const MENU_SLUG  = 'blocksify';
	const PAGE_HOOK  = 'toplevel_page_blocksify';
	const SCRIPT_HANDLE = 'blocksify-dashboard';
	const STYLE_HANDLE  = 'blocksify-dashboard';

	/**
	 * Blocksify brand mark — three-shape geometric logo. `currentColor`
	 * fill so the SVG tints with the menu text color (or the WP admin
	 * theme accent inside the dashboard header). `aria-hidden` because
	 * the adjacent text label carries the accessible name.
	 *
	 * Returned as a raw SVG string so the caller can either ship it as a
	 * `data:` URI (admin menu) or inline it in JSX
	 * (dashboard header — see boot_data()).
	 *
	 * @param int $size Rendered px (sets svg width + height).
	 * @return string
	 */
	public static function brand_svg( $size = 20 ) {
		$size = (int) $size;
		return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-55.03 -55.5 110.06 111" width="' . $size . '" height="' . $size . '" aria-hidden="true" focusable="false">'
			. '<path d="M 10.73,-48.13 L 39.27,-33.87 Q 50.00,-28.50 39.27,-23.13 L 10.73,-8.87 Q 0.00,-3.50 -10.73,-8.87 L -39.27,-23.13 Q -50.00,-28.50 -39.27,-33.87 L -10.73,-48.13 Q 0.00,-53.50 10.73,-48.13 Z" fill="currentColor"/>'
			. '<path d="M -42.30,-17.88 L -13.76,-3.62 Q -3.03,1.75 -3.03,13.75 L -3.03,39.75 Q -3.03,51.75 -13.76,46.38 L -42.30,32.12 Q -53.03,26.75 -53.03,14.75 L -53.03,-11.25 Q -53.03,-23.25 -42.30,-17.88 Z" fill="currentColor" fill-opacity="0.55"/>'
			. '<path d="M 42.30,-17.88 L 13.76,-3.62 Q 3.03,1.75 3.03,13.75 L 3.03,39.75 Q 3.03,51.75 13.76,46.38 L 42.30,32.12 Q 53.03,26.75 53.03,14.75 L 53.03,-11.25 Q 53.03,-23.25 42.30,-17.88 Z" fill="currentColor" fill-opacity="0.8"/>'
			. '</svg>';
	}

	/**
	 * Wire registration / enqueue hooks. Called once from blocksify.php.
	 */
	public static function boot() {
		add_action( 'admin_menu', array( __CLASS__, 'register_menu' ) );
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
		add_filter( 'admin_body_class', array( __CLASS__, 'body_class' ) );
	}

	/**
	 * Return the capability required to view and act on the dashboard.
	 * Used by `register_menu` AND by every REST controller's
	 * `permission_callback` so the gate stays consistent. SPEC §13.4.
	 *
	 * @return string
	 */
	public static function get_capability() {
		/**
		 * Capability required for the Blocksify dashboard surface.
		 * Multisite super-admins / role plugins override here. SPEC §11.2.
		 *
		 * @param string $cap Default `'manage_options'`.
		 */
		$cap = apply_filters( 'blocksify_dashboard_capability', 'manage_options' );
		return is_string( $cap ) && '' !== $cap ? $cap : 'manage_options';
	}

	/**
	 * Top-level admin menu entry. Sits just below WP's native Settings
	 * group (position 81 — between Settings at 80 and the second
	 * separator at 99); see admin-header notes for the menu-position
	 * table.
	 */
	public static function register_menu() {
		add_menu_page(
			__( 'Blocksify', 'blocksify' ),
			__( 'Blocksify', 'blocksify' ),
			self::get_capability(),
			self::MENU_SLUG,
			array( __CLASS__, 'render_page' ),
			'data:image/svg+xml;base64,' . base64_encode( self::brand_svg() ),
			81
		);
	}

	/**
	 * Enqueue the React bundle on the dashboard screen via kit's
	 * `AssetEnqueue` helper. Skips gracefully when the build output is
	 * missing (early-phase development), but ALWAYS fires the Pro action
	 * so Pro's enqueue chain is established at the canonical hook from
	 * day one. Kit reads `build/dashboard.asset.php` for `dependencies`
	 * + `version` automatically.
	 *
	 * @param string $hook Current admin page hook.
	 */
	public static function enqueue_assets( $hook ) {
		if ( self::PAGE_HOOK !== $hook ) {
			return;
		}

		if ( class_exists( '\\PressMaximum\\DashboardKit\\Admin\\AssetEnqueue' ) ) {
			\PressMaximum\DashboardKit\Admin\AssetEnqueue::enqueueOn(
				$hook,
				array(
					'handle'      => self::SCRIPT_HANDLE,
					'src_js'      => BLOCKSIFY_URL . 'build/dashboard.js',
					'src_css'     => BLOCKSIFY_URL . 'build/dashboard.css',
					'asset_php'   => BLOCKSIFY_PATH . 'build/dashboard.asset.php',
					'boot_global' => 'blocksifyDashboard',
					'boot_data'   => array( __CLASS__, 'boot_data' ),
					'text_domain' => 'blocksify',
				)
			);
		}

		/**
		 * Fires on the dashboard page after Free enqueues (or attempts to).
		 * Pro hooks its `pro-dashboard.{js,css}` enqueue here with
		 * `deps: ['blocksify-dashboard']` so the boot order is correct.
		 * SPEC §5.1.
		 *
		 * @param string $hook Current admin page hook.
		 */
		do_action( 'blocksify_dashboard_enqueue_assets', $hook );
	}

	/**
	 * Build the `window.blocksifyDashboard` boot payload. Shape locked by
	 * SPEC §5.2. Filterable so Pro can append fields (`proRuntimeConfig`,
	 * feature flags) without monkey-patching Free.
	 *
	 * @return array
	 */
	public static function boot_data() {
		$user = wp_get_current_user();

		$pro_active  = self::is_pro_active();
		$pro_version = $pro_active && defined( 'BLOCKSIFY_PRO_VERSION' ) ? BLOCKSIFY_PRO_VERSION : null;
		$pro_url     = defined( 'BLOCKSIFY_PRO_URL' ) ? BLOCKSIFY_PRO_URL : 'https://pressmaximum.com/blocksify';

		$boot = array(
			'name'           => 'Blocksify',
			'brandIcon'      => self::brand_svg( 28 ),
			// Ship Settings payload + schema in boot so the Settings tab
			// renders synchronously on first visit — no REST round-trip,
			// no loading spinner. Schema is PHP-static (~1KB) and the
			// settings option is one get_option() call, so the extra
			// payload weight is trivial vs the spinner round-trip it
			// replaces. Settings store seeds `saved` from `settings`;
			// SettingsPage seeds CACHED_SCHEMA from `settingsSchema`.
			'settings'       => Blocksify_Dashboard_Schema::get_settings(),
			'settingsSchema' => Blocksify_Dashboard_Schema::get_schema(),
			'pluginVersion'  => defined( 'BLOCKSIFY_VERSION' ) ? BLOCKSIFY_VERSION : '',
			'wpVersion'      => get_bloginfo( 'version' ),
			'phpVersion'     => PHP_VERSION,
			'capability'     => self::get_capability(),
			'proActive'      => $pro_active,
			'proVersion'     => $pro_version,
			'proUrl'         => $pro_url,
			'restRoot'       => esc_url_raw( rest_url() ),
			'restNamespaces' => array(
				'free' => '/blocksify/v1',
				'pro'  => $pro_active ? '/blocksify-pro/v1' : null,
			),
			'user'           => array(
				'id'          => (int) $user->ID,
				'displayName' => (string) $user->display_name,
			),
			'urls'           => array(
				'newPage'            => admin_url( 'post-new.php?post_type=page' ),
				'siteEditor'         => admin_url( 'site-editor.php' ),
				'blocksifySettings'  => admin_url( 'admin.php?page=' . self::MENU_SLUG ),
			),
		);

		/**
		 * Append fields to the dashboard boot payload before it's localized.
		 * Pro injects `proRuntimeConfig`, feature flags, etc. SPEC §11.2.
		 *
		 * @param array  $boot    Boot payload.
		 * @param string $context `'free'` here; Pro's own localize call passes `'pro'`.
		 */
		return (array) apply_filters( 'blocksify_dashboard_localize', $boot, 'free' );
	}

	/**
	 * Mark the dashboard page on `<body>` so admin CSS can scope. Matches
	 * the WP convention of `{slug}-page` class names.
	 *
	 * @param string $classes Space-delimited class string from core.
	 * @return string
	 */
	public static function body_class( $classes ) {
		$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
		if ( $screen && self::PAGE_HOOK === $screen->id ) {
			$classes .= ' blocksify-dashboard-page';
		}
		return $classes;
	}

	/**
	 * Render the admin page. The React SPA mounts to `#blocksify-dashboard`;
	 * the `.bsy-dashboard-root` class is the per-instance hook for any
	 * dashboard-scoped CSS shipped by Phase 2.
	 */
	public static function render_page() {
		// NOTE: no `.wrap` wrapper — that class triggers WP's default
		// `margin: 10px 20px 0 2px` from wp-admin/css/common.css which leaves
		// a gray strip around the dashboard. Blocksify dashboard is a custom
		// full-bleed SPA, not a standard WP admin page with page title +
		// action buttons, so the wrapper is unwanted. Mount node sits
		// directly inside `#wpbody-content`.
		echo '<div id="blocksify-dashboard" class="bsy-dashboard-root"></div>';
	}

	/**
	 * Pro detection — wraps the FOUNDATION §8.4 PHP convention so callers
	 * don't inline the constant + class_exists guard.
	 *
	 * @return bool
	 */
	private static function is_pro_active() {
		return defined( 'BLOCKSIFY_PRO_VERSION' ) && class_exists( 'Blocksify_Pro' );
	}
}
