"use strict";
/*!
 * Copyright 2017 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
const is = require("is");
const validate_1 = require("./validate");
const validate = validate_1.createValidator();
/*!
 * A regular expression to verify an absolute Resource Path in Firestore. It
 * extracts the project ID, the database name and the relative resource path
 * if available.
 *
 * @type {RegExp}
 */
const RESOURCE_PATH_RE = 
// Note: [\s\S] matches all characters including newlines.
/^projects\/([^/]*)\/databases\/([^/]*)(?:\/documents\/)?([\s\S]*)$/;
/*!
 * A regular expression to verify whether a field name can be passed to the
 * backend without escaping.
 *
 * @type {RegExp}
 */
const UNESCAPED_FIELD_NAME_RE = /^[_a-zA-Z][_a-zA-Z0-9]*$/;
/*!
 * A regular expression to verify field paths that are passed to the API as
 * strings. Field paths that do not match this expression have to be provided
 * as a [FieldPath]{@link FieldPath} object.
 *
 * @type {RegExp}
 */
const FIELD_PATH_RE = /^[^*~/[\]]+$/;
/**
 * An abstract class representing a Firestore path.
 *
 * Subclasses have to implement `split()` and `canonicalString()`.
 *
 * @private
 * @class
 */
class Path {
    /**
     * Creates a new Path with the given segments.
     *
     * @private
     * @hideconstructor
     * @param segments Sequence of parts of a path.
     */
    constructor(segments) {
        this.segments = segments;
    }
    /**
     * String representation as expected by the proto API.
     *
     * @private
     */
    get formattedName() {
        return this.canonicalString();
    }
    /**
     * Returns the number of segments of this field path.
     *
     * @private
     */
    get size() {
        return this.segments.length;
    }
    /**
     * Create a child path beneath the current level.
     *
     * @private
     * @param relativePath Relative path to append to the current path.
     * @returns The new path.
     */
    append(relativePath) {
        if (relativePath instanceof Path) {
            return this.construct(this.segments.concat(relativePath.segments));
        }
        return this.construct(this.segments.concat(this.split(relativePath)));
    }
    /**
     * Returns the path of the parent node.
     *
     * @private
     * @returns The new path or null if we are already at the root.
     */
    parent() {
        if (this.segments.length === 0) {
            return null;
        }
        return this.construct(this.segments.slice(0, this.segments.length - 1));
    }
    /**
     * Checks whether the current path is a prefix of the specified path.
     *
     * @private
     * @param other The path to check against.
     * @returns 'true' iff the current path is a prefix match with 'other'.
     */
    isPrefixOf(other) {
        if (other.segments.length < this.segments.length) {
            return false;
        }
        for (let i = 0; i < this.segments.length; i++) {
            if (this.segments[i] !== other.segments[i]) {
                return false;
            }
        }
        return true;
    }
    /**
     * Returns a string representation of this path.
     *
     * @private
     * @returns A string representing this path.
     */
    toString() {
        return this.formattedName;
    }
    /**
     * Compare the current path against another Path object.
     *
     * @private
     * @param other The path to compare to.
     * @returns -1 if current < other, 1 if current > other, 0 if equal
     */
    compareTo(other) {
        const len = Math.min(this.segments.length, other.segments.length);
        for (let i = 0; i < len; i++) {
            if (this.segments[i] < other.segments[i]) {
                return -1;
            }
            if (this.segments[i] > other.segments[i]) {
                return 1;
            }
        }
        if (this.segments.length < other.segments.length) {
            return -1;
        }
        if (this.segments.length > other.segments.length) {
            return 1;
        }
        return 0;
    }
    /**
     * Returns a copy of the underlying segments.
     *
     * @private
     * @returns A copy of the segments that make up this path.
     */
    toArray() {
        return this.segments.slice();
    }
    /**
     * Returns true if this `Path` is equal to the provided value.
     *
     * @private
     * @param other The value to compare against.
     * @return true if this `Path` is equal to the provided value.
     */
    isEqual(other) {
        return (this === other ||
            (other instanceof this.constructor && this.compareTo(other) === 0));
    }
}
/**
 * A slash-separated path for navigating resources (documents and collections)
 * within Firestore.
 *
 * @private
 * @class
 */
