<?php

declare(strict_types=1);


namespace Apexl\Io\Mirror\Helpers;

use Apexl\Config\ConfigurationInterface;
use Apexl\Config\Exceptions\MissingConfigException;
use Apexl\Io\Mirror\Exceptions\UploaderException;
use Apexl\Io\Mirror\Factories\UploaderServiceFactory;
use Apexl\Io\HookManager;
use Apexl\Io\Mirror\Interfaces\MirrorsFilesInterface;
use Apexl\Io\Mirror\Interfaces\UploaderServiceInterface;
use Apexl\Io\Mirror\Interfaces\UploaderServiceResolverInterface;
use Arrayy\Type\StringCollection;
use DI\DependencyException;
use DI\NotFoundException;

final readonly class UploaderServiceResolver implements UploaderServiceResolverInterface
{
    private StringCollection $aliases;

    public function __construct(
        private ConfigurationInterface $config,
        private UploaderServiceFactory $uploaderFactory,
        private HookManager $hookManager,
    ) {
        $this->aliases = new StringCollection();
    }

    /**
     * @throws DependencyException
     * @throws NotFoundException
     * @throws UploaderException
     */
    public function resolve(string $entityClass): ?UploaderServiceInterface
    {
        if (is_subclass_of($entityClass, MirrorsFilesInterface::class)) {
            return $entityClass::getUploader();
        }

        try {
            $classOrAlias = $this->config->get('mirror.default_uploader');

            if ($classOrAlias) {
                return $this->uploaderFactory->make($this->resolveAlias($classOrAlias));
            }

            $this->noDefaultUploader();
        } catch (MissingConfigException $e) {
            throw new UploaderException(message: $e->getMessage(), code: $e->getCode(), previous: $e);

        }

        return null;
    }

    /**
     * @throws MissingConfigException
     * @throws UploaderException
     */
    private function noDefaultUploader(): void
    {
        if (!$this->config->get('mirror.allow_no_default_uploader')) {
            throw new UploaderException(message: 'No default uploader has been specified in the configuration.');
        }
    }

    /**
     * Map a configured alias to its uploader class, or pass a class-string
     * straight through. Drivers register their alias on the hook, so an install
     * can set MIRROR_DEFAULT_UPLOADER=s3 rather than name a class.
     */
    private function resolveAlias(string $alias): string
    {
        $aliases = $this->aliases->isEmpty() ? $this->hookManager->processHook(
            self::HOOK__UPLOADER_SERVICE_ALIASES,
            $this->aliases,
        ) : $this->aliases;

        return $aliases[$alias] ?? $alias;
    }
}
