<?php

namespace Vich\UploaderBundle\Metadata;

use Metadata\AdvancedMetadataFactoryInterface;
use Vich\UploaderBundle\Exception\MappingNotFoundException;

/**
 * MetadataReader.
 *
 * Exposes a simple interface to read objects metadata.
 *
 * @author Kévin Gomez <contact@kevingomez.fr>
 */
class MetadataReader
{
    /**
     * @var AdvancedMetadataFactoryInterface
     */
    protected $reader;

    /**
     * Constructs a new instance of the MetadataReader.
     *
     * @param AdvancedMetadataFactoryInterface $reader The "low-level" metadata reader
     */
    public function __construct(AdvancedMetadataFactoryInterface $reader)
    {
        $this->reader = $reader;
    }

    /**
     * Tells if the given class is uploadable.
     *
     * @param string      $class   The class name to test (FQCN)
     * @param string|null $mapping If given, also checks that the object has the given mapping
     *
     * @throws MappingNotFoundException
     */
    public function isUploadable(string $class, ?string $mapping = null): bool
    {
        $metadata = $this->reader->getMetadataForClass($class);

        if (null === $metadata) {
            return false;
        }

        if (null === $mapping) {
            return true;
        }

        foreach ($this->getUploadableFields($class) as $fieldMetadata) {
            if ($fieldMetadata['mapping'] === $mapping) {
                return true;
            }
        }

        return false;
    }

    /**
     * Search for all uploadable classes.
     *
     * @return array|null A list of uploadable class names
     *
     * @throws \RuntimeException
     */
    public function getUploadableClasses(): ?array
    {
        return $this->reader->getAllClassNames();
    }

    /**
     * Attempts to read the uploadable fields.
     *
     * @param string      $class   The class name to test (FQCN)
     * @param string|null $mapping If given, also checks that the object has the given mapping
     *
     * @return array A list of uploadable fields
     *
     * @throws MappingNotFoundException
     */
    public function getUploadableFields(string $class, ?string $mapping = null): array
    {
        if (null === $metadata = $this->reader->getMetadataForClass($class)) {
            throw MappingNotFoundException::createNotFoundForClass($mapping ?? '', $class);
        }
        $uploadableFields = [];

        /** @var ClassMetadata $classMetadata */
        foreach ($metadata->classMetadata as $classMetadata) {
            $uploadableFields = \array_merge($uploadableFields, $classMetadata->fields);
        }

        if (null !== $mapping) {
            $uploadableFields = \array_filter($uploadableFields, static function (array $fieldMetadata) use ($mapping): bool {
                return $fieldMetadata['mapping'] === $mapping;
            });
        }

        return $uploadableFields;
    }

    /**
     * Attempts to read the mapping of a specified property.
     *
     * @param string $class The class name to test (FQCN)
     * @param string $field The field
     *
     * @return mixed The field mapping
     *
     * @throws MappingNotFoundException
     */
    public function getUploadableField(string $class, string $field)
    {
        $fieldsMetadata = $this->getUploadableFields($class);

        return $fieldsMetadata[$field] ?? null;
    }
}
