<?php
/**
 * Internal hook/event system for TitanCart.
 *
 * Provides a set-hook / run-hooks event pattern for modules.
 * Allows modules to intercept and modify data at defined points
 * without direct coupling to each other.
 *
 * Usage:
 *   // Register:
 *   TC_Hooks::register('product_data', function( &$product, $id ) { ... }, 10);
 *
 *   // Fire:
 *   TC_Hooks::call('product_data', $product, $id);
 *
 * @package TitanCart\Core
 */

namespace TitanCart\Core;

defined( 'ABSPATH' ) || exit;

class HookManager {

    /**
     * Registered hook handlers.
     * Structure: [ hook_name => [ priority => [ callable, ... ] ] ]
     *
     * @var array<string, array<int, callable[]>>
     */
    private array $hooks = [];

    /**
     * Register a handler for a named hook.
     *
     * @param string   $hook     Hook name.
     * @param callable $callback Handler. Receives hook arguments by reference.
     * @param int      $priority Lower numbers run first (default 10).
     */
    public function register( string $hook, callable $callback, int $priority = 10 ): void {
        $this->hooks[ $hook ][ $priority ][] = $callback;
    }

    /**
     * Fire all handlers registered for a hook.
     *
     * Arguments are passed BY REFERENCE so handlers can mutate data.
     * Extra positional arguments after $hook are forwarded to each handler.
     *
     * @param string $hook   Hook name.
     * @param mixed  ...$args Arguments to pass (by reference via intermediate array).
     */
    public function call( string $hook, mixed &...$args ): void {
        if ( empty( $this->hooks[ $hook ] ) ) {
            return;
        }

        ksort( $this->hooks[ $hook ] ); // Sort by priority.

        foreach ( $this->hooks[ $hook ] as $handlers ) {
            foreach ( $handlers as $handler ) {
                $handler( ...$args );
            }
        }
    }

    /**
     * Remove a specific handler from a hook.
     *
     * @param string   $hook     Hook name.
     * @param callable $callback The exact callable to remove.
     * @param int      $priority Priority it was registered at.
     */
    public function remove( string $hook, callable $callback, int $priority = 10 ): void {
        if ( ! isset( $this->hooks[ $hook ][ $priority ] ) ) {
            return;
        }

        $this->hooks[ $hook ][ $priority ] = array_filter(
            $this->hooks[ $hook ][ $priority ],
            fn( $h ) => $h !== $callback
        );
    }

    /**
     * Check whether any handler is registered for a given hook.
     *
     * @param  string $hook Hook name.
     * @return bool
     */
    public function has( string $hook ): bool {
        return ! empty( $this->hooks[ $hook ] );
    }

    /**
     * Return all hook names that have at least one handler.
     *
     * @return string[]
     */
    public function registered(): array {
        return array_keys( array_filter( $this->hooks ) );
    }
}
