<?php

declare(strict_types=1);

namespace Drupal\flowdrop\Service;

use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\FieldItemListInterface;
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 to arrays.
 *
 * @internal
 *   This class is not part of the public API and may change without notice.
 *
 * Converts Drupal entities into arrays suitable for passing
 * to workflow executions. Handles different entity types and
 * provides complete entity data including field values.
 *
 * This service provides a consistent entity serialization format
 * that can be used across FlowDrop modules (triggers, processors, etc.).
 */
class EntitySerializer {

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

  /**
   * Constructs an EntitySerializer.
   *
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $loggerFactory
   *   The logger factory.
   */
  public function __construct(
    LoggerChannelFactoryInterface $loggerFactory,
  ) {
    $this->logger = $loggerFactory->get("flowdrop");
  }

  /**
   * Serialize an entity to an array.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity to serialize.
   *
   * @return array<string, mixed>
   *   The serialized entity data containing:
   *   - entity_type: The entity type ID
   *   - id: The entity ID
   *   - uuid: The entity UUID
   *   - bundle: The entity bundle
   *   - label: The entity label
   *   - langcode: The entity language code
   *   - fields: Array of field values (for fieldable entities)
   *   - Entity-type-specific properties (created, changed, etc.)
   */
  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<string, mixed>
   *   The serialized fields as key-value pairs.
   */
  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<int, array<string, mixed>>
   *   Serialized entity reference data with target_id, target_label,
   *   target_bundle, and target_uuid for each reference.
   */
  protected function serializeEntityReferenceField(FieldItemListInterface $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
   * (nodes, users, taxonomy terms, media).
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity.
   *
   * @return array<string, mixed>
   *   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<int|string, \Drupal\Core\Entity\EntityInterface> $entities
   *   Array of entities to serialize.
   *
   * @return array<int|string, array<string, mixed>>
   *   Array of serialized entity data, keyed the same as input.
   */
  public function serializeMultiple(array $entities): array {
    $serialized = [];

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

    return $serialized;
  }

}
