<?php

declare(strict_types=1);

namespace Drupal\languagewire_translation_provider\api\Libraries\Document;

use Drupal\languagewire_translation_provider\api\Libraries\Content\DehydrationInterface;
use Drupal\languagewire_translation_provider\api\Libraries\Project\Exception\EmptyParameterException;
use Drupal\languagewire_translation_provider\api\Libraries\Project\Exception\UnrecognizedLanguageException;
use Drupal\languagewire_translation_provider\api\Services\LanguageService;

/**
 * Document Translation Descriptions.
 *
 * DocumentTranslationDescriptions represents
 * a collection of descriptions of how to translate
 * a document.
 *
 * Each of these objects contains:
 * 1) An integer unique id of an uploaded
 * and validated document, sending for translation.
 * 2) An array of string language tags,
 * that indicate to which documents will be translated.
 * 3) An array of integer ids to associate
 * reference files with the document.
 */
final class DocumentTranslationDescriptions implements DehydrationInterface, \Countable {
  /**
   * An array of document translation descriptions.
   *
   * @var array
   */
  private array $descriptions;

  /**
   * Constructs a new DocumentTranslationDescriptions object.
   */
  public function __construct() {
    $this->descriptions = [];
  }

  /**
   * Adds a document translation description to collection.
   *
   * @param int $documentId
   *   The unique id of an uploaded and validated document,
   *   sending for translation.
   * @param string[] $targetLanguages
   *   An array of string language tags, that indicate to
   *   which documents will be translated.
   * @param int[] $referenceFileIds
   *   An array of integer ids to associate reference files
   *   with the document.
   */
  public function addDocumentTranslationDescription(int $documentId, array $targetLanguages, array $referenceFileIds = []): void {
    $targetLanguages = array_unique($targetLanguages);

    $this->validateLanguages($targetLanguages);
    $this->discardPreviousDocumentIfExists($documentId);

    $description = [
      "id" => $documentId,
      "targetLanguages" => $targetLanguages,
      "referenceFiles" => $referenceFileIds,
    ];

    $this->descriptions[] = $description;
  }

  /**
   * Returns an array of present document IDs.
   *
   * @return int[]
   *   An array of present document IDs.
   */
  public function getDocumentIds(): array {
    return array_column($this->descriptions, "id");
  }

  /**
   * Returns an array of present unique reference file IDs.
   *
   * @return int[]
   *   An array of present unique reference file IDs.
   */
  public function getReferenceFileIds(): array {
    $allReferenceFiles = array_column($this->descriptions, "referenceFiles");
    $flattened = array_merge(...(array_values($allReferenceFiles)));
    return array_unique($flattened);
  }

  /**
   * {@inheritDoc}
   */
  public function dehydrate(): array {
    return array_map(function (array $description): \stdClass {
        return (object) $description;
    }, $this->descriptions);
  }

  /**
   * Validates target languages.
   *
   * @param string[] $targetLanguages
   *   An array of string language tags,
   *   that indicate to which documents will
   *   be translated.
   *
   * @throws \Drupal\languagewire_translation_provider\api\Libraries\Project\Exception\EmptyParameterException
   *   If target languages are empty.
   * @throws \Drupal\languagewire_translation_provider\api\Libraries\Project\Exception\UnrecognizedLanguageException
   *   If target languages contain unrecognized languages.
   *
   * @SuppressWarnings(PHPMD.StaticAccess)
   */
  private function validateLanguages(array $targetLanguages): void {
    if (empty($targetLanguages)) {
      throw EmptyParameterException::fromEmptyParameter("target languages");
    }

    $unrecognizedLanguages = array_filter($targetLanguages, function (string $language) :bool {
        return !LanguageService::isSupported($language);
    });

    if (count($unrecognizedLanguages) > 0) {
      throw UnrecognizedLanguageException::fromUnrecognizedLanguages($unrecognizedLanguages);
    }
  }

  /**
   * Discard Previous Document If Exists .
   *
   * @param int $documentId
   *   The unique id of an uploaded and validated document,
   *   sending for translation.
   */
  private function discardPreviousDocumentIfExists(int $documentId): void {
    $this->descriptions = array_filter($this->descriptions, function (array $description) use ($documentId):bool {
       return $description["id"] != $documentId;
    });
  }

  /**
   * {@inheritDoc}
   */
  public function count(): int {
    return count($this->descriptions);
  }

  /**
   * Returns true if there are no translation.
   *
   * Returns true if there are no document translation descriptions present.
   *
   * @return bool
   *   True if there are no document translation descriptions present.
   */
  public function isEmpty(): bool {
    return $this->count() == 0;
  }

}
