<?php

declare(strict_types=1);

namespace Drupal\acquia_dam;

use Drupal\acquia_dam\Entity\ManagedImageField;
use Drupal\acquia_dam\Plugin\media\Source\Asset;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\media\MediaTypeInterface;

/**
 * Media type resolver for assets.
 */
final class MediaTypeResolver {

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  private $entityTypeManager;

  /**
   * Constructs a new MediaTypeResolver object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * Resolves the media type for an asset.
   *
   * @param array $asset
   *   The asset data.
   * @param array $bundles
   *   The media type list.
   *
   * @return \Drupal\media\MediaTypeInterface|null
   *   The media type, or NULL if one cannot be resolved.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function resolve(array $asset, array $bundles = []): ?MediaTypeInterface {
    $result = NULL;

    if (!isset($asset['file_properties'])) {
      return $result;
    }
    $mapping = [
      'ft' => 'format_type',
      'ff' => 'format',
    ];

    // 1. Run a simple mapping logic based on media source plugin definitions.
    foreach ($this->getMediaTypes() as $media_type) {
      $source = $media_type->getSource();
      $definition = $source->getPluginDefinition();
      $property_name = $mapping[$definition['asset_search_key']] ?? $definition['asset_search_key'];

      // Look for property in both file_properties and metadata.
      $property_value = $asset['file_properties'][$property_name] ?? $asset['metadata']['fields'][$property_name] ?? '';
      if (is_array($property_value)) {
        $property_value = reset($property_value);
      }

      // Compare the data type the actual source plugin can handle vs. the type
      // of the current asset in question.
      if ($definition['asset_search_value'] === $property_value && (empty($bundles) || in_array($media_type->id(), $bundles))) {
        $result = $media_type;
        break;
      }
    }

    // 2. Refine the decision based on the conceptual difference between Widen
    // and Drupal. Widen considers, for example, '.svg/.tiff/.ai' files as an
    // image, so a media item of type 'Image' would be created. With on-site
    // storing enabled, reference to the locally saved asset file would be
    // stored in an 'Image' field. In this case, Drupal is unable to apply
    // image styles on such files when rendering the field. Therefore override
    // here the media type based on what an 'Image' field can render.
    if ($result instanceof MediaTypeInterface && $result->id() === 'acquia_dam_image_asset') {
      $media_type_config = $result->get('source_configuration');

      if (isset($media_type_config['download_assets']) && $media_type_config['download_assets']) {
        $file_extension = strtolower(pathinfo($source->getValidFilename($asset), PATHINFO_EXTENSION));

        // At this point there is no media item existing yet for the asset, so
        // we need to instantiate a temporary one to read its bundle field
        // definition and its settings from.
        $accepted_file_extensions = $this->entityTypeManager->getStorage('media')
          ->create(['bundle' => 'acquia_dam_image_asset'])
          ->getFieldDefinition(ManagedImageField::MANAGED_IMAGE_FIELD_NAME)->getSetting('file_extensions') ?? '';

        // If there are no file extensions explicitly configured for
        // the media type, then we should not override the media type to
        // generic since the image media type is still valid for the asset even
        // if it has an uncommon file extension.
        if (empty($accepted_file_extensions)) {
          return $result;
        }

        // Define the values to search for in an array.
        $search = [", ", ","];
        // Define the replacement value (a single space)
        $replace = " ";
        $accepted_file_extensions = explode(' ', str_replace($search, $replace, $accepted_file_extensions));
        if (!in_array($file_extension, $accepted_file_extensions)) {
          // Rely on the generic fallback at the end.
          $result = NULL;
        }
      }
    }

    // 3. Finally, if none of the media types with Asset source plugin can be
    // matched to the asset, then the media type with 'Acquia DAM: Generic'
    // source plugin will be used if exists.
    if (!$result) {
      // Fallback to the media type with 'Acquia DAM: Generic'
      // source plugin as a last resort.
      $generic_types = $this->entityTypeManager->getStorage('media_type')->loadByProperties(['source' => 'acquia_dam_asset:generic']);
      // If there are multiple media types with the generic source plugin,
      // then the first one will be used.
      $result = $generic_types ? reset($generic_types) : NULL;
    }

    return $result;
  }

  /**
   * Gets media types with Asset source plugin.
   *
   * @return \Drupal\media\MediaTypeInterface[]
   *   The media types.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   *
   * @phpstan-return array<string, \Drupal\media\MediaTypeInterface>
   */
  private function getMediaTypes(): array {
    static $media_types = [];

    if ($media_types === []) {
      $media_type_storage = $this->entityTypeManager->getStorage('media_type');
      $media_types = array_filter($media_type_storage->loadMultiple(), static function (MediaTypeInterface $media_type) {
        return $media_type->getSource() instanceof Asset;
      });
    }

    return $media_types;
  }

}
