<?php

declare(strict_types=1);

namespace Drupal\flowdrop\ListBuilder;

use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Trait providing common functionality for FlowDrop list builders.
 *
 * Provides shared table sorting, status pill rendering, entity reference
 * label rendering, and tablesort-enabled render output for list builders
 * that extend EntityListBuilder.
 *
 * Classes using this trait MUST:
 * - Extend \Drupal\Core\Entity\EntityListBuilder
 * - Implement getSortableFields()
 * - Have a $requestStack property of type RequestStack
 *
 * @internal
 */
trait FlowDropListBuilderTrait {

  /**
   * The request stack.
   *
   * @var \Symfony\Component\HttpFoundation\RequestStack
   */
  protected RequestStack $requestStack;

  /**
   * Returns the list of field names that support sorting.
   *
   * @return string[]
   *   An array of entity field names that can be used for table sorting.
   */
  abstract protected function getSortableFields(): array;

  /**
   * Builds a sorted and paged list of entity IDs for the table display.
   *
   * Reads the "order" and "sort" query parameters from the current request
   * to determine which column to sort by. Falls back to sorting by the
   * "changed" field in descending order (newest first).
   *
   * @return array<int|string>
   *   An array of entity IDs, sorted according to the current tablesort.
   */
  protected function getEntityIds(): array {
    $query = $this->getStorage()->getQuery()
      ->accessCheck(TRUE);

    // Get the table sort header to determine sorting.
    $header = $this->buildHeader();

    // Apply table sorting if available, otherwise use default sort.
    if (!empty($header)) {
      $request = $this->requestStack->getCurrentRequest();
      $order = $request->query->get("order");
      $sort = $request->query->get("sort", "desc");

      // Determine the field to sort by.
      $sortField = "changed";
      if ($order !== NULL) {
        foreach ($header as $key => $value) {
          if (is_array($value) && isset($value["data"])) {
            $headerLabel = $value["data"];
            if ($headerLabel instanceof TranslatableMarkup) {
              $headerLabel = $headerLabel->__toString();
            }
            if ($headerLabel === $order && isset($value["field"])) {
              $sortField = $value["field"];
              break;
            }
          }
        }
      }

      // Apply the sort to supported fields.
      $sortableFields = $this->getSortableFields();
      if (in_array($sortField, $sortableFields, TRUE)) {
        $sortDirection = in_array(strtoupper($sort), ['ASC', 'DESC'], TRUE) ? strtoupper($sort) : 'DESC';
        $query->sort($sortField, $sortDirection);
      }
      else {
        // Default sort by changed DESC (newest first).
        $query->sort("changed", "DESC");
      }
    }
    else {
      // Default sort by changed DESC (newest first).
      $query->sort("changed", "DESC");
    }

    // Add pager if limit is set.
    if ($this->limit) {
      $query->pager($this->limit);
    }

    return $query->execute();
  }

  /**
   * Adds tablesort support to the rendered table.
   *
   * @return array<string, mixed>
   *   The render array for the list with tablesort headers and CSS class.
   */
  public function render(): array {
    $build = parent::render();

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

    return $build;
  }

  /**
   * Builds an execution status pill render array for table cell display.
   *
   * Renders a colored pill component using the flowdrop_ui_components:pill
   * SDC. Supports all standard execution statuses with appropriate color
   * variants.
   *
   * @param string $status
   *   The execution status (pending, running, completed, failed, cancelled,
   *   paused, skipped).
   *
   * @return array<string, mixed>
   *   A table cell render array containing the status pill component.
   */
  protected function buildExecutionStatusPill(string $status): array {
    // Map status to variant and label.
    $statusMap = [
      "pending" => ["label" => "Pending", "variant" => "neutral"],
      "running" => ["label" => "Running", "variant" => "info"],
      "completed" => ["label" => "Completed", "variant" => "success"],
      "failed" => ["label" => "Failed", "variant" => "error"],
      "cancelled" => ["label" => "Cancelled", "variant" => "warning"],
      "paused" => ["label" => "Paused", "variant" => "warning"],
      "skipped" => ["label" => "Skipped", "variant" => "neutral"],
    ];

    $config = $statusMap[$status] ?? ["label" => ucfirst($status), "variant" => "neutral"];

    // Map status label to translatable string.
    $label = match ($config["label"]) {
      "Pending" => $this->t("Pending"),
      "Running" => $this->t("Running"),
      "Completed" => $this->t("Completed"),
      "Failed" => $this->t("Failed"),
      "Cancelled" => $this->t("Cancelled"),
      "Paused" => $this->t("Paused"),
      "Skipped" => $this->t("Skipped"),
      default => $this->t("@status", ["@status" => $config["label"]]),
    };

    return [
      "data" => [
        "#type" => "component",
        "#component" => "flowdrop_ui_components:pill",
        "#props" => [
          "label" => (string) $label,
          "variant" => $config["variant"],
          "style" => "soft",
          "size" => "sm",
        ],
      ],
    ];
  }

  /**
   * Builds a render array for an entity reference label with link.
   *
   * Handles three cases: empty reference (shows "None"), missing entity
   * (shows error indicator), and valid entity (shows linked label).
   *
   * @param string $entityId
   *   The referenced entity ID.
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   The entity storage to load the referenced entity from.
   * @param string $routeName
   *   The route name for the entity edit form link.
   * @param string $routeParameter
   *   The route parameter name for the entity ID.
   *
   * @return array<string, mixed>
   *   Render array for the entity reference label.
   */
  protected function buildEntityReferenceLabel(
    string $entityId,
    EntityStorageInterface $storage,
    string $routeName,
    string $routeParameter,
  ): array {
    if (empty($entityId)) {
      return [
        "data" => [
          "#markup" => "<em>" . $this->t("None") . "</em>",
        ],
      ];
    }

    $entity = $storage->load($entityId);

    if ($entity === NULL) {
      return [
        "data" => [
          "#markup" => sprintf(
            '<span class="color-error">%s (missing)</span>',
            Html::escape($entityId)
          ),
        ],
      ];
    }

    return [
      "data" => [
        "#type" => "link",
        "#title" => $entity->label(),
        "#url" => Url::fromRoute($routeName, [
          $routeParameter => $entityId,
        ]),
      ],
    ];
  }

}
