<?php

namespace Drupal\nodehive_entity_access;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeAccessControlHandler;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Access controller for entities with space-based access control.
 *
 * This class provides an EntityAccessControlHandler implementation that
 * controls access to entities based on space membership.
 */
class NodeHiveEntityAccessControlHandler extends NodeAccessControlHandler {

  /**
   * The NodeHive entity access manager.
   *
   * @var mixed
   */
  protected $nodehiveAccessManager;

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

  /**
   * {@inheritdoc}
   */
  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
    $instance = parent::createInstance($container, $entity_type);
    $instance->nodehiveAccessManager = $container->get('nodehive_entity_access.manager');
    $instance->nodehiveEntityTypeManager = $container->get('entity_type.manager');
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
    // Always allow users with 'bypass node access' or admin permission.
    if ($account->hasPermission('bypass node access') || $account->hasPermission('administer nodehive')) {
      return AccessResult::allowed()->setCacheMaxAge(0);
    }

    // For view operations, we need to be especially strict.
    if ($operation === 'view') {
      // Get spaces that contain this node via the node's nodehive_space field.
      if (!$entity->hasField('nodehive_space') || $entity->get('nodehive_space')->isEmpty()) {
        return AccessResult::forbidden("Entity is not in any space")->setCacheMaxAge(0);
      }

      $space_ids = array_column($entity->get('nodehive_space')->getValue(), 'target_id');

      // Find spaces that this user is a member of.
      $space_storage = $this->nodehiveEntityTypeManager->getStorage('nodehive_space');
      $user_space_query = $space_storage->getQuery()
        ->condition('id', $space_ids, 'IN')
        ->condition('editors', $account->id(), 'IN')
        ->accessCheck(FALSE);
      $user_space_ids = $user_space_query->execute();

      // Deny if user is not a member of any space containing this node.
      if (empty($user_space_ids)) {
        return AccessResult::forbidden("User is not a member of any space containing this entity")->setCacheMaxAge(0);
      }

      // Allow access if the user is in a space with this entity.
      return AccessResult::allowed()->setCacheMaxAge(0);
    }

    // For other operations, use the manager service.
    $has_access = $this->nodehiveAccessManager->userHasEntityAccess($entity->id(), $entity->getEntityTypeId(), $account, $operation);

    return $has_access ?
      AccessResult::allowed()->setCacheMaxAge(0) :
      AccessResult::forbidden()->setCacheMaxAge(0);
  }

  /**
   * {@inheritdoc}
   */
  public function access(EntityInterface $entity, $operation, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
    $account = $this->prepareUser($account);

    // For view operations, always use our strict checkAccess implementation.
    if ($operation === 'view') {
      $result = $this->checkAccess($entity, $operation, $account);
      return $return_as_object ? $result : $result->isAllowed();
    }

    // For other operations, use the parent implementation.
    return parent::access($entity, $operation, $account, $return_as_object);
  }

}
