<?php
/**
 * Registers secured admin AJAX actions for WP PowerSuite core.
 *
 * @package WP_PowerSuite\Core\Http
 * @since 1.0.0
 */

namespace WP_PowerSuite\Core\Http;

use WP_PowerSuite\Core\NetworkScope;
use WP_PowerSuite\Core\TemporarySessionGuard;

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

final class AjaxRouter {

	public const NONCE_ACTION = 'wp_powersuite_ajax';

	/**
	 * Register an admin-only AJAX handler with nonce + capability checks.
	 *
	 * @param string               $action     Action suffix (without wp_ajax_ prefix).
	 * @param callable             $handler    Callback after security gate passes.
	 * @param string|array<string> $capability Required capability or list (all required).
	 */
	public static function registerAdminAction( string $action, callable $handler, string|array|null $capability = null ): void {
		$required = $capability ?? NetworkScope::adminCapability();

		add_action(
			'wp_ajax_' . $action,
			static function () use ( $handler, $required, $action ): void {
				if ( ! self::verifyAdminRequest( $required ) ) {
					return;
				}

				if ( TemporarySessionGuard::isActive() && TemporarySessionGuard::blocksAjaxAction( $action ) ) {
					self::sendJsonError( TemporarySessionGuard::deniedPayload() );
					return;
				}

				// Discard buffered notices/warnings so they cannot corrupt JSON
				// when WP_DEBUG_DISPLAY is enabled in local environments.
				self::discardBufferedOutput();

				try {
					$handler();
				} catch ( \Throwable $exception ) {
					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
						error_log( 'WP PowerSuite AJAX handler exception: ' . $exception->getMessage() );
					}
					self::sendJsonError(
						[
							'message' => __( 'An error occurred. Please try again.', 'wp-powersuite' ),
							'code'    => 'handler_exception',
						]
					);
				}
			}
		);
	}

	/**
	 * Send a JSON success response after clearing accidental output buffers.
	 *
	 * @param mixed $data Payload for wp_send_json_success().
	 */
	public static function sendJsonSuccess( mixed $data = null ): void {
		self::discardBufferedOutput();
		wp_send_json_success( $data );
	}

	/**
	 * Send a JSON error response after clearing accidental output buffers.
	 *
	 * @param mixed $data Payload for wp_send_json_error().
	 */
	public static function sendJsonError( mixed $data = null ): void {
		self::discardBufferedOutput();
		wp_send_json_error( $data );
	}

	/**
	 * Clear open output buffers so admin-ajax responses stay valid JSON.
	 * Nesting depth is restored empty for anything WordPress/hosting may rely on.
	 */
	private static function discardBufferedOutput(): void {
		$level = ob_get_level();
		if ( $level < 1 ) {
			return;
		}

		for ( $i = 0; $i < $level; $i++ ) {
			ob_end_clean();
		}
		for ( $i = 0; $i < $level; $i++ ) {
			ob_start();
		}
	}

	/**
	 * Verify nonce and capabilities for admin AJAX requests.
	 *
	 * Sends JSON error responses on failure.
	 *
	 * @param string|array<string> $capability Required capability or list.
	 */
	public static function verifyAdminRequest( string|array|null $capability = null ): bool {
		$required = $capability ?? NetworkScope::adminCapability();
		if ( ! isset( $_POST['nonce'] ) || $_POST['nonce'] === '' ) {
			self::sendJsonError(
				[
					'message' => __( 'Security check failed. Please refresh the page and try again.', 'wp-powersuite' ),
					'code'    => 'nonce_failed',
				]
			);
			return false;
		}

		$nonce = sanitize_text_field( wp_unslash( $_POST['nonce'] ) );
		if ( wp_verify_nonce( $nonce, self::NONCE_ACTION ) === false ) {
			self::sendJsonError(
				[
					'message' => __( 'Security check failed. Please refresh the page and try again.', 'wp-powersuite' ),
					'code'    => 'nonce_failed',
				]
			);
			return false;
		}

		foreach ( (array) $required as $cap ) {
			if ( ! current_user_can( $cap ) ) {
				self::sendJsonError(
					[
						'message' => __( 'You do not have permission to perform this action.', 'wp-powersuite' ),
						'code'    => 'insufficient_permissions',
					]
				);
				return false;
			}
		}

		return true;
	}
}
