<?php

declare(strict_types=1);

namespace Drupal\migrate_leshed\Plugin\migrate\process;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Finds or creates 'personne' nodes from a list of names.
 *
 * Input: a string such as "Julie Faitot et Jonathan Loppin" or
 * "Sonja Beaudouin, Alexandre Delabrière". Names are split on "et" and
 * commas. For each name, the first word is used as 'field_prenom' and the
 * rest as 'field_nom'. The node title is generated from those two fields by
 * the auto_entitylabel module on save. An existing node with the same nom and
 * prenom is reused, otherwise it is created. Returns an array of node IDs.
 *
 * When 'type_de_personne' is set in the configuration, a taxonomy term with
 * that name is found or created (without duplicates) in the
 * 'type_de_personne' vocabulary, and referenced from the node's
 * 'field_type_de_personne' field. The tag is also added to matching
 * existing nodes that do not yet have it.
 */
#[MigrateProcess(id: 'personne_generate', handle_multiples: TRUE)]
final class PersonneGenerate extends ProcessPluginBase implements ContainerFactoryPluginInterface {

  /**
   * Placeholder values that must not create a personne node.
   */
  private const IGNORED_VALUES = ['/', '?', '-'];

  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    private readonly EntityTypeManagerInterface $entityTypeManager,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): self {
    return new self(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('entity_type.manager'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property): array {
    if (!is_string($value) || trim($value) === '') {
      return [];
    }

    // Ignore everything after "avec" (e.g. "Camille Bondon avec les élèves
    // de l'école André Marie" only keeps "Camille Bondon").
    $value = preg_replace('/\s+avec\s+.*$/us', '', $value);

    // Normalize separators: " et " becomes a comma.
    $value = preg_replace('/\s+et\s+/u', ',', $value);
    $names = array_filter(array_map('trim', explode(',', $value)));

    $nids = [];
    foreach ($names as $name) {
      if (in_array($name, self::IGNORED_VALUES, TRUE)) {
        continue;
      }
      // First word is the first name, the rest is the last name.
      $parts = preg_split('/\s+/u', $name, 2);
      $prenom = $parts[0];
      $nom = $parts[1] ?? '';
      // Single-word name: use it as the title, without a first name.
      if ($nom === '') {
        $nom = $prenom;
        $prenom = '';
      }
      $nids[] = $this->getOrCreatePersonne($nom, $prenom);
    }

    return $nids;
  }

  /**
   * Returns the nid of the matching personne node, creating it if needed.
   */
  private function getOrCreatePersonne(string $nom, string $prenom): int {
    $storage = $this->entityTypeManager->getStorage('node');

    $type_name = $this->configuration['type_de_personne'] ?? NULL;
    $tid = $type_name ? $this->getOrCreateType($type_name) : NULL;

    $query = $storage->getQuery()
      ->condition('type', 'personne')
      ->condition('field_nom', $nom)
      ->accessCheck(FALSE)
      ->range(0, 1);
    if ($prenom !== '') {
      $query->condition('field_prenom', $prenom);
    }
    else {
      $query->notExists('field_prenom');
    }
    $ids = $query->execute();
    if ($ids) {
      $nid = (int) reset($ids);
      if ($tid !== NULL) {
        $this->addTypeToPersonne($nid, $tid);
      }
      return $nid;
    }

    // The node title is generated from field_prenom + field_nom by
    // auto_entitylabel on save; it is intentionally not set here.
    $values = [
      'type' => 'personne',
      'field_nom' => $nom,
      'field_prenom' => $prenom === '' ? NULL : $prenom,
      'status' => 1,
      'uid' => $this->configuration['author_uid'] ?? 3,
    ];
    if ($tid !== NULL) {
      $values['field_type_de_personne'] = $tid;
    }
    $node = $storage->create($values);
    $node->save();

    return (int) $node->id();
  }

  /**
   * Adds a type_de_personne term to an existing node if not already set.
   */
  private function addTypeToPersonne(int $nid, int $tid): void {
    $node = $this->entityTypeManager->getStorage('node')->load($nid);
    if ($node === NULL) {
      return;
    }
    $existing = array_column($node->get('field_type_de_personne')->getValue(), 'target_id');
    if (!in_array((string) $tid, array_map('strval', $existing), TRUE)) {
      $node->get('field_type_de_personne')->appendItem(['target_id' => $tid]);
      $node->save();
    }
  }

  /**
   * Returns the tid of the matching type_de_personne term, creating it if needed.
   */
  private function getOrCreateType(string $name): int {
    $storage = $this->entityTypeManager->getStorage('taxonomy_term');

    $ids = $storage->getQuery()
      ->condition('vid', 'type_de_personne')
      ->condition('name', $name)
      ->accessCheck(FALSE)
      ->range(0, 1)
      ->execute();
    if ($ids) {
      return (int) reset($ids);
    }

    $term = $storage->create([
      'vid' => 'type_de_personne',
      'name' => $name,
    ]);
    $term->save();

    return (int) $term->id();
  }

}
