<?php

declare(strict_types=1);

namespace Drupal\flowdrop_node_type;

use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Pager\PagerManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Provides a listing of flowdrop node types.
 *
 * Features sortable table headers with default sorting by label alphabetically.
 * Supports pagination for large datasets.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
final class FlowDropNodeTypeListBuilder extends ConfigEntityListBuilder {

  /**
   * The number of entities to list per page.
   *
   * @var int
   */
  protected $limit = 50;

  /**
   * The current sort field.
   *
   * @var string
   */
  protected string $sortField = "label";

  /**
   * The current sort direction.
   *
   * @var string
   */
  protected string $sortDirection = "asc";

  /**
   * A cache of category labels keyed by category ID.
   *
   * @var array<string, string>
   */
  protected array $categoryLabels = [];

  /**
   * Constructs a new FlowDropNodeTypeListBuilder object.
   *
   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
   *   The entity type definition.
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   The entity storage class.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\Core\Pager\PagerManagerInterface $pagerManager
   *   The pager manager service.
   * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
   *   The request stack.
   */
  public function __construct(
    EntityTypeInterface $entity_type,
    EntityStorageInterface $storage,
    protected readonly EntityTypeManagerInterface $entityTypeManager,
    protected readonly PagerManagerInterface $pagerManager,
    protected readonly RequestStack $requestStack,
  ) {
    parent::__construct($entity_type, $storage);
  }