class ResourcePath extends Path {
    /**
     * Constructs a Firestore Resource Path.
     *
     * @private
     * @hideconstructor
     *
     * @param projectId The Firestore project id.
     * @param databaseId The Firestore database id.
     * @param segments Sequence of names of the parts of the path.
     */
    constructor(projectId, databaseId, ...segments) {
        super(segments);
        this.projectId = projectId;
        this.databaseId = databaseId;
    }
    /**
     * String representation of the path relative to the database root.
     *
     * @private
     * @type {string}
     */
    get relativeName() {
        return this.segments.join('/');
    }
    /**
     * Indicates whether this ResourcePath points to a document.
     *
     * @private
     * @type {boolean}
     */
    get isDocument() {
        return this.segments.length > 0 && this.segments.length % 2 === 0;
    }
    /**
     * Indicates whether this ResourcePath points to a collection.
     *
     * @private
     * @type {boolean}
     */
    get isCollection() {
        return this.segments.length % 2 === 1;
    }
    /**
     * The last component of the path.
     *
     * @private
     * @type {string|null}
     */
    get id() {
        if (this.segments.length > 0) {
            return this.segments[this.segments.length - 1];
        }
        return null;
    }
    /**
     * Returns true if the given string can be used as a relative or absolute
     * resource path.
     *
     * @private
     * @param {string} resourcePath The path to validate.
     * @throws if the string can't be used as a resource path.
     * @returns {boolean} 'true' when the path is valid.
     */
    static validateResourcePath(resourcePath) {
        if (!is.string(resourcePath) || resourcePath === '') {
            throw new Error(`Path must be a non-empty string.`);
        }
        if (resourcePath.indexOf('//') >= 0) {
            throw new Error('Paths must not contain //.');
        }
        return true;
    }
    /**
     * Creates a resource path from an absolute Firestore path.
     *
     * @private
     * @param {string} absolutePath A string representation of a Resource Path.
     * @returns {ResourcePath} The new ResourcePath.
     */
    static fromSlashSeparatedString(absolutePath) {
        const elements = RESOURCE_PATH_RE.exec(absolutePath);
        if (elements) {
            const project = elements[1];
            const database = elements[2];
            const path = elements[3];
            return new ResourcePath(project, database).append(path);
        }
        throw new Error(`Resource name '${absolutePath}' is not valid.`);
    }
    /**
     * Splits a string into path segments, using slashes as separators.
     *
     * @private
     * @override
     * @param {string} relativePath The path to split.
     * @returns {Array.<string>} - The split path segments.
     */
    split(relativePath) {
        // We may have an empty segment at the beginning or end if they had a
        // leading or trailing slash (which we allow).
        return relativePath.split('/').filter(segment => segment.length > 0);
    }
    /**
     * String representation of a ResourcePath as expected by the API.
     *
     * @private
     * @override
     * @returns {string} The representation as expected by the API.
     */
    canonicalString() {
        let components = [
            'projects',
            this.projectId,
            'databases',
            this.databaseId,
        ];
        if (this.segments.length > 0) {
            components = components.concat('documents', this.segments);
        }
        return components.join('/');
    }
    /**
     * Constructs a new instance of ResourcePath. We need this instead of using
     * the normal constructor because polymorphic 'this' doesn't work on static
     * methods.
     *
     * @private
     * @override
     * @param {Array.<string>} segments Sequence of names of the parts of the
     * path.
     * @returns {ResourcePath} The newly created ResourcePath.
     */
    construct(segments) {
        return new ResourcePath(this.projectId, this.databaseId, ...segments);
    }
    /**
     * Compare the current path against another ResourcePath object.
     *
     * @private
     * @override
     * @param {ResourcePath} other The path to compare to.
     * @returns {number} -1 if current < other, 1 if current > other, 0 if equal
     */
    compareTo(other) {
        // Ignore DocumentReference with {{projectId}} placeholders and assume that
        // the resolved IDs match the provided ResourcePath. We could alternatively
        // try to resolve the Project ID here, but this is asynchronous as it
        // requires Disk I/O.
        if (this.projectId !== '{{projectId}}' &&
            other.projectId !== '{{projectId}}') {
            if (this.projectId < other.projectId) {
                return -1;
            }
            if (this.projectId > other.projectId) {
                return 1;
            }
        }
        if (this.databaseId < other.databaseId) {
            return -1;
        }
        if (this.databaseId > other.databaseId) {
            return 1;
        }
        return super.compareTo(other);
    }
    /**
     * Converts this ResourcePath to the Firestore Proto representation.
     *
     * @private
     */
    toProto() {
        return {
            referenceValue: this.formattedName,
        };
    }
}
exports.ResourcePath = ResourcePath;
/**
 * A dot-separated path for navigating sub-objects within a document.
 *
 * @class
 */
