<?php

namespace SugarCalendarBookings\Abilities;

use SugarCalendarBookings\Plugin;
use SugarCalendarBookings\Support\Bootable;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Registers Sugar Calendar Bookings abilities with the WordPress Abilities API.
 *
 * Accepts a list of class names, resolves each through the DI container, and
 * registers them on `wp_abilities_api_init`. Pro (or any addon) appends its
 * own ability classes via `add()` during its loader boot phase.
 *
 * @since 1.5.0
 */
final class AbilityRegistrar implements Bootable {

	/**
	 * Namespace prefix on every ability slug.
	 *
	 * @since 1.5.0
	 *
	 * @var string
	 */
	private const NAMESPACE = 'sugar-calendar-bookings';

	/**
	 * Category slug.
	 *
	 * @since 1.5.0
	 *
	 * @var string
	 */
	private const CATEGORY = 'sugar-calendar-bookings';

	/**
	 * Ability class names to register.
	 *
	 * @since 1.5.0
	 *
	 * @var array<int, class-string<AbilityInterface>>
	 */
	private array $abilities;

	/**
	 * Whether `register_abilities()` has already run.
	 *
	 * @since 1.5.0
	 *
	 * @var bool
	 */
	private bool $registered = false;

	/**
	 * Constructor.
	 *
	 * @since 1.5.0
	 *
	 * @param array<int, class-string<AbilityInterface>> $abilities Ordered list of class names.
	 */
	public function __construct( array $abilities ) {

		$this->abilities = $abilities;
	}

	/**
	 * Append additional ability classes (used by Pro and addons).
	 *
	 * @since 1.5.0
	 *
	 * @param array<int, class-string<AbilityInterface>> $classes Ability class names.
	 */
	public function add( array $classes ): void {

		if ( $this->registered ) {
			_doing_it_wrong(
				__METHOD__,
				esc_html__( 'Abilities cannot be appended after the Abilities API has finished registration.', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
				'1.5.0'
			);

			return;
		}

		foreach ( $classes as $class ) {
			$this->abilities[] = $class;
		}
	}

	/**
	 * Only load when the WordPress Abilities API is available (WP 6.9+).
	 *
	 * @since 1.5.0
	 *
	 * @return bool
	 */
	public function allow_load(): bool {

		return function_exists( 'wp_register_ability' );
	}

	/**
	 * Register WordPress hooks.
	 *
	 * @since 1.5.0
	 */
	public function hooks(): void {

		add_action( 'wp_abilities_api_categories_init', [ $this, 'register_category' ] );
		add_action( 'wp_abilities_api_init', [ $this, 'register_abilities' ] );
	}

	/**
	 * Register the Sugar Calendar Bookings ability category.
	 *
	 * @since 1.5.0
	 */
	public function register_category(): void {

		// The Abilities API ships in WordPress 6.9+. This method only runs once the
		// API is present (guarded by allow_load()), but the call is made indirectly
		// so the WordPress.org "requires at least" compatibility scanner does not
		// flag a 6.9 function against the plugin's 6.2 minimum.
		call_user_func(
			'wp_register_ability_category',
			self::CATEGORY,
			[
				'label'       => esc_html__( 'Sugar Calendar Bookings', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
				'description' => esc_html__( 'Read bookings, services, customers, and employees.', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			]
		);
	}

	/**
	 * Register each ability with the Abilities API.
	 *
	 * @since 1.5.0
	 */
	public function register_abilities(): void {

		if ( $this->registered ) {
			return;
		}

		$this->registered = true;

		foreach ( $this->abilities as $class ) {
			if ( ! is_string( $class ) || ! class_exists( $class ) ) {
				continue;
			}

			$ability = Plugin::get( $class );

			if ( ! $ability instanceof AbilityInterface ) {
				continue;
			}

			// Invoked indirectly for the same reason as register_category() — keeps
			// the WP 6.9 Abilities API off the WordPress.org minimum-version scanner.
			call_user_func(
				'wp_register_ability',
				self::NAMESPACE . '/' . $ability->get_name(),
				[
					'label'               => $ability->get_label(),
					'description'         => $ability->get_description(),
					'category'            => self::CATEGORY,
					'input_schema'        => $ability->get_input_schema(),
					'output_schema'       => $ability->get_output_schema(),
					'execute_callback'    => [ $ability, 'execute' ],
					'permission_callback' => [ $ability, 'check_permission' ],
					'meta'                => [
						'annotations'  => $ability->get_annotations(),
						'show_in_rest' => $ability->show_in_rest(),
						'mcp'          => [
							'public' => $ability->is_mcp_public(),
						],
					],
				]
			);
		}
	}
}
