<?php

declare(strict_types=1);

namespace Drupal\farm_group;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\DependencyInjection\AutowireTrait;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\asset\Entity\AssetInterface;
use Drupal\farm_location\AssetLocation;
use Drupal\farm_location\AssetLocationInterface;
use Drupal\farm_location\LogLocationInterface;
use Drupal\farm_log\LogQueryFactoryInterface;
use Drupal\log\Entity\LogInterface;

/**
 * Asset location logic, accounting for group membership.
 */
class GroupAssetLocation extends AssetLocation implements AssetLocationInterface {

  use AutowireTrait;

  public function __construct(
    protected LogLocationInterface $logLocation,
    protected LogQueryFactoryInterface $logQueryFactory,
    protected EntityTypeManagerInterface $entityTypeManager,
    protected TimeInterface $time,
    protected Connection $database,
    protected GroupMembershipInterface $groupMembership,
  ) {
    parent::__construct($logLocation, $logQueryFactory, $entityTypeManager, $time, $database);
  }

  /**
   * {@inheritdoc}
   */
  public function getMovementLog(AssetInterface $asset, $timestamp = NULL): ?LogInterface {

    // Delegate to the parent service to get the latest movement log that
    // references this asset.
    $latest_movement = parent::getMovementLog($asset, $timestamp);

    // Remember the latest movement id and timestamp.
    $latest_id = 0;
    $latest_timestamp = 0;
    if (!empty($latest_movement)) {
      $latest_id = $latest_movement->id();
      $latest_timestamp = $latest_movement->get('timestamp')->value;
    }

    // Get the groups that this asset is assigned to.
    $groups = $this->groupMembership->getGroup($asset, $timestamp);

    // If there are groups, iterate through them.
    if (!empty($groups)) {
      foreach ($groups as $group) {

        // Get the latest movement log that references the group.
        $group_movement = parent::getMovementLog($group, $timestamp);

        // If the group doesn't have a movement, skip it.
        if (empty($group_movement)) {
          continue;
        }

        // If the group's movement is the latest, replace the latest movement.
        $group_movement_id = $group_movement->id();
        $group_movement_timestamp = $group_movement->get('timestamp')->value;
        if (($group_movement_timestamp > $latest_timestamp) || ($group_movement_timestamp == $latest_timestamp && $group_movement_id > $latest_id)) {
          $latest_movement = $group_movement;
          $latest_id = $group_movement_id;
          $latest_timestamp = $group_movement_timestamp;
        }
      }
    }

    // Return the latest movement log.
    return $latest_movement;
  }

  /**
   * {@inheritdoc}
   */
  public function getAssetsByLocation(array $locations, $timestamp = NULL): array {

    // First delegate to the parent function to get assets in the location.
    /** @var \Drupal\asset\Entity\AssetInterface[] $assets */
    $assets = parent::getAssetsByLocation($locations, $timestamp);

    // Recursively load all group members and add them to the list of assets.
    $groups = array_filter($assets, function (AssetInterface $asset) {
      return $asset->bundle() === 'group';
    });
    $assets += $this->groupMembership->getGroupMembers($groups, TRUE, $timestamp);

    // Get location ids.
    $location_ids = array_map(function (AssetInterface $location) {
      return $location->id();
    }, $locations);

    // It is possible for a group member asset to be in a different location
    // than the group, if it has a movement log that is more recent than the
    // group's. So iterate through all the assets and remove any that are not in
    // one of the specified locations. The asset may be in multiple locations
    // (including this one), so we only want to remove it if none of its
    // locations match.
    $assets = array_filter($assets, function (AssetInterface $asset) use ($location_ids, $timestamp) {

      // Get asset location ids.
      $asset_location_ids = array_map(function (AssetInterface $location) {
        return $location->id();
      }, $this->getLocation($asset, $timestamp));

      // Only include the asset if it is in one of the specified locations.
      return !empty(array_intersect($location_ids, $asset_location_ids));
    });

    // Return the assets.
    return $assets;
  }

}
