<?php
/**
 * Abstract Registry Class
 *
 * Base class for singleton registries managing typed handlers.
 *
 * @package Sequensy
 * @since 2.0.0
 */

declare(strict_types=1);

namespace Sequensy\Registry;

// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}

/**
 * Abstract Registry
 *
 * Provides common functionality for Action and Trigger registries.
 * Subclasses must implement abstract methods to customize behavior.
 */
abstract class AbstractRegistry {

	/**
	 * Registered handlers keyed by type.
	 *
	 * @var array<string, object>
	 */
	protected array $handlers = array();

	/**
	 * Protected constructor for singleton pattern.
	 * Subclasses must call parent::__construct().
	 */
	protected function __construct() {
		add_action( 'init', array( $this, 'fire_registration_hook' ), 5 );
	}

	/**
	 * Fire the registration hook.
	 *
	 * Note: Transient cache is NOT cleared here. Cache invalidation should happen:
	 * - On plugin activation/deactivation
	 * - When explicitly requested via clear_metadata_cache()
	 * - Automatically via transient expiration (1 hour)
	 *
	 * @return void
	 */
	public function fire_registration_hook(): void {
		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Hook name uses class method for registry pattern.
		do_action( $this->get_registration_hook(), $this );
	}

	/**
	 * Clear the metadata cache for this registry.
	 *
	 * Should be called when handlers are added/removed outside of normal
	 * registration (e.g., when a plugin is activated/deactivated).
	 *
	 * @return void
	 */
	public function clear_cache(): void {
		delete_transient( $this->get_transient_key() );
	}

	/**
	 * Reset the registry (for testing).
	 *
	 * Clears all handlers and cache. Should only be used in tests.
	 *
	 * @return void
	 */
	public function reset(): void {
		$this->handlers = array();
		$this->clear_cache();
	}

	/**
	 * Register a handler.
	 *
	 * @param string $type    Handler type identifier.
	 * @param object $handler Handler instance.
	 * @return bool True on success, false if already registered.
	 */
	public function register( string $type, object $handler ): bool {
		if ( isset( $this->handlers[ $type ] ) ) {
			return false;
		}

		$this->handlers[ $type ] = $handler;
		$this->on_registered( $type, $handler );

		return true;
	}

	/**
	 * Get a handler by type.
	 *
	 * @param string $type Handler type identifier.
	 * @return object|null Handler instance or null if not found.
	 */
	public function get( string $type ): ?object {
		return $this->handlers[ $type ] ?? null;
	}

	/**
	 * Get all registered handlers.
	 *
	 * @return array<string, object> Array of handlers keyed by type.
	 */
	public function get_all(): array {
		return $this->handlers;
	}

	/**
	 * Check if a handler type is registered.
	 *
	 * @param string $type Handler type identifier.
	 * @return bool True if registered, false otherwise.
	 */
	public function has( string $type ): bool {
		return isset( $this->handlers[ $type ] );
	}

	/**
	 * Get the WordPress action hook name for registration.
	 *
	 * @return string Hook name (e.g., 'sequensy_register_actions').
	 */
	abstract protected function get_registration_hook(): string;

	/**
	 * Get the transient key for metadata caching.
	 *
	 * @return string Transient key (e.g., 'sequensy_action_metadata').
	 */
	abstract protected function get_transient_key(): string;

	/**
	 * Hook called after a handler is registered.
	 * Override to add post-registration behavior.
	 *
	 * @param string $type    Handler type.
	 * @param object $handler Handler instance.
	 * @return void
	 */
	protected function on_registered( string $type, object $handler ): void {
		// Default: no-op. TriggerRegistry overrides this.
	}
}