class FieldPath extends Path {
    /**
     * Constructs a Firestore Field Path.
     *
     * @param {...string|string[]} segments Sequence of field names that form
     * this path.
     *
     * @example
     * let query = firestore.collection('col');
     * let fieldPath = new FieldPath('f.o.o', 'bar');
     *
     * query.where(fieldPath, '==', 42).get().then(snapshot => {
     *   snapshot.forEach(document => {
     *     console.log(`Document contains {'f.o.o' : {'bar' : 42}}`);
     *   });
     * });
     */
    constructor(...segments) {
        validate.minNumberOfArguments('FieldPath', arguments, 1);
        const elements = is.array(segments[0]) ?
            segments[0] :
            segments;
        for (let i = 0; i < elements.length; ++i) {
            validate.isString(i, elements[i]);
            if (elements[i].length === 0) {
                throw new Error(`Element at index ${i} should not be an empty string.`);
            }
        }
        super(elements);
    }
    /**
     * A special FieldPath value to refer to the ID of a document. It can be used
     * in queries to sort or filter by the document ID.
     *
     * @returns {FieldPath}
     */
    static documentId() {
        return FieldPath._DOCUMENT_ID;
    }
    /**
     * Returns true if the provided value can be used as a field path argument.
     *
     * @private
     * @param fieldPath The value to verify.
     * @throws if the string can't be used as a field path.
     * @returns 'true' when the path is valid.
     */
    static validateFieldPath(fieldPath) {
        if (!(fieldPath instanceof FieldPath)) {
            if (fieldPath === undefined) {
                throw new Error('Path cannot be omitted.');
            }
            if (is.object(fieldPath) &&
                fieldPath.constructor.name === 'FieldPath') {
                throw validate_1.customObjectError(fieldPath);
            }
            if (typeof fieldPath !== 'string') {
                throw new Error('Paths can only be specified as strings or via a FieldPath object.');
            }
            if (fieldPath.indexOf('..') >= 0) {
                throw new Error(`Paths must not contain ".." in them.`);
            }
            if (fieldPath.startsWith('.') || fieldPath.endsWith('.')) {
                throw new Error(`Paths must not start or end with ".".`);
            }
            if (!FIELD_PATH_RE.test(fieldPath)) {
                throw new Error(`Paths can't be empty and must not contain "*~/[]".`);
            }
        }
        return true;
    }
    /**
     * Turns a field path argument into a [FieldPath]{@link FieldPath}.
     * Supports FieldPaths as input (which are passed through) and dot-separated
     * strings.
     *
     * @private
     * @param {string|FieldPath} fieldPath The FieldPath to create.
     * @returns {FieldPath} A field path representation.
     */
    static fromArgument(fieldPath) {
        // validateFieldPath() is used in all public API entry points to validate
        // that fromArgument() is only called with a Field Path or a string.
        return fieldPath instanceof FieldPath ?
            fieldPath :
            new FieldPath(...fieldPath.split('.'));
    }
    /**
     * String representation of a FieldPath as expected by the API.
     *
     * @private
     * @override
     * @returns {string} The representation as expected by the API.
     */
    canonicalString() {
        return this.segments
            .map(str => {
            return UNESCAPED_FIELD_NAME_RE.test(str) ?
                str :
                '`' + str.replace('\\', '\\\\').replace('`', '\\`') + '`';
        })
            .join('.');
    }
    /**
     * Splits a string into path segments, using dots as separators.
     *
     * @private
     * @override
     * @param {string} fieldPath The path to split.
     * @returns {Array.<string>} - The split path segments.
     */
    split(fieldPath) {
        return fieldPath.split('.');
    }
    /**
     * Constructs a new instance of FieldPath. We need this instead of using
     * the normal constructor because polymorphic 'this' doesn't work on static
     * methods.
     *
     * @private
     * @override
     * @param {Array.<string>} segments Sequence of field names.
     * @returns {ResourcePath} The newly created FieldPath.
     */
    construct(segments) {
        return new FieldPath(...segments);
    }
    /**
     * Returns true if this `FieldPath` is equal to the provided value.
     *
     * @param {*} other The value to compare against.
     * @return {boolean} true if this `FieldPath` is equal to the provided value.
     */
    isEqual(other) {
        return super.isEqual(other);
    }
}
/**
 * A special sentinel value to refer to the ID of a document.
 *
 * @private
 */
FieldPath._DOCUMENT_ID = new FieldPath('__name__');
exports.FieldPath = FieldPath;
//# sourceMappingURL=path.js.map