<?php
/**
 * Hook Manager Service
 *
 * Centralized WordPress hook management with optional tracing.
 *
 * @package     WPUltimateSecurity\UltimateSecurity\Services
 * @since       2.0.0
 */

declare(strict_types=1);

namespace WPUltimateSecurity\UltimateSecurity\Services;

/**
 * Centralized hook management with tracing support.
 *
 * Provides:
 * - Wrapper methods for WordPress hook functions
 * - Optional hook tracing for debugging
 * - Consistent hook registration patterns
 *
 * @since 2.0.0
 */
class HookManager
{
    /**
     * Whether hook tracing is enabled.
     *
     * @var bool
     */
    private static bool $tracing = false;

    /**
     * Traced hook records.
     *
     * @var array<int, array{type: string, operation: string, hook: string, callback: string, priority: int, time: float}>
     */
    private static array $traced = [];

    /**
     * Add an action hook.
     *
     * @param string   $hook         Hook name.
     * @param callable $callback     Callback function.
     * @param int      $priority     Priority (default 10).
     * @param int      $acceptedArgs Number of accepted arguments (default 1).
     *
     * @return void
     */
    public static function addAction(
        string $hook,
        callable $callback,
        int $priority = 10,
        int $acceptedArgs = 1
    ): void {
        add_action($hook, $callback, $priority, $acceptedArgs);

        if (self::$tracing) {
            self::trace('action', 'add', $hook, $callback, $priority);
        }
    }

    /**
     * Add a filter hook.
     *
     * @param string   $hook         Hook name.
     * @param callable $callback     Callback function.
     * @param int      $priority     Priority (default 10).
     * @param int      $acceptedArgs Number of accepted arguments (default 1).
     *
     * @return void
     */
    public static function addFilter(
        string $hook,
        callable $callback,
        int $priority = 10,
        int $acceptedArgs = 1
    ): void {
        add_filter($hook, $callback, $priority, $acceptedArgs);

        if (self::$tracing) {
            self::trace('filter', 'add', $hook, $callback, $priority);
        }
    }

    /**
     * Fire an action.
     *
     * @param string $hook    Hook name.
     * @param mixed  ...$args Arguments to pass.
     *
     * @return void
     */
    public static function doAction(string $hook, mixed ...$args): void
    {
        if (self::$tracing) {
            self::trace('action', 'fire', $hook, null, 0);
        }

        do_action($hook, ...$args);
    }

    /**
     * Apply filters.
     *
     * @param string $hook    Hook name.
     * @param mixed  $value   Value to filter.
     * @param mixed  ...$args Additional arguments.
     *
     * @return mixed Filtered value.
     */
    public static function applyFilters(string $hook, mixed $value, mixed ...$args): mixed
    {
        if (self::$tracing) {
            self::trace('filter', 'apply', $hook, null, 0);
        }

        return apply_filters($hook, $value, ...$args);
    }

    /**
     * Remove an action.
     *
     * @param string   $hook     Hook name.
     * @param callable $callback Callback to remove.
     * @param int      $priority Priority used when adding.
     *
     * @return bool True if removed, false otherwise.
     */
    public static function removeAction(string $hook, callable $callback, int $priority = 10): bool
    {
        return remove_action($hook, $callback, $priority);
    }

    /**
     * Remove a filter.
     *
     * @param string   $hook     Hook name.
     * @param callable $callback Callback to remove.
     * @param int      $priority Priority used when adding.
     *
     * @return bool True if removed, false otherwise.
     */
    public static function removeFilter(string $hook, callable $callback, int $priority = 10): bool
    {
        return remove_filter($hook, $callback, $priority);
    }

    /**
     * Enable hook tracing.
     *
     * @return void
     */
    public static function enableTracing(): void
    {
        self::$tracing = true;
    }

    /**
     * Disable hook tracing.
     *
     * @return void
     */
    public static function disableTracing(): void
    {
        self::$tracing = false;
    }

    /**
     * Get traced hooks.
     *
     * @return array<int, array{type: string, operation: string, hook: string, callback: string, priority: int, time: float}>
     */
    public static function getTraced(): array
    {
        return self::$traced;
    }

    /**
     * Clear traced hooks.
     *
     * @return void
     */
    public static function clearTraced(): void
    {
        self::$traced = [];
    }

    /**
     * Record a hook trace.
     *
     * @param string        $type      Hook type (action or filter).
     * @param string        $operation Operation (add, fire, apply).
     * @param string        $hook      Hook name.
     * @param callable|null $callback  Callback if applicable.
     * @param int           $priority  Priority.
     *
     * @return void
     */
    private static function trace(
        string $type,
        string $operation,
        string $hook,
        ?callable $callback,
        int $priority
    ): void {
        self::$traced[] = [
            'type' => $type,
            'operation' => $operation,
            'hook' => $hook,
            'callback' => self::formatCallback($callback),
            'priority' => $priority,
            'time' => microtime(true),
        ];
    }

    /**
     * Format callback for logging.
     *
     * @param callable|null $callback The callback to format.
     *
     * @return string Formatted callback string.
     */
    private static function formatCallback(?callable $callback): string
    {
        if ($callback === null) {
            return '';
        }

        if (is_string($callback)) {
            return $callback;
        }

        if (is_array($callback)) {
            $class = is_object($callback[0]) ? get_class($callback[0]) : $callback[0];
            return $class . '::' . $callback[1];
        }

        if ($callback instanceof \Closure) {
            return 'Closure';
        }

        return 'unknown';
    }
}
