<?php

declare(strict_types=1);

namespace Drupal\flowdrop_session;

use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityListBuilder;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Link;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\flowdrop\ListBuilder\FlowDropListBuilderTrait;
use Drupal\flowdrop_session\Entity\FlowDropSessionInterface;
use Drupal\flowdrop_session\Form\SessionFilterForm;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides a list controller for session entities.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
final class FlowDropSessionListBuilder extends EntityListBuilder {

  use FlowDropListBuilderTrait;

  /**
   * The date formatter service.
   *
   * @var \Drupal\Core\Datetime\DateFormatterInterface
   */
  protected DateFormatterInterface $dateFormatter;

  /**
   * The workflow entity storage.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected EntityStorageInterface $workflowStorage;

  /**
   * The form builder.
   *
   * @var \Drupal\Core\Form\FormBuilderInterface
   */
  protected FormBuilderInterface $formBuilder;

  /**
   * {@inheritdoc}
   */
  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type): static {
    $entity_type_manager = $container->get('entity_type.manager');
    $instance = new static(
      $entity_type,
      $entity_type_manager->getStorage($entity_type->id()),
    );
    $instance->dateFormatter = $container->get('date.formatter');
    $instance->workflowStorage = $entity_type_manager->getStorage('flowdrop_workflow');
    $instance->requestStack = $container->get('request_stack');
    $instance->formBuilder = $container->get('form_builder');
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  protected function getSortableFields(): array {
    return ['name', 'status', 'workflow_id', 'created', 'changed'];
  }

  /**
   * {@inheritdoc}
   *
   * @return array<string, array{data: \Drupal\Core\StringTranslation\TranslatableMarkup, specifier?: string}>
   *   An array of header columns.
   */
  public function buildHeader(): array {
    $header['name'] = [
      'data' => $this->t('Name'),
      'field' => 'name',
      'specifier' => 'name',
    ];
    $header['workflow'] = [
      'data' => $this->t('Workflow'),
      'field' => 'workflow_id',
      'specifier' => 'workflow_id',
    ];
    $header['status'] = [
      'data' => $this->t('Status'),
      'field' => 'status',
      'specifier' => 'status',
    ];
    $header['owner'] = [
      'data' => $this->t('Owner'),
    ];
    $header['created'] = [
      'data' => $this->t('Created'),
      'field' => 'created',
      'specifier' => 'created',
    ];
    $header['changed'] = [
      'data' => $this->t('Updated'),
      'field' => 'changed',
      'specifier' => 'changed',
      'sort' => 'desc',
    ];
    return $header + parent::buildHeader();
  }

  /**
   * {@inheritdoc}
   *
   * @return array<string, array{data?: mixed}|mixed>
   *   A renderable array representing the row.
   */
  public function buildRow(EntityInterface $entity): array {
    if (!$entity instanceof FlowDropSessionInterface) {
      return parent::buildRow($entity);
    }

    $workflow = $entity->getWorkflow();
    $owner = $entity->getOwner();

    // The session name is the primary link: it opens the session in the
    // playground. The label carries the entity ID (e.g. "Calculator User
    // #523") so the row is unambiguous against the ?session= URL. Fall back to
    // plain text when the workflow is gone or the user can't open the
    // playground.
    $label = $entity->getDisplayLabel();
    $playgroundUrl = $this->playgroundUrl($entity);
    $row['name'] = $playgroundUrl !== NULL
      ? ['data' => Link::fromTextAndUrl($label, $playgroundUrl)->toRenderable()]
      : $label;
    $row['workflow'] = $workflow !== NULL ? $workflow->label() : $this->t('Unknown');
    $row['status'] = $entity->getStatus();
    $row['owner'] = $owner !== NULL ? $owner->getDisplayName() : $this->t('Anonymous');
    $row['created'] = $this->dateFormatter->format($entity->get('created')->value, 'short');
    $row['changed'] = $this->dateFormatter->format($entity->getChangedTime(), 'short');

    return $row + parent::buildRow($entity);
  }

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

    if ($entity instanceof FlowDropSessionInterface) {
      $url = $this->playgroundUrl($entity);
      if ($url !== NULL) {
        // Negative weight makes this the default (primary) operation.
        $operations['playground'] = [
          'title' => $this->t('Open in playground'),
          'weight' => -10,
          'url' => $url,
        ];
      }
    }

    return $operations;
  }

  /**
   * Builds the playground deep-link URL for a session.
   *
   * @param \Drupal\flowdrop_session\Entity\FlowDropSessionInterface $session
   *   The session entity.
   *
   * @return \Drupal\Core\Url|null
   *   The playground URL with the session pre-selected, or NULL when the
   *   workflow no longer exists or the user can't open the playground.
   */
  protected function playgroundUrl(FlowDropSessionInterface $session): ?Url {
    $workflowId = $session->getWorkflowId();
    if ($workflowId === '' || $this->workflowStorage->load($workflowId) === NULL) {
      return NULL;
    }

    $url = Url::fromRoute(
      'flowdrop_playground.workflow.session.page',
      [
        'flowdrop_workflow' => $workflowId,
        'flowdrop_session' => $session->id(),
      ],
    );

    return $url->access() ? $url : NULL;
  }

  /**
   * {@inheritdoc}
   */
  protected function getEntityIds(): array {
    $request = $this->requestStack->getCurrentRequest();
    $query = $this->getStorage()->getQuery()
      ->accessCheck(TRUE);

    $workflow = $request->query->get('workflow', '');
    $status = $request->query->get('status', '');
    $dateFrom = $request->query->get('date_from', '');
    $dateTo = $request->query->get('date_to', '');

    if (!empty($workflow)) {
      $query->condition('workflow_id', $workflow);
    }
    if (!empty($status)) {
      $query->condition('status', $status);
    }
    if (!empty($dateFrom)) {
      $query->condition('created', strtotime($dateFrom), '>=');
    }
    if (!empty($dateTo)) {
      $query->condition('created', strtotime($dateTo . ' 23:59:59'), '<=');
    }

    $header = $this->buildHeader();
    if (!empty($header)) {
      $order = $request->query->get('order');
      $sort = $request->query->get('sort', 'desc');

      $sortField = 'changed';
      if ($order !== NULL) {
        foreach ($header as $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;
            }
          }
        }
      }

      $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 {
        $query->sort('changed', 'DESC');
      }
    }
    else {
      $query->sort('changed', 'DESC');
    }

    if ($this->limit) {
      $query->pager($this->limit);
    }

    return $query->execute();
  }

  /**
   * {@inheritdoc}
   */
  public function render(): array {
    $build = parent::render();

    $build['table']['#header'] = $this->buildHeader();
    $build['table']['#attributes']['class'][] = 'sortable-table';

    $build['filter_form'] = $this->formBuilder->getForm(SessionFilterForm::class);
    $build['filter_form']['#weight'] = -10;

    return $build;
  }

}
