<?php

declare(strict_types=1);

namespace Drupal\flowdrop_trigger\Service;

use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\media\MediaInterface;
use Drupal\node\NodeInterface;
use Drupal\taxonomy\TermInterface;
use Drupal\user\UserInterface;
use Psr\Log\LoggerInterface;

/**
 * Service for serializing entities for trigger data.
 *
 * Converts Drupal entities into arrays suitable for passing
 * to workflow executions. Handles different entity types and
 * provides complete entity data including field values.
 */
class EntitySerializer {

  /**
   * Logger instance.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected LoggerInterface $logger;

  /**
   * Constructs an EntitySerializer.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $loggerFactory
   *   The logger factory.
   */
  public function __construct(
    protected EntityTypeManagerInterface $entityTypeManager,
    LoggerChannelFactoryInterface $loggerFactory,
  ) {
    $this->logger = $loggerFactory->get("flowdrop_trigger");
  }

  /**
   * Serialize an entity to an array.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity to serialize.
   *
   * @return array
   *   The serialized entity data.
   */
  public function serialize(EntityInterface $entity): array {
    try {
      // Build base entity data.
      $data = [
        "entity_type" => $entity->getEntityTypeId(),
        "id" => $entity->id(),
        "uuid" => $entity->uuid(),
        "bundle" => $entity->bundle(),
        "label" => $entity->label(),
        "langcode" => $entity->language()->getId(),
      ];

      // Add fieldable entity data.
      if ($entity instanceof FieldableEntityInterface) {
        $data["fields"] = $this->serializeFields($entity);
      }

      // Add entity-specific data based on type.
      $data = array_merge($data, $this->getEntityTypeSpecificData($entity));

      return $data;
    }
    catch (\Exception $e) {
      $this->logger->error("Failed to serialize entity @type @id: @message", [
        "@type" => $entity->getEntityTypeId(),
        "@id" => $entity->id() ?? "new",
        "@message" => $e->getMessage(),
      ]);

      // Return minimal data on error.
      return [
        "entity_type" => $entity->getEntityTypeId(),
        "id" => $entity->id(),
        "bundle" => $entity->bundle(),
        "serialization_error" => $e->getMessage(),
      ];
    }
  }

  /**
   * Serialize entity fields.
   *
   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
   *   The fieldable entity.
   *
   * @return array
   *   The serialized fields.
   */
  protected function serializeFields(FieldableEntityInterface $entity): array {
    $fields = [];

    foreach ($entity->getFields() as $fieldName => $fieldItemList) {
      // Skip computed fields.
      $fieldDefinition = $fieldItemList->getFieldDefinition();
      if ($fieldDefinition->isComputed()) {
        continue;
      }

      try {
        $value = $fieldItemList->getValue();

        // Handle entity references specially to include basic reference info.
        $fieldType = $fieldDefinition->getType();
        if (in_array($fieldType, ["entity_reference", "entity_reference_revisions"], TRUE)) {
          $value = $this->serializeEntityReferenceField($fieldItemList);
        }

        $fields[$fieldName] = $value;
      }
      catch (\Exception $e) {
        $this->logger->warning("Failed to serialize field @field: @message", [
          "@field" => $fieldName,
          "@message" => $e->getMessage(),
        ]);
        $fields[$fieldName] = NULL;
      }
    }

    return $fields;
  }

  /**
   * Serialize an entity reference field with basic referenced entity info.
   *
   * @param \Drupal\Core\Field\FieldItemListInterface $fieldItemList
   *   The field item list.
   *
   * @return array
   *   Serialized entity reference data.
   */
  protected function serializeEntityReferenceField($fieldItemList): array {
    $values = [];

    foreach ($fieldItemList as $delta => $item) {
      $value = $item->getValue();

      // Try to get basic info about referenced entity.
      if (isset($value["target_id"])) {
        $referencedEntity = $item->entity;
        if ($referencedEntity instanceof EntityInterface) {
          $value["target_label"] = $referencedEntity->label();
          $value["target_bundle"] = $referencedEntity->bundle();
          $value["target_uuid"] = $referencedEntity->uuid();
        }
      }

      $values[$delta] = $value;
    }

    return $values;
  }

  /**
   * Get entity type specific data.
   *
   * Extracts commonly used properties for specific entity types.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity.
   *
   * @return array
   *   Entity type specific data.
   */
  protected function getEntityTypeSpecificData(EntityInterface $entity): array {
    $data = [];

    // Handle nodes.
    if ($entity instanceof NodeInterface) {
      $data["created"] = $entity->getCreatedTime();
      $data["changed"] = $entity->getChangedTime();
      $data["status"] = $entity->isPublished();
      $data["sticky"] = $entity->isSticky();
      $data["promoted"] = $entity->isPromoted();
      $data["author_id"] = $entity->getOwnerId();
      $data["title"] = $entity->getTitle();
    }

    // Handle users.
    if ($entity instanceof UserInterface) {
      $data["username"] = $entity->getAccountName();
      $data["email"] = $entity->getEmail();
      $data["roles"] = $entity->getRoles();
      $data["status"] = $entity->isActive();
      $data["created"] = $entity->getCreatedTime();
      $data["last_login"] = $entity->getLastLoginTime();
      $data["last_access"] = $entity->getLastAccessedTime();
    }

    // Handle taxonomy terms.
    if ($entity instanceof TermInterface) {
      $data["name"] = $entity->getName();
      $data["description"] = $entity->getDescription();
      $data["weight"] = $entity->getWeight();
      $data["vocabulary"] = $entity->bundle();
      $parent = $entity->get("parent")->getValue();
      $data["parent_id"] = !empty($parent) ? ($parent[0]["target_id"] ?? NULL) : NULL;
    }

    // Handle media.
    if ($entity instanceof MediaInterface) {
      $data["name"] = $entity->getName();
      $data["created"] = $entity->getCreatedTime();
      $data["changed"] = $entity->getChangedTime();
      $data["status"] = $entity->isPublished();
      $data["author_id"] = $entity->getOwnerId();
      $data["media_type"] = $entity->bundle();
    }

    return $data;
  }

  /**
   * Serialize multiple entities.
   *
   * @param array $entities
   *   Array of entities to serialize.
   *
   * @return array
   *   Array of serialized entity data.
   */
  public function serializeMultiple(array $entities): array {
    $serialized = [];

    foreach ($entities as $key => $entity) {
      if ($entity instanceof EntityInterface) {
        $serialized[$key] = $this->serialize($entity);
      }
    }

    return $serialized;
  }

}
