// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { HttpHeaders, isHttpHeadersLike } from "./httpHeaders";
import { Serializer } from "./serializer";
import { generateUuid } from "./util/utils";
export function isWebResourceLike(object) {
    if (object && typeof object === "object") {
        const castObject = object;
        if (typeof castObject.url === "string" &&
            typeof castObject.method === "string" &&
            typeof castObject.headers === "object" &&
            isHttpHeadersLike(castObject.headers) &&
            typeof castObject.validateRequestProperties === "function" &&
            typeof castObject.prepare === "function" &&
            typeof castObject.clone === "function") {
            return true;
        }
    }
    return false;
}
/**
 * Creates a new WebResource object.
 *
 * This class provides an abstraction over a REST call by being library / implementation agnostic and wrapping the necessary
 * properties to initiate a request.
 */
export class WebResource {
    constructor(url, method, body, query, headers, streamResponseBody, withCredentials, abortSignal, timeout, onUploadProgress, onDownloadProgress, proxySettings, keepAlive, decompressResponse, streamResponseStatusCodes) {
        this.streamResponseBody = streamResponseBody;
        this.streamResponseStatusCodes = streamResponseStatusCodes;
        this.url = url || "";
        this.method = method || "GET";
        this.headers = isHttpHeadersLike(headers) ? headers : new HttpHeaders(headers);
        this.body = body;
        this.query = query;
        this.formData = undefined;
        this.withCredentials = withCredentials || false;
        this.abortSignal = abortSignal;
        this.timeout = timeout || 0;
        this.onUploadProgress = onUploadProgress;
        this.onDownloadProgress = onDownloadProgress;
        this.proxySettings = proxySettings;
        this.keepAlive = keepAlive;
        this.decompressResponse = decompressResponse;
        this.requestId = this.headers.get("x-ms-client-request-id") || generateUuid();
    }
    /**
     * Validates that the required properties such as method, url, headers["Content-Type"],
     * headers["accept-language"] are defined. It will throw an error if one of the above
     * mentioned properties are not defined.
     */
    validateRequestProperties() {
        if (!this.method) {
            throw new Error("WebResource.method is required.");
        }
        if (!this.url) {
            throw new Error("WebResource.url is required.");
        }
    }
    /**
     * Prepares the request.
     * @param options - Options to provide for preparing the request.
     * @returns Returns the prepared WebResource (HTTP Request) object that needs to be given to the request pipeline.
     */
    prepare(options) {
        if (!options) {
            throw new Error("options object is required");
        }
        if (options.method === undefined ||
            options.method === null ||
            typeof options.method.valueOf() !== "string") {
            throw new Error("options.method must be a string.");
        }
        if (options.url && options.pathTemplate) {
            throw new Error("options.url and options.pathTemplate are mutually exclusive. Please provide exactly one of them.");
        }
        if ((options.pathTemplate === undefined ||
            options.pathTemplate === null ||
            typeof options.pathTemplate.valueOf() !== "string") &&
            (options.url === undefined ||
                options.url === null ||
                typeof options.url.valueOf() !== "string")) {
            throw new Error("Please provide exactly one of options.pathTemplate or options.url.");
        }
        // set the url if it is provided.
        if (options.url) {
            if (typeof options.url !== "string") {
                throw new Error('options.url must be of type "string".');
            }
            this.url = options.url;
        }
        // set the method
        if (options.method) {
            const validMethods = ["GET", "PUT", "HEAD", "DELETE", "OPTIONS", "POST", "PATCH", "TRACE"];
            if (validMethods.indexOf(options.method.toUpperCase()) === -1) {
                throw new Error('The provided method "' +
                    options.method +
                    '" is invalid. Supported HTTP methods are: ' +
                    JSON.stringify(validMethods));
            }
        }
        this.method = options.method.toUpperCase();
        // construct the url if path template is provided
        if (options.pathTemplate) {
            const { pathTemplate, pathParameters } = options;
            if (typeof pathTemplate !== "string") {
                throw new Error('options.pathTemplate must be of type "string".');
            }
            if (!options.baseUrl) {
                options.baseUrl = "https://management.azure.com";
            }
            const baseUrl = options.baseUrl;
            let url = baseUrl +
                (baseUrl.endsWith("/") ? "" : "/") +
                (pathTemplate.startsWith("/") ? pathTemplate.slice(1) : pathTemplate);
            const segments = url.match(/({[\w-]*\s*[\w-]*})/gi);
            if (segments && segments.length) {
                if (!pathParameters) {
                    throw new Error(`pathTemplate: ${pathTemplate} has been provided. Hence, options.pathParameters must also be provided.`);
                }
                segments.forEach(function (item) {
                    const pathParamName = item.slice(1, -1);
                    const pathParam = pathParameters[pathParamName];
                    if (pathParam === null ||
                        pathParam === undefined ||
                        !(typeof pathParam === "string" || typeof pathParam === "object")) {
                        const stringifiedPathParameters = JSON.stringify(pathParameters, undefined, 2);
                        throw new Error(`pathTemplate: ${pathTemplate} contains the path parameter ${pathParamName}` +
                            ` however, it is not present in parameters: ${stringifiedPathParameters}.` +
                            `The value of the path parameter can either be a "string" of the form { ${pathParamName}: "some sample value" } or ` +
                            `it can be an "object" of the form { "${pathParamName}": { value: "some sample value", skipUrlEncoding: true } }.`);
                    }
                    if (typeof pathParam.valueOf() === "string") {
                        url = url.replace(item, encodeURIComponent(pathParam));
                    }
                    if (typeof pathParam.valueOf() === "object") {
                        if (!pathParam.value) {
                            throw new Error(`options.pathParameters[${pathParamName}] is of type "object" but it does not contain a "value" property.`);
                        }
                        if (pathParam.skipUrlEncoding) {
                            url = url.replace(item, pathParam.value);
                        }
                        else {
                            url = url.replace(item, encodeURIComponent(pathParam.value));
                        }
                    }
                });
            }
            this.url = url;
        }
        // append query parameters to the url if they are provided. They can be provided with pathTemplate or url option.
        if (options.queryParameters) {
            const queryParameters = options.queryParameters;
            if (typeof queryParameters !== "object") {
                throw new Error(`options.queryParameters must be of type object. It should be a JSON object ` +
                    `of "query-parameter-name" as the key and the "query-parameter-value" as the value. ` +
                    `The "query-parameter-value" may be fo type "string" or an "object" of the form { value: "query-parameter-value", skipUrlEncoding: true }.`);
            }
            // append question mark if it is not present in the url
            if (this.url && this.url.indexOf("?") === -1) {
                this.url += "?";
            }
            // construct queryString
            const queryParams = [];
            // We need to populate this.query as a dictionary if the request is being used for Sway's validateRequest().
            this.query = {};
            for (const queryParamName in queryParameters) {
                const queryParam = queryParameters[queryParamName];
                if (queryParam) {
                    if (typeof queryParam === "string") {
                        queryParams.push(queryParamName + "=" + encodeURIComponent(queryParam));
                        this.query[queryParamName] = encodeURIComponent(queryParam);
                    }
                    else if (typeof queryParam === "object") {
                        if (!queryParam.value) {
                            throw new Error(`options.queryParameters[${queryParamName}] is of type "object" but it does not contain a "value" property.`);
                        }
                        if (queryParam.skipUrlEncoding) {
                            queryParams.push(queryParamName + "=" + queryParam.value);
                            this.query[queryParamName] = queryParam.value;
                        }
                        else {
                            queryParams.push(queryParamName + "=" + encodeURIComponent(queryParam.value));
                            this.query[queryParamName] = encodeURIComponent(queryParam.value);
                        }
                    }
                }
            } // end-of-for
            // append the queryString
            this.url += queryParams.join("&");
        }
        // add headers to the request if they are provided
        if (options.headers) {
            const headers = options.headers;
            for (const headerName of Object.keys(options.headers)) {
                this.headers.set(headerName, headers[headerName]);
            }
        }
        // ensure accept-language is set correctly
        if (!this.headers.get("accept-language")) {
            this.headers.set("accept-language", "en-US");
        }
        // ensure the request-id is set correctly
        if (!this.headers.get("x-ms-client-request-id") && !options.disableClientRequestId) {
            this.headers.set("x-ms-client-request-id", this.requestId);
        }
        // default
        if (!this.headers.get("Content-Type")) {
            this.headers.set("Content-Type", "application/json; charset=utf-8");
        }
        // set the request body. request.js automatically sets the Content-Length request header, so we need not set it explicitly
        this.body = options.body;
        if (options.body !== undefined && options.body !== null) {
            // body as a stream special case. set the body as-is and check for some special request headers specific to sending a stream.
            if (options.bodyIsStream) {
                if (!this.headers.get("Transfer-Encoding")) {
                    this.headers.set("Transfer-Encoding", "chunked");
                }
                if (this.headers.get("Content-Type") !== "application/octet-stream") {
                    this.headers.set("Content-Type", "application/octet-stream");
                }
            }
            else {
                if (options.serializationMapper) {
                    this.body = new Serializer(options.mappers).serialize(options.serializationMapper, options.body, "requestBody");
                }
                if (!options.disableJsonStringifyOnBody) {
                    this.body = JSON.stringify(options.body);
                }
            }
        }
        if (options.spanOptions) {
            this.spanOptions = options.spanOptions;
        }
        if (options.tracingContext) {
            this.tracingContext = options.tracingContext;
        }
        this.abortSignal = options.abortSignal;
        this.onDownloadProgress = options.onDownloadProgress;
        this.onUploadProgress = options.onUploadProgress;
        return this;
    }
    /**
     * Clone this WebResource HTTP request object.
     * @returns The clone of this WebResource HTTP request object.
     */
    clone() {
        const result = new WebResource(this.url, this.method, this.body, this.query, this.headers && this.headers.clone(), this.streamResponseBody, this.withCredentials, this.abortSignal, this.timeout, this.onUploadProgress, this.onDownloadProgress, this.proxySettings, this.keepAlive, this.decompressResponse, this.streamResponseStatusCodes);
        if (this.formData) {
            result.formData = this.formData;
        }
        if (this.operationSpec) {
            result.operationSpec = this.operationSpec;
        }
        if (this.shouldDeserialize) {
            result.shouldDeserialize = this.shouldDeserialize;
        }
        if (this.operationResponseGetter) {
            result.operationResponseGetter = this.operationResponseGetter;
        }
        return result;
    }
}
//# sourceMappingURL=webResource.js.map