<?php

declare(strict_types=1);

namespace Drupal\languagewire_translation_provider\api\Libraries\Document;

use Drupal\languagewire_translation_provider\api\Libraries\Content\ContentInterface;
use Drupal\languagewire_translation_provider\api\Services\LanguageService;

/**
 * Document value object base class.
 *
 * It represents a LanguageWire document.
 * A document is a placeholder for content that a client wants to translate or
 * wants to retrieve or store.
 *
 * Apart from storing content, `Document` can store additional,
 * yet optional, data. A document can be
 * equipped with a comment describing it. It can also have metadata.
 * {@link Metadata} is a set of key-value
 * pairs that are completely arbitrary. Both comments and metadata
 * are never translated or manipulated in any way.
 * A translated document will contain the same comment and metadata
 * as its source. Use it to your advantage.
 *
 * Apart from the three mentioned attributes a document has a unique ID.
 * This ID is generated by the LanguageWire
 * server, the client must not create IDs.
 * See {@link DocumentService} or
 * {@link UploadService} for more information.
 *
 * Example usage:
 * ```
 * $universalDocument = UniversalDocumentFactory::create(['Hello World']);
 * $content = new UdfContent($universalDocument);
 * $document = new DraftDocument($content, Language::DANISH, 'Document title');
 *
 * // To get the document ID it must be uploaded to the LanguageWire
 * // server first
 * $uploadedDocument = $documentService->upload($document);
 * echo $uploadedDocument->getDocumentId();
 * ```
 */
abstract class DocumentBase implements DocumentInterface {
  /**
   * Maximum allowed document title length in characters.
   */
  protected const MAX_DOCUMENT_TITLE_LENGTH = 100;

  /**
   * Document title.
   *
   * @var string
   */
  protected string $title;
  /**
   * Document language.
   *
   * @var string|null
   */
  protected ?string $language;
  /**
   * Document content.
   *
   * @var \Drupal\languagewire_translation_provider\api\Libraries\Content\ContentInterface|null
   */
  protected ?ContentInterface $content;

  /**
   * Constructs a new Document object.
   *
   * @param \Drupal\languagewire_translation_provider\api\Libraries\Content\ContentInterface|null $content
   *   Document content.
   * @param string|null $language
   *   Document language.
   * @param string $title
   *   Document title.
   *
   * @throws InvalidLanguageException
   *   Thrown when language is not supported by LanguageWire.
   * @throws DocumentTitleTooLongException
   *   Thrown when title length is too long.
   *
   * @see Language
   */
  public function __construct(
    ?ContentInterface $content = NULL,
    ?string $language = NULL,
    string $title = ''
  ) {
    $this->validateLanguage($language);
    $this->validateTitle($title);

    $this->content = $content;
    $this->title = $title;
    $this->language = $language;
  }

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

  /**
   * Returns document source language.
   *
   * @return string|null
   *   Document source language.
   */
  public function getLanguage(): ?string {
    return $this->language;
  }

  /**
   * Returns content associated with the document.
   *
   * @return \Drupal\languagewire_translation_provider\api\Libraries\Content\ContentInterface|null
   *   Document content.
   */
  public function getContent(): ?ContentInterface {
    return $this->content;
  }

  /**
   * Validates document language.
   *
   * Language code must come from a predefined list of
   * languages supported by LanguageWire.
   *
   * @param string|null $language
   *   Document language.
   *
   * @see Language
   */
  protected function validateLanguage(?string $language): void {
    if ($language !== NULL && !LanguageService::isSupported($language)) {
      throw new InvalidLanguageException(sprintf('Language %s was not recognized', $language));
    }
  }

  /**
   * Validates document title.
   *
   * Checks whether title length is correct.
   *
   * @param string $title
   *   Document title.
   *
   * @throws DocumentTitleTooLongException.
   *   Thrown when title length is too long.
   */
  protected function validateTitle(string $title): void {
    if (strlen($title) > static::MAX_DOCUMENT_TITLE_LENGTH) {
      throw new DocumentTitleTooLongException(
        sprintf(
          'Title maximal length is %s but %d characters long was provided',
          static::MAX_DOCUMENT_TITLE_LENGTH,
          strlen($title)
        )
      );
    }
  }

}
