<?php

namespace Drupal\commerce_order;

use Drupal\Core\Entity\EntityTypeBundleInfo;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\profile\Entity\ProfileInterface;
use Drupal\user\UserInterface;

class AddressBook implements AddressBookInterface {

  /**
   * Constructs a new AddressBook object.
   *
   * @param \Drupal\Core\Entity\EntityTypeBundleInfo $entityTypeBundleInfo
   *   The entity type bundle info.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   */
  public function __construct(
    protected EntityTypeBundleInfo $entityTypeBundleInfo,
    protected EntityTypeManagerInterface $entityTypeManager,
  ) {
  }

  /**
   * {@inheritdoc}
   */
  public function allowsMultiple($profile_type_id) {
    $bundle_info = $this->entityTypeBundleInfo->getBundleInfo('profile');

    return !empty($bundle_info[$profile_type_id]['multiple']);
  }

  /**
   * {@inheritdoc}
   */
  public function hasUi() {
    $profile_types = $this->loadTypes();
    if (empty($profile_types)) {
      // No profile types available.
      return FALSE;
    }
    elseif (count($profile_types) === 1) {
      $profile_type = reset($profile_types);
      if (!$profile_type->allowsMultiple()) {
        // There is only one profile type, and it only allows one profile per
        // customer. No point in presenting an "address book" for one address.
        return FALSE;
      }
    }

    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function loadTypes() {
    $profile_type_storage = $this->entityTypeManager->getStorage('profile_type');
    /** @var \Drupal\profile\Entity\ProfileTypeInterface[] $profile_types */
    $profile_types = $profile_type_storage->loadByProperties([
      'third_party_settings.commerce_order.customer_profile_type' => TRUE,
    ]);
    return $profile_types;
  }

  /**
   * {@inheritdoc}
   */
  public function loadAll(UserInterface $customer, $profile_type_id, array $available_countries = []) {
    if ($customer->isAnonymous()) {
      return [];
    }

    /** @var \Drupal\profile\ProfileStorageInterface $profile_storage */
    $profile_storage = $this->entityTypeManager->getStorage('profile');
    $profiles = $profile_storage->loadMultipleByUser($customer, $profile_type_id, TRUE);
    // Filter out profiles with unavailable countries.
    foreach ($profiles as $profile_id => $profile) {
      if (!$this->isAvailable($profile, $available_countries)) {
        unset($profiles[$profile_id]);
      }
    }

    return $profiles;
  }

  /**
   * {@inheritdoc}
   */
  public function load(UserInterface $customer, $profile_type_id, array $available_countries = []) {
    if ($customer->isAnonymous()) {
      return NULL;
    }

    /** @var \Drupal\profile\ProfileStorageInterface $profile_storage */
    $profile_storage = $this->entityTypeManager->getStorage('profile');
    $profile = $profile_storage->loadByUser($customer, $profile_type_id);
    if ($profile && !$this->isAvailable($profile, $available_countries)) {
      $profile = NULL;
    }

    return $profile;
  }

  /**
   * {@inheritdoc}
   */
  public function needsCopy(ProfileInterface $profile) {
    return (bool) $profile->getData('copy_to_address_book', FALSE);
  }

  /**
   * {@inheritdoc}
   */
  public function copy(ProfileInterface $profile, UserInterface $customer) {
    if ($customer->isAnonymous()) {
      return;
    }

    // Check if there is an existing address book profile to update.
    // This can happen in two scenarios:
    // 1) The profile was already copied to the address book once.
    // 2) The customer is only allowed to have a single address book profile.
    $address_book_profile = NULL;
    $address_book_profile_id = $profile->getData('address_book_profile_id');
    if ($address_book_profile_id) {
      /** @var \Drupal\profile\ProfileStorageInterface $profile_storage */
      $profile_storage = $this->entityTypeManager->getStorage('profile');
      /** @var \Drupal\profile\Entity\ProfileInterface $address_book_profile */
      $address_book_profile = $profile_storage->load($address_book_profile_id);
    }
    if (!$address_book_profile && !$this->allowsMultiple($profile->bundle())) {
      $address_book_profile = $this->load($customer, $profile->bundle());
    }

    if ($address_book_profile) {
      $address_book_profile->populateFromProfile($profile);
      $address_book_profile->save();
    }
    else {
      $address_book_profile = $profile->createDuplicate();
      $address_book_profile->setOwnerId($customer->id());
      $address_book_profile->unsetData('copy_to_address_book');
      $address_book_profile->save();
    }

    $profile->unsetData('copy_to_address_book');
    $profile->setData('address_book_profile_id', $address_book_profile->id());
    $profile->save();
  }

  /**
   * Checks if the given profile is available.
   *
   * If the list of available countries is restricted, the profile address
   * is checked against it.
   *
   * @param \Drupal\profile\Entity\ProfileInterface $profile
   *   The profile.
   * @param array $available_countries
   *   List of country codes. If empty, all countries will be available.
   *
   * @return bool
   *   TRUE if the profile is available, FALSE otherwise.
   */
  protected function isAvailable(ProfileInterface $profile, array $available_countries) {
    if (empty($available_countries)) {
      return TRUE;
    }
    /** @var \Drupal\address\Plugin\Field\FieldType\AddressItem $address */
    $address = $profile->get('address')->first();
    $country_code = $address ? $address->getCountryCode() : 'ZZ';

    return in_array($country_code, $available_countries);
  }

}
