<?php

namespace Sitecake\Event;

/**
 * Simple event manager — replaces Cake\Event\EventManager.
 *
 * Supports:
 *  - on($eventName, callable)               register a named listener
 *  - on($listenerObject)                    auto-register via implementedEvents()
 *  - dispatch($name, $data, $subject)       fire event, return Event object
 *  - instance()                             global singleton (mirrors CakePHP API)
 */
class EventManager
{
    /** @var EventManager|null Global singleton */
    protected static ?EventManager $globalInstance = null;

    /** @var array<string, callable[]> */
    protected array $listeners = [];

    /**
     * Register a listener.
     *
     * @param string|object $listenerOrName  Event name, or an object whose implementedEvents() maps names to methods
     * @param callable|null $callable        Listener callable (required when $listenerOrName is a string)
     */
    public function on($listenerOrName, callable $callable = null): void
    {
        if ($callable === null && is_object($listenerOrName)) {
            // Auto-register all events the object declares
            if (method_exists($listenerOrName, 'implementedEvents')) {
                foreach ($listenerOrName->implementedEvents() as $eventName => $method) {
                    $this->listeners[$eventName][] = [$listenerOrName, $method];
                }
            }
            return;
        }
        $this->listeners[(string)$listenerOrName][] = $callable;
    }

    /**
     * Dispatch an event and return it.
     *
     * @param string $name    Event name
     * @param array  $data    Payload passed to listeners
     * @param mixed  $subject The object that dispatched the event
     */
    public function dispatch(string $name, array $data = [], $subject = null): Event
    {
        $event = new Event($name, $subject, $data);

        foreach ($this->listeners[$name] ?? [] as $listener) {
            $result = call_user_func($listener, $event);
            if ($result !== null) {
                $event->setResult($result);
            }
            if ($event->isStopped()) {
                break;
            }
        }

        return $event;
    }

    /**
     * Remove a listener.
     *
     * @param string|object $listenerOrName  Event name to clear all listeners, or a listener object
     */
    public function off($listenerOrName): void
    {
        if (is_string($listenerOrName)) {
            unset($this->listeners[$listenerOrName]);
            return;
        }
        if (is_object($listenerOrName) && method_exists($listenerOrName, 'implementedEvents')) {
            foreach ($listenerOrName->implementedEvents() as $eventName => $method) {
                if (isset($this->listeners[$eventName])) {
                    $this->listeners[$eventName] = array_values(array_filter(
                        $this->listeners[$eventName],
                        fn($l) => !($l === [$listenerOrName, $method])
                    ));
                }
            }
        }
    }

    /**
     * Return all registered callables for a given event name.
     *
     * @return callable[]
     */
    public function listeners(string $name): array
    {
        return $this->listeners[$name] ?? [];
    }

    /** Return (or create) the global singleton instance. */
    public static function instance(): self
    {
        if (self::$globalInstance === null) {
            self::$globalInstance = new self();
        }
        return self::$globalInstance;
    }
}
