<?php

declare(strict_types=1);

namespace Drupal\data_stream_notification\Entity;

use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\Attribute\ConfigEntityType;
use Drupal\Core\Entity\EntityDeleteForm;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\data_stream_notification\DataStreamNotificationListBuilder;
use Drupal\data_stream_notification\DataStreamNotificationPluginCollection;
use Drupal\data_stream_notification\Form\DataStreamNotificationForm;
use Drupal\entity\EntityAccessControlHandler;
use Drupal\entity\EntityPermissionProvider;
use Drupal\entity\Routing\DefaultHtmlRouteProvider;

/**
 * Defines the DataStreamNotification entity.
 *
 * @ingroup farm
 */
#[ConfigEntityType(
  id: 'data_stream_notification',
  label: new TranslatableMarkup('Data stream notification'),
  label_collection: new TranslatableMarkup('Data stream notifications'),
  label_singular: new TranslatableMarkup('data stream notification'),
  label_plural: new TranslatableMarkup('data stream notifications'),
  entity_keys: [
    'id' => 'id',
    'label' => 'label',
    'status' => 'status',
  ],
  handlers: [
    'access' => EntityAccessControlHandler::class,
    'form' => [
      'add' => DataStreamNotificationForm::class,
      'edit' => DataStreamNotificationForm::class,
      'delete' => EntityDeleteForm::class,
    ],
    'list_builder' => DataStreamNotificationListBuilder::class,
    'permission_provider' => EntityPermissionProvider::class,
    'route_provider' => [
      'default' => DefaultHtmlRouteProvider::class,
    ],
  ],
  links: [
    'collection' => '/data-stream-notifications',
    'add-form' => '/data-stream-notifications/add',
    'edit-form' => '/data-stream-notifications/{data_stream_notification}/edit',
    'delete-form' => '/data-stream-notifications/{data_stream_notification}/delete',
    'enable' => '/data-stream-notifications/{data_stream_notification}/enable',
    'disable' => '/data-stream-notifications/{data_stream_notification}/disable',
  ],
  admin_permission: 'administer data stream notification',
  label_count: [
    'singular' => '@count data stream notification',
    'plural' => '@count data stream notifications',
  ],
  config_export: [
    'id',
    'label',
    'data_stream',
    'activation_threshold',
    'deactivation_threshold',
    'condition_operator',
    'condition',
    'delivery_interval',
    'delivery',
  ],
)]
class DataStreamNotification extends ConfigEntityBase implements DataStreamNotificationInterface {

  /**
   * The notification ID.
   *
   * @var string
   */
  protected string $id;

  /**
   * The notification label.
   *
   * @var string
   */
  protected string $label;

  /**
   * The data stream ID.
   *
   * @var int
   */
  protected int $data_stream;

  /**
   * The activation threshold.
   *
   * @var int
   */
  protected int $activation_threshold;

  /**
   * The deactivation threshold.
   *
   * @var int
   */
  protected int $deactivation_threshold;

  /**
   * The condition operator.
   *
   * @var string
   */
  protected string $condition_operator;

  /**
   * Stores all conditions of this notification.
   *
   * @var array
   */
  protected array $condition = [];

  /**
   * The condition plugin collection.
   *
   * @var \Drupal\data_stream_notification\DataStreamNotificationPluginCollection
   */
  protected $conditionCollection;

  /**
   * The delivery_interval.
   *
   * @var int
   */
  protected int $delivery_interval;

  /**
   * Stores all deliveries of this notification.
   *
   * @var array
   */
  protected array $delivery = [];

  /**
   * The delivery plugin collection.
   *
   * @var \Drupal\data_stream_notification\DataStreamNotificationPluginCollection
   */
  protected $deliveryCollection;

  /**
   * {@inheritdoc}
   */
  public function getPluginCollections() {
    foreach (['condition', 'delivery'] as $type) {
      $name = $type . 'Collection';
      if (empty($this->$name)) {
        $this->$name = new DataStreamNotificationPluginCollection(\Drupal::service("plugin.manager.data_stream_notification_$type"), $this->$type);
      }
    }
    return [
      'condition' => $this->conditionCollection,
      'delivery' => $this->deliveryCollection,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public static function postDelete(EntityStorageInterface $storage, array $entities) {
    parent::postDelete($storage, $entities);

    // Clear the notification state on deletion.
    /** @var \Drupal\data_stream_notification\Entity\DataStreamNotificationInterface $entity */
    foreach ($entities as $entity) {
      \Drupal::state()->delete($entity->getStateKey());
    }
  }

  /**
   * {@inheritdoc}
   */
  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
    parent::postSave($storage, $update);

    // Initialize the notification state on creation.
    if ($update === FALSE) {
      $this->resetState();
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getState(): array {
    return \Drupal::state()->get($this->getStateKey());
  }

  /**
   * {@inheritdoc}
   */
  public function isActive(): bool {
    return !empty($this->getState()['active']);
  }

  /**
   * {@inheritdoc}
   */
  public function resetState(bool $active = FALSE): array {

    // Initialize the notification state.
    $new_state = [
      'active' => $active,
      'activate_count' => $active ? 1 : 0,
      'deactivate_count' => 0,
    ];
    \Drupal::state()->set($this->getStateKey(), $new_state);
    return $new_state;
  }

  /**
   * {@inheritdoc}
   */
  public function incrementState(string $key): array {

    // Bail if an invalid key is provided.
    if (!in_array($key, ['activate_count', 'deactivate_count'])) {
      return [];
    }

    // Get the current state and save a copy as the new_state.
    $state = \Drupal::state();
    $notification_key = $this->getStateKey();
    $current_state = $state->get($notification_key);
    $new_state = $current_state;

    // Update the desired key in the new_state.
    $new_state[$key]++;

    // Check if the notification active state should be changed.
    $current_active_state = $current_state['active'];

    // Reset the other key to 0. This enforces that thresholds are consecutive.
    // Do not reset the activate_count while the notification is active.
    $other_key = $key === 'activate_count' ? 'deactivate_count' : 'activate_count';
    if (!($current_active_state && $other_key === 'activate_count')) {
      $new_state[$other_key] = 0;
    }

    // If currently active, check if the deactivation threshold was reached.
    if ($current_active_state && $new_state['deactivate_count'] >= $this->deactivation_threshold) {
      return $this->resetState(FALSE);
    }

    // If not currently active, check if the activation threshold was reached.
    elseif (!$current_active_state && $new_state['activate_count'] >= $this->activation_threshold) {
      return $this->resetState(TRUE);
    }

    // Otherwise just increment the key in the notification state.
    $state->set($notification_key, $new_state);
    return $new_state;
  }

  /**
   * {@inheritdoc}
   */
  public function getStateKey(): string {
    return 'data_stream_notification.state.' . $this->id();
  }

}
