<?php

declare(strict_types=1);

namespace PpPhp\Core\Storage;

/**
 * StorageRegistry — the one place callers obtain a driver.
 *
 * The plugin, api/upload.php, the file endpoint and the view controls call
 * StorageRegistry::driver($connectionId) (or ::forRef($ref) to resolve the
 * backend a stored file already lives on) and then use the StorageDriver
 * interface. Adding a backend = registering one class here; nothing else moves.
 */
final class StorageRegistry
{
    /** driver type => implementing class. Cloud classes are optional (phase 2). */
    private const DRIVERS = [
        'local'    => LocalDriver::class,
        's3'       => __NAMESPACE__ . '\\S3Driver',
        'gdrive'   => __NAMESPACE__ . '\\GoogleDriveDriver',
        'onedrive' => __NAMESPACE__ . '\\OneDriveDriver',
        'ftp'      => __NAMESPACE__ . '\\FtpStorageDriver',
    ];

    /** @var array<string,StorageDriver> */
    private static array $instances = [];

    /**
     * Driver for a named connection (default 'local'). $overrides merge into the
     * connection config (e.g. ['private'=>true] for a private field).
     */
    public static function driver(string $connectionId = 'local', array $overrides = []): StorageDriver
    {
        $conn = StorageConfig::get($connectionId);
        $conn = array_merge($conn, $overrides);
        $type = (string) ($conn['driver'] ?? 'local');
        $cacheKey = $type . ':' . ($conn['id'] ?? $connectionId) . ':' . md5(serialize($overrides));

        if (isset(self::$instances[$cacheKey])) return self::$instances[$cacheKey];

        $class = self::DRIVERS[$type] ?? null;
        if ($class === null || !class_exists($class)) {
            // Cloud driver not present yet (or unknown type): fall back to local
            // so a mis-typed field never fatals the page — it just stays local.
            $class = LocalDriver::class;
        }
        /** @var StorageDriver $inst */
        $inst = new $class($conn);
        return self::$instances[$cacheKey] = $inst;
    }

    /** The driver a stored reference lives on (reads ref['driver']). */
    public static function forRef(array $ref, array $overrides = []): StorageDriver
    {
        $type = (string) ($ref['driver'] ?? 'local');
        // Match a saved connection of this driver type, else synthesise one.
        foreach (StorageConfig::all() as $c) {
            if ((string) ($c['driver'] ?? '') === $type) {
                return self::driver((string) $c['id'], $overrides);
            }
        }
        $class = self::DRIVERS[$type] ?? LocalDriver::class;
        if (!class_exists($class)) $class = LocalDriver::class;
        return new $class(array_merge(['driver' => $type], $overrides));
    }

    /** Resolve a stored reference to a URL in one call. */
    public static function urlFor(array $ref, array $opts = []): string
    {
        return self::forRef($ref)->url($ref, $opts);
    }

    /** Whether a driver type is available in this install. */
    public static function available(string $type): bool
    {
        $class = self::DRIVERS[$type] ?? null;
        return $class !== null && class_exists($class);
    }
}