  /**
   * {@inheritdoc}
   */
  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type): static {
    return new static(
      $entity_type,
      $container->get("entity_type.manager")->getStorage($entity_type->id()),
      $container->get("entity_type.manager"),
      $container->get("pager.manager"),
      $container->get("request_stack"),
    );
  }

  /**
   * {@inheritdoc}
   *
   * @return array<\Drupal\Core\Entity\EntityInterface>
   *   An array of entities sorted according to the current tablesort.
   */
  public function load(): array {
    $entities = parent::load();

    // Get sort parameters from request.
    $request = $this->requestStack->getCurrentRequest();
    $order = $request->query->get("order");
    $sort = $request->query->get("sort", "asc");

    // Determine the field to sort by based on header label.
    if ($order !== NULL) {
      $fieldMap = [
        "Label" => "label",
        "Status" => "enabled",
        "Category" => "category",
        "Executor" => "executor_plugin",
        "Version" => "plugin_version",
      ];
      if (isset($fieldMap[$order])) {
        $this->sortField = $fieldMap[$order];
        $this->sortDirection = $sort;
      }
    }

    // Sort entities in memory.
    usort($entities, function ($a, $b) {
      /** @var \Drupal\flowdrop_node_type\FlowDropNodeTypeInterface $a */
      /** @var \Drupal\flowdrop_node_type\FlowDropNodeTypeInterface $b */
      $valueA = $this->getEntitySortValue($a, $this->sortField);
      $valueB = $this->getEntitySortValue($b, $this->sortField);

      // Compare values.
      if ($valueA === $valueB) {
        return 0;
      }

      $result = $valueA < $valueB ? -1 : 1;

      // Reverse for descending order.
      if ($this->sortDirection === "desc") {
        $result = -$result;
      }

      return $result;
    });

    // Apply pagination.
    return $this->getEntityListForPager($entities);
  }

  /**
   * Gets a paginated slice of entities.
   *
   * @param array<\Drupal\Core\Entity\EntityInterface> $entities
   *   All sorted entities.
   *
   * @return array<\Drupal\Core\Entity\EntityInterface>
   *   Entities for the current page.
   */
  protected function getEntityListForPager(array $entities): array {
    // Get current page from request.
    $page = $this->requestStack->getCurrentRequest()?->query->getInt("page", 0) ?? 0;

    // Calculate offset.
    $offset = $page * $this->limit;

    // Initialize pager.
    $this->pagerManager->createPager(count($entities), $this->limit);

    // Return slice of entities for current page.
    return array_slice($entities, $offset, $this->limit);
  }

  /**
   * Gets the value for sorting from an entity.
   *
   * @param \Drupal\flowdrop_node_type\FlowDropNodeTypeInterface $entity
   *   The entity to get the value from.
   * @param string $field
   *   The field name to get.
   *
   * @return mixed
   *   The value for sorting.
   */
  protected function getEntitySortValue(FlowDropNodeTypeInterface $entity, string $field): mixed {
    switch ($field) {
      case "id":
        return strtolower($entity->id() ?? "");

      case "label":
        return strtolower($entity->label() ?? "");

      case "category":
        return strtolower($entity->getCategory());

      case "plugin_version":
        return strtolower($entity->getPluginVersion() ?? "");

      case "enabled":
        return $entity->isEnabled() ? 1 : 0;

      case "executor_plugin":
        return strtolower($entity->getExecutorPlugin() ?? "");

      default:
        return "";
    }
  }

  /**
   * {@inheritdoc}
   *
   * Consistent column order: Label, Status, Category, Executor Plugin, Version.
   *
   * @return array<string, array<string, mixed>>
   *   An array of sortable header definitions.
   */
  public function buildHeader(): array {
    $header = [
      "label" => [
        "data" => $this->t("Label"),
        "field" => "label",
        "specifier" => "label",
        "sort" => "asc",
      ],
      "enabled" => [
        "data" => $this->t("Status"),
        "field" => "enabled",
        "specifier" => "enabled",
      ],
      "category" => [
        "data" => $this->t("Category"),
        "field" => "category",
        "specifier" => "category",
      ],
      "executor_plugin" => [
        "data" => $this->t("Executor"),
        "field" => "executor_plugin",
        "specifier" => "executor_plugin",
      ],
      "plugin_version" => [
        "data" => $this->t("Version"),
        "field" => "plugin_version",
        "specifier" => "plugin_version",
      ],
    ];
    return $header + parent::buildHeader();
  }

  /**
   * {@inheritdoc}
   *
   * @return array<string, mixed>
   *   An array of row data for the entity.
   */
  public function buildRow(EntityInterface $entity): array {
    /** @var \Drupal\flowdrop_node_type\FlowDropNodeTypeInterface $entity */
    $row["label"] = $entity->label();
    $row["enabled"] = $this->buildStatusPill($entity->isEnabled());
    $row["category"] = $this->getCategoryLabel($entity->getCategory());
    $row["executor_plugin"] = $entity->getExecutorPlugin() ?: $this->t("None");
    $row["plugin_version"] = $entity->getPluginVersion() ?: $this->t("N/A");
    return $row + parent::buildRow($entity);
  }

  /**
   * Gets the human-readable label for a category.
   *
   * Looks up the category entity and returns its label. Falls back to
   * transforming the category ID to a human-readable format if the
   * category entity doesn't exist.
   *
   * @param string $categoryId
   *   The category ID (machine name).
   *
   * @return string
   *   The human-readable category label.
   */
  protected function getCategoryLabel(string $categoryId): string {
    // Return from cache if available.
    if (isset($this->categoryLabels[$categoryId])) {
      return $this->categoryLabels[$categoryId];
    }

    // Try to load the category entity.
    try {
      $categoryEntity = $this->entityTypeManager
        ->getStorage("flowdrop_node_category")
        ->load($categoryId);

      if ($categoryEntity !== NULL) {
        $label = $categoryEntity->label();
        if (is_string($label) && $label !== "") {
          $this->categoryLabels[$categoryId] = $label;
          return $label;
        }
      }
    }
    catch (\Exception) {
      // If category entity storage doesn't exist, fall through to fallback.
    }

    // Fallback: transform snake_case to human-readable format.
    // E.g., "human_in_loop_interrupts" becomes "Human In Loop Interrupts".
    $label = ucwords(str_replace("_", " ", $categoryId));
    $this->categoryLabels[$categoryId] = $label;
    return $label;
  }

  /**
   * {@inheritdoc}
   *
   * @return array<string, mixed>
   *   The render array for the list.
   */
  public function render(): array {
    $build = parent::render();

    // Add tablesort to the table.
    $build["table"]["#header"] = $this->buildHeader();
    $build["table"]["#attributes"]["class"][] = "sortable-table";

    // Add pager for pagination.
    $build["pager"] = [
      "#type" => "pager",
    ];

    return $build;
  }

  /**
   * Builds a status pill render array for table cell display.
   *
   * @param bool $isEnabled
   *   Whether the entity is enabled.
   *
   * @return array<string, mixed>
   *   A table cell render array containing the status pill component.
   */
  private function buildStatusPill(bool $isEnabled): array {
    return [
      "data" => [
        "#type" => "component",
        "#component" => "flowdrop_ui_components:pill",
        "#props" => [
          "label" => $isEnabled ? (string) $this->t("Enabled") : (string) $this->t("Disabled"),
          "variant" => $isEnabled ? "success" : "neutral",
          "style" => "soft",
          "size" => "sm",
        ],
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getDefaultOperations(EntityInterface $entity): array {
    $operations = [];

    // Edit operation.
    $entityId = $entity->id();
    if (is_string($entityId)) {
      $operations["edit"] = [
        "title" => $this->t("Edit"),
        "url" => $entity->toUrl("edit-form"),
        "weight" => 10,
      ];

      // Delete operation.
      $operations["delete"] = [
        "title" => $this->t("Delete"),
        "url" => $entity->toUrl("delete-form"),
        "weight" => 100,
      ];
    }

    return $operations;
  }

}
