<?php

namespace Drupal\media\OEmbed;

use Drupal\Component\Utility\UrlHelper;

/**
 * Value object for oEmbed provider endpoints.
 *
 * @internal
 *   This class is an internal part of the oEmbed system and should only be
 *   instantiated by instances of Drupal\media\OEmbed\Provider.
 */
class Endpoint {

  /**
   * The endpoint's URL.
   *
   * @var string
   */
  protected $url;

  /**
   * The provider this endpoint belongs to.
   *
   * @var \Drupal\media\OEmbed\Provider
   */
  protected $provider;

  /**
   * List of URL schemes supported by the provider.
   *
   * @var string[]
   */
  protected $schemes;

  /**
   * List of supported formats. Only 'json' and 'xml' are allowed.
   *
   * @var string[]
   *
   * @see https://oembed.com/#section2
   */
  protected $formats;

  /**
   * Whether the provider supports oEmbed discovery.
   *
   * @var bool
   */
  protected $supportsDiscovery;

  /**
   * Endpoint constructor.
   *
   * @param string $url
   *   The endpoint URL. May contain a @code '{format}' @endcode placeholder.
   * @param \Drupal\media\OEmbed\Provider $provider
   *   The provider this endpoint belongs to.
   * @param string[] $schemes
   *   List of URL schemes supported by the provider.
   * @param string[] $formats
   *   List of supported formats. Can be "json", "xml" or both.
   * @param bool $supports_discovery
   *   Whether the provider supports oEmbed discovery.
   *
   * @throws \InvalidArgumentException
   *   If the endpoint URL is empty.
   */
  public function __construct($url, Provider $provider, array $schemes = [], array $formats = [], $supports_discovery = FALSE) {
    $this->provider = $provider;
    $this->schemes = array_map('mb_strtolower', $schemes);

    $this->formats = $formats = array_map('mb_strtolower', $formats);
    // Assert that only the supported formats are present.
    assert(array_diff($formats, ['json', 'xml']) == []);

    // Use the first provided format to build the endpoint URL. If no formats
    // are provided, default to JSON.
    $this->url = str_replace('{format}', reset($this->formats) ?: 'json', $url);

    if (!UrlHelper::isValid($this->url, TRUE) || !UrlHelper::isExternal($this->url)) {
      throw new \InvalidArgumentException('oEmbed endpoint must have a valid external URL');
    }

    $this->supportsDiscovery = (bool) $supports_discovery;
  }

  /**
   * Returns the endpoint URL.
   *
   * The URL will be built with the first available format. If the endpoint
   * does not provide any formats, JSON will be used.
   *
   * @return string
   *   The endpoint URL.
   */
  public function getUrl() {
    return $this->url;
  }

  /**
   * Returns the provider this endpoint belongs to.
   *
   * @return \Drupal\media\OEmbed\Provider
   *   The provider object.
   */
  public function getProvider() {
    return $this->provider;
  }

  /**
   * Returns list of URL schemes supported by the provider.
   *
   * @return string[]
   *   List of schemes.
   */
  public function getSchemes() {
    return $this->schemes;
  }

  /**
   * Returns list of supported formats.
   *
   * @return string[]
   *   List of formats.
   */
  public function getFormats() {
    return $this->formats;
  }

  /**
   * Returns whether the provider supports oEmbed discovery.
   *
   * @return bool
   *   Returns TRUE if the provides discovery, otherwise FALSE.
   */
  public function supportsDiscovery() {
    return $this->supportsDiscovery;
  }

  /**
   * Tries to match a URL against the endpoint schemes.
   *
   * @param string $url
   *   Media item URL.
   *
   * @return bool
   *   TRUE if the URL matches against the endpoint schemes, otherwise FALSE.
   */
  public function matchUrl($url) {
    foreach ($this->getSchemes() as $scheme) {
      // Convert scheme into a valid regular expression.
      $regexp = str_replace(['.', '*'], ['\.', '.*'], $scheme);
      if (preg_match("|^$regexp$|", $url)) {
        return TRUE;
      }
    }
    return FALSE;
  }

  /**
   * Builds and returns the endpoint URL.
   *
   * @param string $url
   *   The canonical media URL.
   *
   * @return string
   *   URL of the oEmbed endpoint.
   */
  public function buildResourceUrl($url) {
    $query = ['url' => $url];
    return $this->getUrl() . '?' . UrlHelper::buildQuery($query);
  }

}
