<?php
/**
 * WPFAQBlock Loader
 *
 * Registers and runs all WordPress actions and filters for the plugin.
 * Based on the standard WordPress plugin boilerplate loader pattern.
 *
 * @package WPFAQBlock
 * @since   2.0.0
 */

// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class WPFAQBlock_Loader
 *
 * Maintains a list of all hooks (actions and filters) that are registered
 * with WordPress and executes them on 'run()'.
 */
class WPFAQBlock_Loader {

	/**
	 * Registered actions.
	 *
	 * @since 2.0.0
	 * @var   array<int, array{hook: string, component: object|null, callback: string, priority: int, args: int}>
	 */
	private array $actions = array();

	/**
	 * Registered filters.
	 *
	 * @since 2.0.0
	 * @var   array<int, array{hook: string, component: object|null, callback: string, priority: int, args: int}>
	 */
	private array $filters = array();

	// ──────────────────────────────────────────────
	// Public API
	// ──────────────────────────────────────────────

	/**
	 * Queue an action hook.
	 *
	 * @since 2.0.0
	 *
	 * @param string      $hook      The WordPress action hook name.
	 * @param object|null $component The object containing the callback method, or null for plain functions.
	 * @param string      $callback  The name of the method or function.
	 * @param int         $priority  Hook priority. Default 10.
	 * @param int         $args      Number of arguments passed to the callback. Default 1.
	 * @return void
	 */
	public function add_action(
		string $hook,
		?object $component,
		string $callback,
		int $priority = 10,
		int $args = 1
	): void {
		$this->actions = $this->add( $this->actions, $hook, $component, $callback, $priority, $args );
	}

	/**
	 * Queue a filter hook.
	 *
	 * @since 2.0.0
	 *
	 * @param string      $hook      The WordPress filter hook name.
	 * @param object|null $component The object containing the callback method, or null for plain functions.
	 * @param string      $callback  The name of the method or function.
	 * @param int         $priority  Hook priority. Default 10.
	 * @param int         $args      Number of arguments passed to the callback. Default 1.
	 * @return void
	 */
	public function add_filter(
		string $hook,
		?object $component,
		string $callback,
		int $priority = 10,
		int $args = 1
	): void {
		$this->filters = $this->add( $this->filters, $hook, $component, $callback, $priority, $args );
	}

	/**
	 * Register all queued hooks with WordPress.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function run(): void {
		foreach ( $this->filters as $hook ) {
			add_filter(
				$hook['hook'],
				$this->resolve_callable( $hook['component'], $hook['callback'] ),
				$hook['priority'],
				$hook['args']
			);
		}

		foreach ( $this->actions as $hook ) {
			add_action(
				$hook['hook'],
				$this->resolve_callable( $hook['component'], $hook['callback'] ),
				$hook['priority'],
				$hook['args']
			);
		}
	}

	// ──────────────────────────────────────────────
	// Private helpers
	// ──────────────────────────────────────────────

	/**
	 * Append a hook definition to a hooks array.
	 *
	 * @since 2.0.0
	 *
	 * @param array<int, array>  $hooks     Existing hooks array.
	 * @param string             $hook      Hook name.
	 * @param object|null        $component Owning object or null.
	 * @param string             $callback  Method / function name.
	 * @param int                $priority  Priority.
	 * @param int                $args      Accepted argument count.
	 * @return array<int, array>
	 */
	private function add(
		array $hooks,
		string $hook,
		?object $component,
		string $callback,
		int $priority,
		int $args
	): array {
		$hooks[] = array(
			'hook'      => $hook,
			'component' => $component,
			'callback'  => $callback,
			'priority'  => $priority,
			'args'      => $args,
		);

		return $hooks;
	}

	/**
	 * Resolve a callable from a component + method name, or a plain function name.
	 *
	 * @since 2.0.0
	 *
	 * @param object|null $component Object instance or null.
	 * @param string      $callback  Method or function name.
	 * @return callable
	 */
	private function resolve_callable( ?object $component, string $callback ): callable {
		if ( null === $component ) {
			// Plain PHP function (e.g. 'wpfaqblock_load_textdomain').
			return $callback;
		}

		return array( $component, $callback );
	}
}
