<?php

declare(strict_types=1);

final class MyronModuleRegistry
{
    /** @var array<string, bool> */
    private array $presentModules = [];

    /** @var list<callable(MyronActionRegistry): void> */
    private array $actionInitializers = [];

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

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

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

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

    /** @var array<string, list<array<string, mixed>>> */
    private array $adminMenuItems = [];

    /** @var list<string> */
    private array $adminStylesheets = [];

    /** @var list<callable(string, string): bool> */
    private array $publicHandlers = [];

    /** @var list<callable(string, array<string, mixed>): string> */
    private array $publicPathFilters = [];

    /** @var list<callable(string, array<string, mixed>): ?array<string, mixed>> */
    private array $publicPageResolvers = [];

    /** @var list<callable(array<string, string|null>, bool): ?MyronActionCallerContext> */
    private array $authMethods = [];

    /** @var array<string, callable(PDO): void|list<string>> */
    private array $migrations = [];

    /** @var list<callable(MyronSectionRendererRegistry): void> */
    private array $sectionRendererInitializers = [];

    public function markPresent(string $slug): void
    {
        $slug = trim($slug);
        if ($slug !== '') {
            $this->presentModules[$slug] = true;
        }
    }

    public function isPresent(string $slug): bool
    {
        return isset($this->presentModules[trim($slug)]);
    }

    /**
     * @return list<string>
     */
    public function presentModules(): array
    {
        return array_keys($this->presentModules);
    }

    /**
     * @param callable(MyronActionRegistry): void $initializer
     */
    public function registerActionInitializer(callable $initializer): void
    {
        $this->actionInitializers[] = $initializer;
    }

    public function initializeActions(MyronActionRegistry $registry): void
    {
        foreach ($this->actionInitializers as $initializer) {
            $initializer($registry);
        }
    }

    /**
     * @param callable(MyronSectionRendererRegistry): void $initializer
     */
    public function registerSectionRendererInitializer(callable $initializer): void
    {
        $this->sectionRendererInitializers[] = $initializer;
    }

    public function initializeSectionRenderers(MyronSectionRendererRegistry $registry): void
    {
        foreach ($this->sectionRendererInitializers as $initializer) {
            $initializer($registry);
        }
    }

    /**
     * @param array<string, string> $routes
     * @param array<string, string> $capabilities
     */
    public function registerAdminRoutes(array $routes, array $capabilities = []): void
    {
        $this->adminRoutes = array_replace($this->adminRoutes, $routes);
        $this->adminRouteCapabilities = array_replace($this->adminRouteCapabilities, $capabilities);
    }

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

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

    /**
     * @param array<string, string> $routes
     */
    public function registerAdminUrlRoutes(array $routes): void
    {
        $this->adminUrlRoutes = array_replace($this->adminUrlRoutes, $routes);
    }

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

    /**
     * @param array<string, string> $aliases
     */
    public function registerLegacyAdminAliases(array $aliases): void
    {
        $this->legacyAdminAliases = array_replace($this->legacyAdminAliases, $aliases);
    }

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

    /**
     * @param array<string, mixed> $item
     */
    public function registerAdminMenuItem(string $branchKey, array $item): void
    {
        $branchKey = trim($branchKey);
        if ($branchKey === '') {
            return;
        }
        $this->adminMenuItems[$branchKey][] = $item;
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function adminMenuItems(string $branchKey): array
    {
        return $this->adminMenuItems[$branchKey] ?? [];
    }

    public function registerAdminStylesheet(string $assetPath): void
    {
        $assetPath = trim($assetPath);
        if ($assetPath !== '') {
            $this->adminStylesheets[] = $assetPath;
        }
    }

    /**
     * @return list<string>
     */
    public function adminStylesheets(): array
    {
        return array_values(array_unique($this->adminStylesheets));
    }

    /**
     * @param callable(string, string): bool $handler
     */
    public function registerPublicHandler(callable $handler): void
    {
        $this->publicHandlers[] = $handler;
    }

    /**
     * @return list<callable(string, string): bool>
     */
    public function publicHandlers(): array
    {
        return $this->publicHandlers;
    }

    /**
     * @param callable(string, array<string, mixed>): string $filter
     */
    public function registerPublicPathFilter(callable $filter): void
    {
        $this->publicPathFilters[] = $filter;
    }

    /**
     * @param array<string, mixed> $context
     */
    public function filterPublicPath(string $resolvedPath, array $context = []): string
    {
        foreach ($this->publicPathFilters as $filter) {
            $resolvedPath = $filter($resolvedPath, $context);
        }

        return $resolvedPath;
    }

    /**
     * @param callable(string, array<string, mixed>): ?array<string, mixed> $resolver
     */
    public function registerPublicPageResolver(callable $resolver): void
    {
        $this->publicPageResolvers[] = $resolver;
    }

    /**
     * @param array<string, mixed> $context
     * @return array<string, mixed>|null
     */
    public function resolvePublicPage(string $resolvedPath, array $context = []): ?array
    {
        foreach ($this->publicPageResolvers as $resolver) {
            $result = $resolver($resolvedPath, $context);
            if (is_array($result)) {
                return $result;
            }
        }

        return null;
    }

    /**
     * @param callable(array<string, string|null>, bool): ?MyronActionCallerContext $method
     */
    public function registerAuthMethod(callable $method): void
    {
        $this->authMethods[] = $method;
    }

    /**
     * @return list<callable(array<string, string|null>, bool): ?MyronActionCallerContext>
     */
    public function authMethods(): array
    {
        return $this->authMethods;
    }

    /**
     * @param callable(PDO): void|list<string> $migration
     */
    public function registerMigration(string $version, callable|array $migration): void
    {
        $version = trim($version);
        if ($version !== '') {
            $this->migrations[$version] = $migration;
        }
    }

    /**
     * @param array<string, callable(PDO): void|list<string>> $migrations
     */
    public function registerMigrations(array $migrations): void
    {
        foreach ($migrations as $version => $migration) {
            $this->registerMigration((string) $version, $migration);
        }
    }

    /**
     * @return array<string, callable(PDO): void|list<string>>
     */
    public function migrations(): array
    {
        return $this->migrations;
    }
}
