<?php

declare(strict_types=1);

namespace PpPhp\Core\DataOps;

/**
 * The single source of truth for which import/export formats exist.
 *
 * Used by BOTH the console (Pages module settings, the build's toolbar + guards)
 * and the generated app (export.php / import.php). Because every consumer asks
 * the registry, a new format shows up everywhere at once — settings checkbox,
 * toolbar menu, accept="" list, and the server-side whitelist.
 *
 * Register extras from a plugin or a project bootstrap:
 *     FormatRegistry::registerExport(MyPdfFormat::class);
 *     FormatRegistry::registerReader(MyXlsxReader::class);
 */
final class FormatRegistry
{
    /** @var array<string, class-string<ExportFormat>> */
    private static array $exports = [];

    /** @var array<string, class-string<ImportReader>> */
    private static array $readers = [];

    private static bool $booted = false;

    /** Built-in formats. Kept as class-strings so nothing loads until used. */
    private const BUILTIN_EXPORTS = [
        Formats\CsvFormat::class,
        Formats\XlsxFormat::class,
        Formats\ExcelFormat::class,
        Formats\WordFormat::class,
        Formats\JsonFormat::class,
        Formats\XmlFormat::class,
    ];

    private const BUILTIN_READERS = [
        Readers\DelimitedReader::class,
        Readers\XlsxReader::class,
        Readers\JsonReader::class,
        Readers\XmlReader::class,
    ];

    private static function boot(): void
    {
        if (self::$booted) return;
        self::$booted = true;
        foreach (self::BUILTIN_EXPORTS as $c) self::registerExport($c);
        foreach (self::BUILTIN_READERS as $c) self::registerReader($c);
    }

    /** @param class-string<ExportFormat> $class */
    public static function registerExport(string $class): void
    {
        self::$booted = true;   // an explicit registration implies boot order is ours
        if (!class_exists($class)) return;
        self::$exports[$class::id()] = $class;
    }

    /** @param class-string<ImportReader> $class */
    public static function registerReader(string $class): void
    {
        self::$booted = true;
        if (!class_exists($class)) return;
        self::$readers[$class::id()] = $class;
    }

    /** @return array<string, class-string<ExportFormat>> */
    public static function exports(): array
    {
        if (!self::$booted) self::boot();
        return self::$exports;
    }

    /** @return array<string, class-string<ImportReader>> */
    public static function readers(): array
    {
        if (!self::$booted) self::boot();
        return self::$readers;
    }

    public static function export(string $id): ?ExportFormat
    {
        $c = self::exports()[$id] ?? null;
        return $c ? new $c() : null;
    }

    public static function reader(string $id): ?ImportReader
    {
        $c = self::readers()[$id] ?? null;
        return $c ? new $c() : null;
    }

    /** The reader that handles a file extension, or null. */
    public static function readerForExtension(string $ext): ?ImportReader
    {
        $ext = strtolower(ltrim($ext, '.'));
        foreach (self::readers() as $c) {
            if (in_array($ext, $c::extensions(), true)) return new $c();
        }
        return null;
    }

    /**
     * Descriptors for the Pages module UI / build.
     *
     * @return list<array{id:string,label:string,extension:string}>
     */
    public static function exportChoices(): array
    {
        $out = [];
        foreach (self::exports() as $id => $c) {
            $out[] = ['id' => $id, 'label' => $c::label(), 'extension' => $c::extension()];
        }
        return $out;
    }

    /** @return list<array{id:string,label:string,extensions:list<string>,paste:bool}> */
    public static function readerChoices(): array
    {
        $out = [];
        foreach (self::readers() as $id => $c) {
            $out[] = ['id' => $id, 'label' => $c::label(), 'extensions' => $c::extensions(), 'paste' => $c::supportsPaste()];
        }
        return $out;
    }

    /**
     * Every file extension accepted by the given reader ids — powers accept=""
     * and the upload guard.
     *
     * @param list<string> $readerIds
     * @return list<string>
     */
    public static function extensionsFor(array $readerIds): array
    {
        $out = [];
        foreach ($readerIds as $id) {
            $c = self::readers()[$id] ?? null;
            if ($c) foreach ($c::extensions() as $e) $out[$e] = true;
        }
        return array_keys($out);
    }
}
