<?php

namespace Drupal\languagewire_translation_provider\api\Libraries\Project;

use Drupal\languagewire_translation_provider\api\Libraries\Document\DocumentReferenceFiles;
use Drupal\languagewire_translation_provider\api\Libraries\Project\Exception\EmptyParameterException;
use Drupal\languagewire_translation_provider\api\Libraries\Project\Exception\InvalidDeadlineException;

/**
 * Base class for project specifications.
 */
abstract class ProjectSpecificationBase implements ProjectSpecificationBaseInterface {
  /**
   * Purchase order number.
   *
   * @var ?string
   */
  protected ?string $purchaseOrderNumber;
  /**
   * Project deadline.
   *
   * @var \DateTimeInterface
   */
  protected \DateTimeInterface $deadline;
  /**
   * Project title.
   *
   * @var string
   */
  protected string $title;
  /**
   * Project briefing.
   *
   * @var ?string
   */
  protected ?string $briefing;
  /**
   * Project correlation ID.
   *
   * @var string
   */
  protected string $correlationId;
  /**
   * Project reference file IDs.
   *
   * @var int[]
   */
  protected array $projectReferenceFileIds;
  /**
   * Document reference files.
   *
   * @var \Drupal\languagewire_translation_provider\api\Libraries\Document\DocumentReferenceFiles
   */
  protected DocumentReferenceFiles $documentReferenceFiles;

  /**
   * Constructs a new ProjectSpecificationBase object.
   *
   * @param string $correlationId
   *   Correlation ID.
   * @param string $title
   *   Project title.
   * @param \DateTimeInterface $deadline
   *   Project deadline.
   * @param string|null $briefing
   *   Project briefing.
   * @param string|null $purchaseOrderNumber
   *   Purchase order number.
   * @param int[] $projectReferenceFileIds
   *   Project reference file IDs.
   * @param \Drupal\languagewire_translation_provider\api\Libraries\Document\DocumentReferenceFiles|null $documentReferenceFiles
   *   Document reference files.
   *
   * @throws \Drupal\languagewire_translation_provider\api\Libraries\Project\Exception\EmptyParameterException
   *   If any of the required parameters are empty.
   * @throws \Drupal\languagewire_translation_provider\api\Libraries\Project\Exception\InvalidDeadlineException
   * @throws \Exception
   *   If deadline is not in the future or not in UTC.
   */
  public function __construct(
    string $correlationId,
    string $title,
    \DateTimeInterface $deadline,
    ?string $briefing,
    ?string $purchaseOrderNumber,
    array $projectReferenceFileIds = [],
    DocumentReferenceFiles $documentReferenceFiles = NULL
  ) {
    $this->purchaseOrderNumber = $purchaseOrderNumber;
    $this->deadline = $deadline;
    $this->title = $title;
    $this->briefing = $briefing;
    $this->correlationId = $correlationId;
    $this->projectReferenceFileIds = $projectReferenceFileIds;
    $this->documentReferenceFiles = $documentReferenceFiles ?? new DocumentReferenceFiles();

    $this->validateDeadline($deadline);
  }

  /**
   * Project briefing.
   *
   * @return string
   *   Project briefing.
   */
  public function getBriefing(): string {
    return $this->briefing;
  }

  /**
   * Purchase order number.
   *
   * @return string
   *   Purchase order number.
   */
  public function getPurchaseOrderNumber(): string {
    return $this->purchaseOrderNumber;
  }

  /**
   * Project deadline as a formatted string.
   *
   * The return type is string to maintain backwards compatibility.
   * Use getDeadlineObject() to receive a DateTimeInterface object.
   *
   * @return string
   *   Project deadline as a formatted string.
   */
  public function getDeadline(): string {
    return $this->getFormattedDeadline();
  }

  /**
   * Project deadline as a formatted string.
   *
   * @return string
   *   Project deadline as a formatted string.
   */
  public function getFormattedDeadline(): string {
    return $this->deadline->format(static::DEADLINE_FORMAT);
  }

  /**
   * Project deadline as a DateTimeInterface.
   *
   * @return \DateTimeInterface
   *   Project deadline as a DateTimeInterface.
   */
  public function getDeadlineObject(): \DateTimeInterface {
    return $this->deadline;
  }

  /**
   * Project title.
   *
   * @inheritDoc
   */
  public function getTitle(): string {
    return $this->title;
  }

  /**
   * Project correlation ID.
   *
   * @inheritDoc
   */
  public function getCorrelationId(): string {
    return $this->correlationId;
  }

  /**
   * Validates deadline.
   *
   * @param \DateTimeInterface $deadline
   *   Deadline to validate.
   *
   * @SuppressWarnings(PHPMD.StaticAccess)
   *
   * @throws \Exception.
   *   If deadline is not in the future or not in UTC.
   */
  protected function validateDeadline(\DateTimeInterface $deadline): void {
    $timezoneUtc = new \DateTimeZone('UTC');
    $nowUtc = new \DateTimeImmutable('now', $timezoneUtc);

    if ($deadline->getTimezone()->getName() != $timezoneUtc->getName()) {
      throw InvalidDeadlineException::fromNonUtcDate($deadline);
    }

    if ($nowUtc >= $deadline) {
      throw InvalidDeadlineException::fromNonFutureDate($deadline);
    }
  }

  /**
   * Validates that all required parameters are not empty.
   *
   * @throws \Drupal\languagewire_translation_provider\api\Libraries\Project\Exception\EmptyParameterException
   *   If any of the required parameters are empty.
   *
   * @SuppressWarnings(PHPMD.StaticAccess)
   */
  protected function validateRequiredParametersAreNotEmpty(): void {
    if (empty($this->getCorrelationId())) {
      throw EmptyParameterException::fromEmptyParameter("correlationId");
    }

    if (empty($this->getTitle())) {
      throw EmptyParameterException::fromEmptyParameter("correlationId");
    }
  }

  /**
   * Returns a list of unique reference files IDs.
   *
   * Returns a list of unique reference files IDs,
   * across both project reference files and document-specific
   * reference files.
   */
  protected function getUniqueReferenceFileIds(): array {
    return array_unique(array_merge(
          $this->projectReferenceFileIds,
          $this->documentReferenceFiles->getUniqueReferenceFileIds()
      ));
  }

}
