<?php

namespace Google\Web_Stories_Dependencies\AmpProject;

use Google\Web_Stories_Dependencies\AmpProject\Exception\FailedToParseUrl;
/**
 * Helper class to work with URLs.
 *
 * @property-read string|null $scheme
 * @property-read string|null $host
 * @property-read string|null $port
 * @property-read string|null $user
 * @property-read string|null $pass
 * @property-read string|null $path
 * @property-read string|null $query
 * @property-read string|null $fragment
 *
 * @package ampproject/amp-toolbox
 */
final class Url
{
    const SCHEME = 'scheme';
    const HOST = 'host';
    const PORT = 'port';
    const USER = 'user';
    const PASS = 'pass';
    const PATH = 'path';
    const QUERY = 'query';
    const FRAGMENT = 'fragment';
    /**
     * Regular expression pattern used to collapse the current path ('.').
     *
     * @var string
     */
    const COLLAPSE_CURRENT_PATHS_REGEX_PATTERN = '#/\\./#';
    /**
     * Regular expression pattern used to collapse a relative path ('..').
     *
     * @var string
     */
    const COLLAPSE_RELATIVE_PATHS_REGEX_PATTERN = '#(?<=/)(?!\\.\\./)[^/]+/\\.\\./#';
    /**
     * Error message to use when the __get() is triggered for an unknown property.
     *
     * @var string
     */
    const PROPERTY_GETTER_ERROR_MESSAGE = 'Undefined property: AmpProject\\Url::';
    /**
     * Scheme.
     *
     * @var string|null
     */
    private $scheme;
    /**
     * Host.
     *
     * @var string|null
     */
    private $host;
    /**
     * Port.
     *
     * @var string|null
     */
    private $port;
    /**
     * User.
     *
     * @var string|null
     */
    private $user;
    /**
     * Password.
     *
     * @var string|null
     */
    private $pass;
    /**
     * Query string.
     *
     * @var string|null
     */
    private $query;
    /**
     * Path.
     *
     * @var string|null
     */
    private $path;
    /**
     * Fragment.
     *
     * @var string|null
     */
    private $fragment;
    /**
     * Default URL parts to use when constructing an absolute URL out of a relative one.
     *
     * @var array{
     *    scheme: string,
     *    host: string,
     *    port: null,
     *    user: null,
     *    pass: null,
     *    path: null,
     *    query: null,
     *    fragment: null
     * }
     */
    const URL_DEFAULT_PARTS = [self::SCHEME => 'https', self::HOST => 'example.com', self::PORT => null, self::USER => null, self::PASS => null, self::PATH => null, self::QUERY => null, self::FRAGMENT => null];
    /**
     * Url constructor.
     *
     * @param string|null $url     URL.
     * @param Url|null    $baseUrl Base URL.
     * @throws FailedToParseUrl Exception when the URL or Base URL is malformed.
     */
    public function __construct($url = null, Url $baseUrl = null)
    {
        $parsedUrl = [];
        if ($url !== null) {
            $parsedUrl = \parse_url($url);
            if (\false === $parsedUrl) {
                throw FailedToParseUrl::forUrl($url);
            }
        }
        if ($baseUrl) {
            if (isset($parsedUrl[self::PATH]) && 0 !== \strpos($parsedUrl[self::PATH], '/')) {
                $root = \rtrim($baseUrl->path, '/');
                $parsedUrl[self::PATH] = $this->unrelativizePath("{$root}/{$parsedUrl[self::PATH]}");
            }
            $parsedUrl = \array_merge($baseUrl->toArray(), $parsedUrl);
        }
        if ($parsedUrl) {
            foreach ($parsedUrl as $key => $value) {
                if (\property_exists($this, $key)) {
                    $this->{$key} = $value;
                }
            }
        }
    }
    /**
     * Eliminate relative segments (../ and ./) from a path.
     *
     * @param string $path Path with relative segments. This is not a URL, so no host and no query string.
     * @return string Unrelativized path.
     */
    private function unrelativizePath($path)
    {
        // Eliminate current directory relative paths, like <foo/./bar/./baz.css> => <foo/bar/baz.css>.
        do {
            $path = \preg_replace(self::COLLAPSE_CURRENT_PATHS_REGEX_PATTERN, '/', $path, -1, $count);
        } while (0 !== $count);
        // Collapse relative paths, like <foo/bar/../../baz.css> => <baz.css>.
        do {
            $path = \preg_replace(self::COLLAPSE_RELATIVE_PATHS_REGEX_PATTERN, '', $path, 1, $count);
        } while (0 !== $count);
        return $path;
    }
    /**
     * Check whether the URL is a valid image source URL.
     *
     * @return bool Whether the src string is a valid image source URL.
     */
    public function isValidNonDataUrl()
    {
        // Bail early on 'data:' assets.
        if ($this->scheme === 'data') {
            return \false;
        }
        // @TODO: This probably needs additional logic.
        $parts = \array_merge(self::URL_DEFAULT_PARTS, $this->toArray(\true));
        return (bool) \filter_var(self::toString($parts), \FILTER_VALIDATE_URL);
    }
    /**
     * Get the URL parts as an associative array.
     *
     * @param bool $sparse Whether to only include parts with non-empty values.
     *
     * @return array Associative array with URL parts.
     */
    public function toArray($sparse = \false)
    {
        $array = [self::SCHEME => $this->scheme, self::HOST => $this->host, self::PORT => $this->port, self::USER => $this->user, self::PASS => $this->pass, self::PATH => $this->path, self::QUERY => $this->query, self::FRAGMENT => $this->fragment];
        return $sparse ? \array_filter($array) : $array;
    }
    /**
     * Serialize to string.
     *
     * @return string URL.
     */
    public function __toString()
    {
        return self::toString($this->toArray());
    }
    /**
     * Return a provided URL parsed into parts as an assembled string.
     *
     * @param array $parts Parts of the URL to assemble.
     * @return string Assembled URL string.
     */
    public static function toString($parts)
    {
        $url = !empty($parts[self::SCHEME]) ? "{$parts[self::SCHEME]}://" : '//';
        if (!empty($parts[self::USER])) {
            $url .= "{$parts[self::USER]}";
            if (!empty($parts[self::PASS])) {
                $url .= ":{$parts[self::PASS]}";
            }
            $url .= '@';
        }
        $url .= !empty($parts[self::HOST]) ? "{$parts[self::HOST]}" : 'localhost';
        if (!empty($parts[self::PORT])) {
            $url .= ":{$parts[self::PORT]}";
        }
        $url .= !empty($parts[self::PATH]) ? "{$parts[self::PATH]}" : '/';
        if (!empty($parts[self::QUERY])) {
            $url .= "?{$parts[self::QUERY]}";
        }
        if (!empty($parts[self::FRAGMENT])) {
            $url .= "#{$parts[self::FRAGMENT]}";
        }
        return $url;
    }
    /**
     * Magic getter to return the individual parts.
     *
     * @param string $name Name of the part to return.
     * @return string|null Part string or null if it was not found during parsing.
     */
    public function __get($name)
    {
        if (\array_key_exists($name, self::URL_DEFAULT_PARTS)) {
            return $this->{$name};
        }
        // Mimic regular PHP behavior for missing notices.
        \trigger_error(self::PROPERTY_GETTER_ERROR_MESSAGE . $name, \E_USER_NOTICE);
        return null;
    }
}
