<?php

declare(strict_types=1);

namespace ProPresenter\Parser;

use Rv\Data\MacrosDocument;

/**
 * Wraps a protobuf MacrosDocument — the global ProPresenter `Macros` file
 * which lists every macro definition and the collections that group them.
 *
 * Lookup helpers index macros and collections by UUID (case-insensitive) and
 * by name, mirroring the convention used by {@see Song}. Collection
 * membership is resolved by mapping {@see MacroCollection::getMacroUuids()}
 * back through this library.
 */
class MacroLibrary
{
    /** @var Macro[] */
    private array $macros = [];

    /** @var MacroCollection[] */
    private array $collections = [];

    /** @var array<string, Macro> */
    private array $macrosByUuid = [];

    /** @var array<string, Macro> */
    private array $macrosByName = [];

    /** @var array<string, MacroCollection> */
    private array $collectionsByUuid = [];

    /** @var array<string, MacroCollection> */
    private array $collectionsByName = [];

    /** @var array<string, MacroCollection[]> */
    private array $collectionsByMacroUuid = [];

    public function __construct(
        private readonly MacrosDocument $document,
    ) {
        foreach ($this->document->getMacros() as $macroProto) {
            $macro = new Macro($macroProto);
            $this->macros[] = $macro;

            $uuid = strtoupper($macro->getUuid());
            if ($uuid !== '') {
                $this->macrosByUuid[$uuid] = $macro;
            }

            $name = $macro->getName();
            if ($name !== '') {
                $this->macrosByName[$name] = $macro;
            }
        }

        foreach ($this->document->getMacroCollections() as $collectionProto) {
            $collection = new MacroCollection($collectionProto);
            $this->collections[] = $collection;

            $uuid = strtoupper($collection->getUuid());
            if ($uuid !== '') {
                $this->collectionsByUuid[$uuid] = $collection;
            }

            $name = $collection->getName();
            if ($name !== '') {
                $this->collectionsByName[$name] = $collection;
            }

            foreach ($collection->getMacroUuids() as $macroUuid) {
                $key = strtoupper($macroUuid);
                if ($key === '') {
                    continue;
                }
                $this->collectionsByMacroUuid[$key][] = $collection;
            }
        }
    }

    /**
     * @return Macro[]
     */
    public function getMacros(): array
    {
        return $this->macros;
    }

    public function getMacroByUuid(string $uuid): ?Macro
    {
        return $this->macrosByUuid[strtoupper($uuid)] ?? null;
    }

    public function getMacroByName(string $name): ?Macro
    {
        return $this->macrosByName[$name] ?? null;
    }

    /**
     * @return MacroCollection[]
     */
    public function getCollections(): array
    {
        return $this->collections;
    }

    public function getCollectionByUuid(string $uuid): ?MacroCollection
    {
        return $this->collectionsByUuid[strtoupper($uuid)] ?? null;
    }

    public function getCollectionByName(string $name): ?MacroCollection
    {
        return $this->collectionsByName[$name] ?? null;
    }

    /**
     * Resolve a collection's referenced macros to {@see Macro} wrappers.
     *
     * Unknown UUIDs (referenced but not defined at document root) are skipped.
     *
     * @return Macro[]
     */
    public function getMacrosForCollection(MacroCollection $collection): array
    {
        $resolved = [];
        foreach ($collection->getMacroUuids() as $uuid) {
            $macro = $this->getMacroByUuid($uuid);
            if ($macro !== null) {
                $resolved[] = $macro;
            }
        }

        return $resolved;
    }

    /**
     * Get the collections a macro belongs to (membership is by UUID
     * reference). A macro may legally appear in zero, one, or many
     * collections.
     *
     * @return MacroCollection[]
     */
    public function getCollectionsForMacro(Macro $macro): array
    {
        $key = strtoupper($macro->getUuid());
        if ($key === '') {
            return [];
        }

        return $this->collectionsByMacroUuid[$key] ?? [];
    }

    /**
     * Get the underlying protobuf MacrosDocument.
     */
    public function getDocument(): MacrosDocument
    {
        return $this->document;
    }
}
