<?php

namespace App\Domain\Website\Blocks;

use App\Domain\Website\Enums\BlockCategory;
use InvalidArgumentException;

/**
 * The single source of truth for which blocks the builder offers.
 *
 * Registered as a singleton in WebsiteServiceProvider. Because block types are
 * plain classes, a client-specific capability can be added by dropping one file
 * into Blocks/Types and registering it — nothing in the database changes.
 */
class BlockRegistry
{
    /** @var array<string,BlockType> */
    protected array $types = [];

    public function register(BlockType|string $type): static
    {
        $instance = is_string($type) ? new $type : $type;

        if (! $instance instanceof BlockType) {
            throw new InvalidArgumentException('Block types must extend BlockType.');
        }

        $this->types[$instance->key()] = $instance;

        return $this;
    }

    public function registerMany(array $types): static
    {
        foreach ($types as $type) {
            $this->register($type);
        }

        return $this;
    }

    public function has(string $key): bool
    {
        return isset($this->types[$key]);
    }

    public function get(string $key): ?BlockType
    {
        return $this->types[$key] ?? null;
    }

    /** @throws InvalidArgumentException when the key was never registered. */
    public function resolve(string $key): BlockType
    {
        return $this->types[$key]
            ?? throw new InvalidArgumentException("Unknown website block type [{$key}].");
    }

    /** @return array<string,BlockType> */
    public function all(): array
    {
        return $this->types;
    }

    public function keys(): array
    {
        return array_keys($this->types);
    }

    /** Blocks offered in the picker, grouped by category, hidden ones removed. */
    public function grouped(): array
    {
        $grouped = [];

        foreach ($this->types as $type) {
            if ($type->isHidden()) {
                continue;
            }
            $grouped[$type->category()->value][] = $type;
        }

        // Present categories in the enum's declared order, not insertion order.
        $ordered = [];
        foreach (BlockCategory::cases() as $category) {
            if (! empty($grouped[$category->value])) {
                $ordered[$category->value] = $grouped[$category->value];
            }
        }

        return $ordered;
    }

    /** @return BlockType[] */
    public function inCategory(BlockCategory $category): array
    {
        return array_values(array_filter(
            $this->types,
            fn (BlockType $t) => $t->category() === $category && ! $t->isHidden(),
        ));
    }
}
