<?php

namespace Drupal\thunder_gqls\Plugin\GraphQL\SchemaExtension;

use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\graphql\GraphQL\ResolverRegistryInterface;
use Drupal\graphql\Plugin\DataProducerPluginManager;
use Drupal\graphql\Plugin\GraphQL\SchemaExtension\SdlSchemaExtensionPluginBase;
use Drupal\thunder_gqls\Traits\ResolverHelperTrait;
use Drupal\user\EntityOwnerInterface;
use GraphQL\Language\Source;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * The base class for Thunder schema extension plugins.
 */
abstract class ThunderSchemaExtensionPluginBase extends SdlSchemaExtensionPluginBase {

  use ResolverHelperTrait;

  /**
   * The data producer plugin manager.
   *
   * @var \Drupal\graphql\Plugin\DataProducerPluginManager
   */
  protected DataProducerPluginManager $dataProducerManager;

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

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    $plugin = parent::create($container, $configuration, $plugin_id, $plugin_definition);
    $plugin->createResolverBuilder();
    $plugin->setDataProducerManager($container->get('plugin.manager.graphql.data_producer'));
    $plugin->setEntityTypeManager($container->get('entity_type.manager'));
    return $plugin;
  }

  /**
   * {@inheritdoc}
   */
  public function getBaseDefinition(): ?Source {
    return $this->loadDefinitionFileSafe('base');
  }

  /**
   * {@inheritdoc}
   */
  public function getExtensionDefinition(): ?Source {
    return $this->loadDefinitionFileSafe('extension');
  }

  /**
   * Returns the path to a schema definition file.
   *
   * @param string $type
   *   The definition type ('base' or 'extension').
   *
   * @return string
   *   Absolute path to the .graphqls file.
   */
  protected function getDefinitionFileName(string $type): string {
    $id = $this->getPluginId();
    $definition = $this->getPluginDefinition();
    $module = $this->moduleHandler->getModule($definition['provider']);
    return $module->getPath() . '/graphql/' . $id . '.' . $type . '.graphqls';
  }

  /**
   * Loads a definition file without throwing when the file is absent.
   *
   * Returns NULL when no file exists, allowing schema extensions that only
   * need one of the two definition types to omit the other entirely.
   * Emits a deprecation warning when an empty file is found, since older
   * graphql module versions required an empty file as a no-op placeholder.
   *
   * @param string $type
   *   The definition type ('base' or 'extension').
   *
   * @return \GraphQL\Language\Source|null
   *   The parsed source, or NULL if no file is present.
   */
  protected function loadDefinitionFileSafe(string $type): ?Source {
    $file = $this->getDefinitionFileName($type);

    if (!file_exists($file)) {
      return NULL;
    }

    if (trim(file_get_contents($file)) === '') {
      @trigger_error(sprintf(
        'An empty schema definition file "%s" was found for plugin "%s". Empty definition files are no longer required and should be removed. Please delete the file.',
        $file,
        $this->getPluginId()
      ), E_USER_NOTICE);
      return NULL;
    }

    return parent::loadDefinitionFile($type);
  }

  /**
   * {@inheritdoc}
   */
  public function registerResolvers(ResolverRegistryInterface $registry): void {
    $this->registry = $registry;
  }

  /**
   * Set the plugin manager.
   *
   * @param \Drupal\graphql\Plugin\DataProducerPluginManager $pluginManager
   *   The data producer plugin manager.
   */
  protected function setDataProducerManager(DataProducerPluginManager $pluginManager): void {
    $this->dataProducerManager = $pluginManager;
  }

  /**
   * Set the entity type manager.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager service.
   */
  protected function setEntityTypeManager(EntityTypeManagerInterface $entityTypeManager): void {
    $this->entityTypeManager = $entityTypeManager;
  }

  /**
   * Add fields common to all entities.
   *
   * @param string $type
   *   The type name.
   * @param string $entity_type_id
   *   The entity type ID.
   */
  protected function resolveBaseFields(string $type, string $entity_type_id): void {
    $this->addFieldResolverIfNotExists(
      $type,
      'uuid',
      $this->builder->produce('entity_uuid')
        ->map('entity', $this->builder->fromParent())
    );

    $this->addFieldResolverIfNotExists(
      $type,
      'id',
      // Coerce NULL to 0 for preview.
      $this->builder->defaultValue(
        $this->builder->produce('entity_id')
          ->map('entity', $this->builder->fromParent()),
        $this->builder->fromValue(0)
      )
    );

    $this->addFieldResolverIfNotExists(
      $type,
      'entity',
      $this->builder->produce('entity_type_id')
        ->map('entity', $this->builder->fromParent())
    );

    $this->addFieldResolverIfNotExists(
      $type,
      'name',
      $this->builder->defaultValue(
        $this->builder->produce('entity_label')
          ->map('entity', $this->builder->fromParent()),
        $this->builder->fromValue('')
      )
    );

    $this->addFieldResolverIfNotExists($type, 'language',
      $this->builder->fromPath('entity', 'langcode.value')
    );

    $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
    if ($entity_type->hasLinkTemplate('canonical')) {
      $this->addFieldResolverIfNotExists($type, 'url',
        $this->builder->compose(
          $this->builder->cond([
            [
              $this->builder->produce('entity_is_new')
                ->map('entity', $this->builder->fromParent()),
              $this->builder->fromValue('#'),
            ], [
              $this->builder->fromValue('TRUE'),
              $this->builder->compose(
                $this->builder->produce('entity_url')
                  ->map('entity', $this->builder->fromParent()),
                $this->builder->produce('url_path')
                  ->map('url', $this->builder->fromParent()),
              ),
            ],
          ])
        )
      );
    }
    else {
      $this->addFieldResolverIfNotExists($type, 'url',
        $this->builder->fromValue(NULL)
      );
    }

    if (method_exists($entity_type->getClass(), 'getCreatedTime')) {
      $this->addFieldResolverIfNotExists($type, 'created',
        $this->builder->produce('entity_created')
          ->map('entity', $this->builder->fromParent())
      );
    }

    if ($entity_type->entityClassImplements(EntityChangedInterface::class)) {
      $this->addFieldResolverIfNotExists($type, 'changed',
        $this->builder->produce('entity_changed')
          ->map('entity', $this->builder->fromParent())
      );
    }

    if ($entity_type->entityClassImplements(EntityPublishedInterface::class)) {
      $this->addFieldResolverIfNotExists($type, 'published',
        $this->builder->produce('entity_published')
          ->map('entity', $this->builder->fromParent())
      );
    }

    if ($entity_type->entityClassImplements(EntityOwnerInterface::class)) {
      $this->addFieldResolverIfNotExists($type, 'author',
        $this->builder->produce('entity_owner')
          ->map('entity', $this->builder->fromParent())
      );
    }

    $this->addFieldResolverIfNotExists($type, 'entityLinks',
      $this->builder->produce('entity_links')
        ->map('entity', $this->builder->fromParent())
    );
  }

  /**
   * Add fields common to all media types.
   *
   * @param string $type
   *   The type name.
   */
  protected function resolveMediaInterfaceFields(string $type): void {
    $this->resolveBaseFields($type, 'media');

    $this->addFieldResolverIfNotExists($type, 'thumbnail',
      $this->builder->produce('thunder_image')
        ->map('entity', $this->builder->fromPath('entity', 'thumbnail.entity'))
        ->map('field', $this->builder->fromPath('entity', 'thumbnail'))
    );

    if ($this->dataProducerManager->hasDefinition('media_expire_fallback_entity')) {
      $this->addFieldResolverIfNotExists($type, 'fallbackMedia',
        $this->builder->produce('media_expire_fallback_entity')
          ->map('entity', $this->builder->fromParent())
      );
    }
  }

  /**
   * Add fields common to all paragraph types.
   *
   * @param string $type
   *   The type name.
   */
  protected function resolveParagraphInterfaceFields(string $type): void {
    $this->addFieldResolverIfNotExists($type, 'summary',
      $this->builder->produce('paragraph_summary')
        ->map('paragraph', $this->builder->fromParent())
    );
  }

  /**
   * Add fields common to all page types.
   *
   * @param string $type
   *   The type name.
   * @param string $entity_type_id
   *   The entity type ID.
   */
  protected function resolvePageInterfaceFields(string $type, string $entity_type_id): void {
    $this->resolveBaseFields($type, $entity_type_id);
  }

  /**
   * Add content query field resolvers.
   *
   * @param string $page_type
   *   The page type name.
   * @param string $entity_type_id
   *   The entity type ID.
   */
  protected function resolvePageInterfaceQueryFields(string $page_type, string $entity_type_id): void {
    $this->addFieldResolverIfNotExists('Query', $page_type,
      $this->builder->produce('entity_load_by_uuid')
        ->map('type', $this->builder->fromValue($entity_type_id))
        ->map('uuid', $this->builder->fromArgument('uuid'))
    );
  }

}
