<?php

declare(strict_types=1);

namespace App\Services\Hook;

/**
 * Hook 매니저 — 그누보드 g7 Action/Filter 패턴의 Laravel 12 패리티.
 *
 * - doAction(event, ...args): 부수효과 호출 (반환값 무시)
 * - applyFilters(event, value, ...args): 값 변환 (체이닝)
 * - addAction / addFilter: 콜백 등록 (priority 낮을수록 먼저)
 *
 * 자동 발견은 HookServiceProvider 가 담당.
 */
class HookManager
{
    /**
     * @var array<string, array<int, array<int, callable>>>
     *      event => priority => callable[]
     */
    private array $actions = [];

    /**
     * @var array<string, array<int, array<int, callable>>>
     */
    private array $filters = [];

    /**
     * Action 등록.
     */
    public function addAction(string $event, callable $callback, int $priority = 10): void
    {
        $this->actions[$event][$priority][] = $callback;
    }

    /**
     * Filter 등록.
     */
    public function addFilter(string $event, callable $callback, int $priority = 10): void
    {
        $this->filters[$event][$priority][] = $callback;
    }

    /**
     * Action 호출 — 모든 콜백을 priority 순으로 실행. 반환값은 무시한다.
     *
     * @param  mixed  ...$args
     */
    public function doAction(string $event, mixed ...$args): void
    {
        if (! isset($this->actions[$event])) {
            return;
        }

        $buckets = $this->actions[$event];
        ksort($buckets);

        foreach ($buckets as $callbacks) {
            foreach ($callbacks as $callback) {
                $callback(...$args);
            }
        }
    }

    /**
     * Filter 호출 — $value 를 콜백 체인을 통해 변환한 결과를 반환.
     *
     * 각 콜백의 첫 인자는 직전 콜백 결과값. 나머지는 보조 인자(불변).
     *
     * @param  mixed  $value
     * @param  mixed  ...$args
     * @return mixed
     */
    public function applyFilters(string $event, mixed $value, mixed ...$args): mixed
    {
        if (! isset($this->filters[$event])) {
            return $value;
        }

        $buckets = $this->filters[$event];
        ksort($buckets);

        foreach ($buckets as $callbacks) {
            foreach ($callbacks as $callback) {
                $value = $callback($value, ...$args);
            }
        }

        return $value;
    }

    /**
     * 디버그/테스트용 — 등록된 모든 event 이름 반환.
     *
     * @return array{actions: array<int, string>, filters: array<int, string>}
     */
    public function listEvents(): array
    {
        return [
            'actions' => array_keys($this->actions),
            'filters' => array_keys($this->filters),
        ];
    }

    /**
     * 테스트 격리용 — 모든 hook 초기화.
     */
    public function flush(): void
    {
        $this->actions = [];
        $this->filters = [];
    }
}
