<?php

namespace Drupal\localgov_elections\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Link;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides an electionmenu block.
 *
 * @Block(
 *   id = "localgov_elections_electionmenu",
 *   admin_label = @Translation("Election Menu"),
 *   category = @Translation("Custom")
 * )
 */
class ElectionmenuBlock extends BlockBase implements ContainerFactoryPluginInterface {

  /**
   * The current route match.
   *
   * @var \Drupal\Core\Routing\RouteMatchInterface
   */
  protected $routeMatch;

  /**
   * The election node.
   *
   * @var \Drupal\node\NodeInterface
   */
  protected NodeInterface $node;

  /**
   * The entity type manager service.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected EntityTypeManagerInterface $entityTypeManager;

  /**
   * Constructs a new ElectionmenuBlock instance.
   *
   * @param array $configuration
   *   The plugin configuration, i.e. an array with configuration values keyed
   *   by configuration option name. The special key 'context' may be used to
   *   initialize the defined contexts by setting it to an array of context
   *   values keyed by context names.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
   *   The current route match.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager service.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteMatchInterface $route_match, EntityTypeManagerInterface $entity_type_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->routeMatch = $route_match;
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('current_route_match'),
        $container->get('entity_type.manager')
    );
  }

  /**
   * Get links for the block.
   *
   * @return array
   *   An array of links.
   */
  private function getLinks(NodeInterface $node): array {
    $urls = [];
    $urls[] = [
      'attributes' => new Attribute(),
      'link' => Link::fromTextAndUrl($this->t('Results'), Url::fromRoute('entity.node.canonical', ['node' => $this->node->id()])),
    ];

    // Allow editors to hide the map.
    if ($node->hasField('localgov_election_display_map')) {
      $display_map = $node->get('localgov_election_display_map')?->value;
    }
    else {
      $display_map = FALSE;
    }
    // Check that there is geo data to display.
    $query = $this->entityTypeManager->getStorage('node')->getQuery();
    $results = $query->condition('type', 'localgov_area_vote')
      ->condition('type', 'localgov_area_vote')
      ->condition('localgov_election', $node->id())
      ->exists('localgov_election_boundary_data')
      ->accessCheck(FALSE)
      ->execute();
    // If map to be displayed and there is geo data show the link.
    if ($display_map == "1" && $results) {
      $urls[] = [
        'attributes' => new Attribute(),
        'link' => Link::fromTextAndUrl($this->t('Electoral map'), Url::fromRoute('view.localgov_election_electoral_map.page_map', ['node' => $this->node->id()])),
      ];
    }

    // Should next 2 links should be displayed i.e. there are finalised votes.
    $query = $this->entityTypeManager->getStorage('node')->getQuery();
    $results = $query->condition('type', 'localgov_area_vote')
      ->condition('type', 'localgov_area_vote')
      ->condition('localgov_election', $node->id())
      ->condition('localgov_election_votes_final', TRUE)
      ->accessCheck(FALSE)
      ->execute();
    if ($results) {
      $urls[] = [
        'attributes' => new Attribute(),
        'link' => Link::fromTextAndUrl($this->t('Results timeline'), Url::fromRoute('view.localgov_election_results_timeline.page_timeline', ['node' => $this->node->id()])),
      ];

      // Hide share of the vote for national parliamentary elections as per:
      // https://github.com/localgovdrupal/localgov_elections/issues/57
      if ($node->get('localgov_election_type')?->value !== "NationalParliamentary") {
        $urls[] = [
          'attributes' => new Attribute(),
          'link' => Link::fromTextAndUrl($this->t('Share of the vote'), Url::fromRoute('view.localgov_election_results_vote.page_vote_share', ['node' => $this->node->id()])),
        ];
      }
    }

    // Work out if next link should be displayed i.e. there are PDFs uploaded.
    $query = $this->entityTypeManager->getStorage('node')->getQuery();
    $results = $query->condition('type', 'localgov_area_vote')
      ->condition('type', 'localgov_area_vote')
      ->condition('localgov_election', $node->id())
      ->exists('localgov_election_cand_file')
      ->accessCheck(FALSE)
      ->execute();
    if ($results) {
      $urls[] = [
        'attributes' => new Attribute(),
        'link' => Link::fromTextAndUrl($this->t('Electoral candidates'), Url::fromRoute('view.localgov_electoral_candidates.page_candidates', ['node' => $this->node->id()])),
      ];
    }

    // Show Election notices link if the election has notices.
    if ($node->hasField('localgov_election_notices') && !$node->get('localgov_election_notices')->isEmpty()) {
      $urls[] = [
        'attributes' => new Attribute(),
        'link' => Link::fromTextAndUrl($this->t('Election notices'), Url::fromUri('internal:/node/' . $this->node->id() . '/notices')),
      ];
    }

    return $urls;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts(): array {
    return Cache::mergeContexts(parent::getCacheContexts(), ['route']);
  }

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

    $node = $this->getElectionNode();
    if ($node instanceof NodeInterface) {
      $tags = Cache::mergeTags($tags, $node->getCacheTags());
      // Area vote nodes determine which links are shown (map, timeline,
      // share of vote, candidates). Invalidate when any area vote changes.
      $tags = Cache::mergeTags($tags, ['node_list:localgov_area_vote']);
    }

    return $tags;
  }

  /**
   * Resolve the election node from the current route.
   *
   * @return \Drupal\node\NodeInterface|null
   *   The election node, or NULL if the current route is not an election page.
   */
  protected function getElectionNode(): ?NodeInterface {
    $node = $this->routeMatch->getParameter('node');

    if (!($node instanceof NodeInterface)) {
      $node = $this->entityTypeManager->getStorage('node')->load((int) $node);
    }
    if (!($node instanceof NodeInterface)) {
      return NULL;
    }

    if ($node->bundle() === 'localgov_area_vote') {
      $election_field = $node->localgov_election?->first();
      if ($election_field) {
        $node_ref = $election_field->getValue()['target_id'];
        $node = $this->entityTypeManager->getStorage('node')->load((int) $node_ref);
      }
      else {
        return NULL;
      }
    }

    if ($node instanceof NodeInterface && $node->bundle() === 'localgov_election') {
      return $node;
    }

    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function build(): array {
    $node = $this->getElectionNode();
    if (!$node) {
      return [];
    }

    $this->node = $node;

    return [
      '#theme' => 'election_menu',
      '#attached' => ['library' => ['localgov_elections/election_menu']],
      '#links' => $this->getLinks($node),
    ];
  }

}
