// --------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
//
// Code generated by Microsoft (R) AutoRest Code Generator.
// Changes may cause incorrect behavior and will be lost if the code is
// regenerated.
// --------------------------------------------------------------------------

import AzureCore
import Foundation
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// swiftlint:disable cyclomatic_complexity
// swiftlint:disable function_body_length
// swiftlint:disable type_body_length

internal final class ChatThread {
    internal let client: ChatClientInternal

    init(client: ChatClientInternal) {
        self.client = client
    }

    /// Gets chat message read receipts for a thread.
    /// - Parameters:
    ///    - chatThreadId : Thread id to get the chat message read receipts for.
    ///    - options: A list of options for the operation
    ///    - completionHandler: A completion handler that receives a status code on
    ///     success.
    internal func listChatReadReceipts(
        chatThreadId: String,
        withOptions options: ListChatReadReceiptsOptions? = nil,
        completionHandler: @escaping HTTPResultHandler<PagedCollection<ChatMessageReadReceiptInternal>>
    ) {
        let dispatchQueue = options?.dispatchQueue ?? client.commonOptions.dispatchQueue ?? DispatchQueue.main

        // Create request parameters
        let params = RequestParameters(
            (.path, "chatThreadId", chatThreadId, .encode), (.query, "maxPageSize", options?.maxPageSize, .encode),
            (.query, "skip", options?.skip, .encode), (.uri, "endpoint", client.endpoint.absoluteString, .skipEncoding),
            (.query, "api-version", client.options.apiVersion, .encode),
            (.header, "Accept", "application/json", .encode)
        )

        // Construct request
        let urlTemplate = "/chat/threads/{chatThreadId}/readReceipts"
        guard let requestUrl = client.url(host: "{endpoint}", template: urlTemplate, params: params),
              let request = try? HTTPRequest(method: .get, url: requestUrl, headers: params.headers)
        else {
            client.options.logger.error("Failed to construct HTTP request.")
            return
        }

        // Send request
        let context = PipelineContext.of(keyValues: [
            ContextKey.allowedStatusCodes.rawValue: [200, 401, 403, 429, 503] as AnyObject
        ])
        context.add(cancellationToken: options?.cancellationToken, applying: client.options)
        context.merge(with: options?.context)
        client.request(request, context: context) { result, httpResponse in
            guard let data = httpResponse?.data else {
                let noDataError = AzureError.client("Response data expected but not found.")
                dispatchQueue.async {
                    completionHandler(.failure(noDataError), httpResponse)
                }
                return
            }
            switch result {
            case .success:
                guard let statusCode = httpResponse?.statusCode else {
                    let noStatusCodeError = AzureError.client("Expected a status code in response but didn't find one.")
                    dispatchQueue.async {
                        completionHandler(.failure(noStatusCodeError), httpResponse)
                    }
                    return
                }
                if [
                    200
                ].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let codingKeys = PagedCodingKeys(
                            items: "value",
                            continuationToken: "nextLink"
                        )
                        let paged = try PagedCollection<ChatMessageReadReceiptInternal>(
                            client: self.client,
                            request: request,
                            context: context,
                            data: data,
                            codingKeys: codingKeys,
                            decoder: decoder
                        )
                        dispatchQueue.async {
                            completionHandler(.success(paged), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }

                if [401].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Unauthorized.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [403].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Forbidden.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [429].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Too many requests.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [503].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(
                                .failure(AzureError.service("Service unavailable.", decoded)),
                                httpResponse
                            )
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
            case let .failure(error):
                dispatchQueue.async {
                    completionHandler(.failure(error), httpResponse)
                }
            }
        }
    }

    /// Sends a read receipt event to a thread, on behalf of a user.
    /// - Parameters:
    ///    - chatReadReceipt : Read receipt details.
    ///    - chatThreadId : Thread id to send the read receipt event to.
    ///    - options: A list of options for the operation
    ///    - completionHandler: A completion handler that receives a status code on
    ///     success.
    internal func send(
        chatReadReceipt: SendReadReceiptRequest,
        chatThreadId: String,
        withOptions options: SendChatReadReceiptOptions? = nil,
        completionHandler: @escaping HTTPResultHandler<Void>
    ) {
        let dispatchQueue = options?.dispatchQueue ?? client.commonOptions.dispatchQueue ?? DispatchQueue.main

        // Create request parameters
        let params = RequestParameters(
            (.path, "chatThreadId", chatThreadId, .encode),
            (.uri, "endpoint", client.endpoint.absoluteString, .skipEncoding),
            (.query, "api-version", client.options.apiVersion, .encode),
            (.header, "Content-Type", "application/json", .encode), (.header, "Accept", "application/json", .encode)
        )

        // Construct request
        guard let requestBody = try? JSONEncoder().encode(chatReadReceipt) else {
            client.options.logger.error("Failed to encode request body as json.")
            return
        }
        let urlTemplate = "/chat/threads/{chatThreadId}/readReceipts"
        guard let requestUrl = client.url(host: "{endpoint}", template: urlTemplate, params: params),
              let request = try? HTTPRequest(method: .post, url: requestUrl, headers: params.headers, data: requestBody)
        else {
            client.options.logger.error("Failed to construct HTTP request.")
            return
        }

        // Send request
        let context = PipelineContext.of(keyValues: [
            ContextKey.allowedStatusCodes.rawValue: [200, 401, 403, 429, 503] as AnyObject
        ])
        context.add(cancellationToken: options?.cancellationToken, applying: client.options)
        context.merge(with: options?.context)
        client.request(request, context: context) { result, httpResponse in
            guard let data = httpResponse?.data else {
                let noDataError = AzureError.client("Response data expected but not found.")
                dispatchQueue.async {
                    completionHandler(.failure(noDataError), httpResponse)
                }
                return
            }
            switch result {
            case .success:
                guard let statusCode = httpResponse?.statusCode else {
                    let noStatusCodeError = AzureError.client("Expected a status code in response but didn't find one.")
                    dispatchQueue.async {
                        completionHandler(.failure(noStatusCodeError), httpResponse)
                    }
                    return
                }
                if [
                    200
                ].contains(statusCode) {
                    dispatchQueue.async {
                        completionHandler(
                            .success(()),
                            httpResponse
                        )
                    }
                }
                if [401].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Unauthorized.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [403].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Forbidden.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [429].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Too many requests.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [503].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(
                                .failure(AzureError.service("Service unavailable.", decoded)),
                                httpResponse
                            )
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
            case let .failure(error):
                dispatchQueue.async {
                    completionHandler(.failure(error), httpResponse)
                }
            }
        }
    }

    /// Sends a message to a thread.
    /// - Parameters:
    ///    - chatMessage : Details of the message to send.
    ///    - chatThreadId : The thread id to send the message to.
    ///    - options: A list of options for the operation
    ///    - completionHandler: A completion handler that receives a status code on
    ///     success.
    internal func send(
        chatMessage: SendChatMessageRequest,
        chatThreadId: String,
        withOptions options: SendChatMessageOptions? = nil,
        completionHandler: @escaping HTTPResultHandler<SendChatMessageResult>
    ) {
        let dispatchQueue = options?.dispatchQueue ?? client.commonOptions.dispatchQueue ?? DispatchQueue.main

        // Create request parameters
        let params = RequestParameters(
            (.path, "chatThreadId", chatThreadId, .encode),
            (.uri, "endpoint", client.endpoint.absoluteString, .skipEncoding),
            (.query, "api-version", client.options.apiVersion, .encode),
            (.header, "Content-Type", "application/json", .encode), (.header, "Accept", "application/json", .encode)
        )

        // Construct request
        guard let requestBody = try? JSONEncoder().encode(chatMessage) else {
            client.options.logger.error("Failed to encode request body as json.")
            return
        }
        let urlTemplate = "/chat/threads/{chatThreadId}/messages"
        guard let requestUrl = client.url(host: "{endpoint}", template: urlTemplate, params: params),
              let request = try? HTTPRequest(method: .post, url: requestUrl, headers: params.headers, data: requestBody)
        else {
            client.options.logger.error("Failed to construct HTTP request.")
            return
        }

        // Send request
        let context = PipelineContext.of(keyValues: [
            ContextKey.allowedStatusCodes.rawValue: [201, 401, 403, 429, 503] as AnyObject
        ])
        context.add(cancellationToken: options?.cancellationToken, applying: client.options)
        context.merge(with: options?.context)
        client.request(request, context: context) { result, httpResponse in
            guard let data = httpResponse?.data else {
                let noDataError = AzureError.client("Response data expected but not found.")
                dispatchQueue.async {
                    completionHandler(.failure(noDataError), httpResponse)
                }
                return
            }
            switch result {
            case .success:
                guard let statusCode = httpResponse?.statusCode else {
                    let noStatusCodeError = AzureError.client("Expected a status code in response but didn't find one.")
                    dispatchQueue.async {
                        completionHandler(.failure(noStatusCodeError), httpResponse)
                    }
                    return
                }
                if [
                    201
                ].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(SendChatMessageResult.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.success(decoded), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [401].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Unauthorized.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [403].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Forbidden.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [429].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Too many requests.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [503].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(
                                .failure(AzureError.service("Service unavailable.", decoded)),
                                httpResponse
                            )
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
            case let .failure(error):
                dispatchQueue.async {
                    completionHandler(.failure(error), httpResponse)
                }
            }
        }
    }

    /// Gets a list of messages from a thread.
    /// - Parameters:
    ///    - chatThreadId : The thread id of the message.
    ///    - options: A list of options for the operation
    ///    - completionHandler: A completion handler that receives a status code on
    ///     success.
    internal func listChatMessages(
        chatThreadId: String,
        withOptions options: ListChatMessagesOptions? = nil,
        completionHandler: @escaping HTTPResultHandler<PagedCollection<ChatMessageInternal>>
    ) {
        let dispatchQueue = options?.dispatchQueue ?? client.commonOptions.dispatchQueue ?? DispatchQueue.main

        // Create request parameters
        let params = RequestParameters(
            (.path, "chatThreadId", chatThreadId, .encode), (.query, "maxPageSize", options?.maxPageSize, .encode),
            (.query, "startTime", options?.startTime, .encode),
            (.uri, "endpoint", client.endpoint.absoluteString, .skipEncoding),
            (.query, "api-version", client.options.apiVersion, .encode),
            (.header, "Accept", "application/json", .encode)
        )

        // Construct request
        let urlTemplate = "/chat/threads/{chatThreadId}/messages"
        guard let requestUrl = client.url(host: "{endpoint}", template: urlTemplate, params: params),
              let request = try? HTTPRequest(method: .get, url: requestUrl, headers: params.headers)
        else {
            client.options.logger.error("Failed to construct HTTP request.")
            return
        }

        // Send request
        let context = PipelineContext.of(keyValues: [
            ContextKey.allowedStatusCodes.rawValue: [200, 401, 403, 429, 503] as AnyObject
        ])
        context.add(cancellationToken: options?.cancellationToken, applying: client.options)
        context.merge(with: options?.context)
        client.request(request, context: context) { result, httpResponse in
            guard let data = httpResponse?.data else {
                let noDataError = AzureError.client("Response data expected but not found.")
                dispatchQueue.async {
                    completionHandler(.failure(noDataError), httpResponse)
                }
                return
            }
            switch result {
            case .success:
                guard let statusCode = httpResponse?.statusCode else {
                    let noStatusCodeError = AzureError.client("Expected a status code in response but didn't find one.")
                    dispatchQueue.async {
                        completionHandler(.failure(noStatusCodeError), httpResponse)
                    }
                    return
                }
                if [
                    200
                ].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let codingKeys = PagedCodingKeys(
                            items: "value",
                            continuationToken: "nextLink"
                        )
                        let paged = try PagedCollection<ChatMessageInternal>(
                            client: self.client,
                            request: request,
                            context: context,
                            data: data,
                            codingKeys: codingKeys,
                            decoder: decoder
                        )
                        dispatchQueue.async {
                            completionHandler(.success(paged), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }

                if [401].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Unauthorized.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [403].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Forbidden.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [429].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Too many requests.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [503].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(
                                .failure(AzureError.service("Service unavailable.", decoded)),
                                httpResponse
                            )
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
            case let .failure(error):
                dispatchQueue.async {
                    completionHandler(.failure(error), httpResponse)
                }
            }
        }
    }

    /// Gets a message by id.
    /// - Parameters:
    ///    - chatThreadId : The thread id to which the message was sent.
    ///    - chatMessageId : The message id.
    ///    - options: A list of options for the operation
    ///    - completionHandler: A completion handler that receives a status code on
    ///     success.
    internal func getChatMessage(
        chatThreadId: String,
        chatMessageId: String,
        withOptions options: GetChatMessageOptions? = nil,
        completionHandler: @escaping HTTPResultHandler<ChatMessageInternal>
    ) {
        let dispatchQueue = options?.dispatchQueue ?? client.commonOptions.dispatchQueue ?? DispatchQueue.main

        // Create request parameters
        let params = RequestParameters(
            (.path, "chatThreadId", chatThreadId, .encode), (.path, "chatMessageId", chatMessageId, .encode),
            (.uri, "endpoint", client.endpoint.absoluteString, .skipEncoding),
            (.query, "api-version", client.options.apiVersion, .encode),
            (.header, "Accept", "application/json", .encode)
        )

        // Construct request
        let urlTemplate = "/chat/threads/{chatThreadId}/messages/{chatMessageId}"
        guard let requestUrl = client.url(host: "{endpoint}", template: urlTemplate, params: params),
              let request = try? HTTPRequest(method: .get, url: requestUrl, headers: params.headers)
        else {
            client.options.logger.error("Failed to construct HTTP request.")
            return
        }

        // Send request
        let context = PipelineContext.of(keyValues: [
            ContextKey.allowedStatusCodes.rawValue: [200, 401, 403, 429, 503] as AnyObject
        ])
        context.add(cancellationToken: options?.cancellationToken, applying: client.options)
        context.merge(with: options?.context)
        client.request(request, context: context) { result, httpResponse in
            guard let data = httpResponse?.data else {
                let noDataError = AzureError.client("Response data expected but not found.")
                dispatchQueue.async {
                    completionHandler(.failure(noDataError), httpResponse)
                }
                return
            }
            switch result {
            case .success:
                guard let statusCode = httpResponse?.statusCode else {
                    let noStatusCodeError = AzureError.client("Expected a status code in response but didn't find one.")
                    dispatchQueue.async {
                        completionHandler(.failure(noStatusCodeError), httpResponse)
                    }
                    return
                }
                if [
                    200
                ].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(ChatMessageInternal.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.success(decoded), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [401].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Unauthorized.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [403].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Forbidden.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [429].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Too many requests.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [503].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(
                                .failure(AzureError.service("Service unavailable.", decoded)),
                                httpResponse
                            )
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
            case let .failure(error):
                dispatchQueue.async {
                    completionHandler(.failure(error), httpResponse)
                }
            }
        }
    }

    /// Updates a message.
    /// - Parameters:
    ///    - chatMessage : Details of the request to update the message.
    ///    - chatThreadId : The thread id to which the message was sent.
    ///    - chatMessageId : The message id.
    ///    - options: A list of options for the operation
    ///    - completionHandler: A completion handler that receives a status code on
    ///     success.
    internal func update(
        chatMessage: UpdateChatMessageRequest,
        chatThreadId: String,
        chatMessageId: String,
        withOptions options: UpdateChatMessageOptions? = nil,
        completionHandler: @escaping HTTPResultHandler<Void>
    ) {
        let dispatchQueue = options?.dispatchQueue ?? client.commonOptions.dispatchQueue ?? DispatchQueue.main

        // Create request parameters
        let params = RequestParameters(
            (.path, "chatThreadId", chatThreadId, .encode), (.path, "chatMessageId", chatMessageId, .encode),
            (.uri, "endpoint", client.endpoint.absoluteString, .skipEncoding),
            (.query, "api-version", client.options.apiVersion, .encode),
            (.header, "Content-Type", "application/merge-patch+json", .encode),
            (.header, "Accept", "application/json", .encode)
        )

        // Construct request
        guard let requestBody = try? JSONEncoder().encode(chatMessage) else {
            client.options.logger.error("Failed to encode request body as json.")
            return
        }
        let urlTemplate = "/chat/threads/{chatThreadId}/messages/{chatMessageId}"
        guard let requestUrl = client.url(host: "{endpoint}", template: urlTemplate, params: params),
              let request = try? HTTPRequest(
                  method: .patch,
                  url: requestUrl,
                  headers: params.headers,
                  data: requestBody
              )
        else {
            client.options.logger.error("Failed to construct HTTP request.")
            return
        }

        // Send request
        let context = PipelineContext.of(keyValues: [
            ContextKey.allowedStatusCodes.rawValue: [204, 401, 403, 429, 503] as AnyObject
        ])
        context.add(cancellationToken: options?.cancellationToken, applying: client.options)
        context.merge(with: options?.context)
        client.request(request, context: context) { result, httpResponse in
            guard let data = httpResponse?.data else {
                let noDataError = AzureError.client("Response data expected but not found.")
                dispatchQueue.async {
                    completionHandler(.failure(noDataError), httpResponse)
                }
                return
            }
            switch result {
            case .success:
                guard let statusCode = httpResponse?.statusCode else {
                    let noStatusCodeError = AzureError.client("Expected a status code in response but didn't find one.")
                    dispatchQueue.async {
                        completionHandler(.failure(noStatusCodeError), httpResponse)
                    }
                    return
                }
                if [
                    204
                ].contains(statusCode) {
                    dispatchQueue.async {
                        completionHandler(
                            .success(()),
                            httpResponse
                        )
                    }
                }
                if [401].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Unauthorized.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [403].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Forbidden.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [429].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Too many requests.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [503].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(
                                .failure(AzureError.service("Service unavailable.", decoded)),
                                httpResponse
                            )
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
            case let .failure(error):
                dispatchQueue.async {
                    completionHandler(.failure(error), httpResponse)
                }
            }
        }
    }

    /// Deletes a message.
    /// - Parameters:
    ///    - chatThreadId : The thread id to which the message was sent.
    ///    - chatMessageId : The message id.
    ///    - options: A list of options for the operation
    ///    - completionHandler: A completion handler that receives a status code on
    ///     success.
    internal func deleteChatMessage(
        chatThreadId: String,
        chatMessageId: String,
        withOptions options: DeleteChatMessageOptions? = nil,
        completionHandler: @escaping HTTPResultHandler<Void>
    ) {
        let dispatchQueue = options?.dispatchQueue ?? client.commonOptions.dispatchQueue ?? DispatchQueue.main

        // Create request parameters
        let params = RequestParameters(
            (.path, "chatThreadId", chatThreadId, .encode), (.path, "chatMessageId", chatMessageId, .encode),
            (.uri, "endpoint", client.endpoint.absoluteString, .skipEncoding),
            (.query, "api-version", client.options.apiVersion, .encode),
            (.header, "Accept", "application/json", .encode)
        )

        // Construct request
        let urlTemplate = "/chat/threads/{chatThreadId}/messages/{chatMessageId}"
        guard let requestUrl = client.url(host: "{endpoint}", template: urlTemplate, params: params),
              let request = try? HTTPRequest(method: .delete, url: requestUrl, headers: params.headers)
        else {
            client.options.logger.error("Failed to construct HTTP request.")
            return
        }

        // Send request
        let context = PipelineContext.of(keyValues: [
            ContextKey.allowedStatusCodes.rawValue: [204, 401, 403, 429, 503] as AnyObject
        ])
        context.add(cancellationToken: options?.cancellationToken, applying: client.options)
        context.merge(with: options?.context)
        client.request(request, context: context) { result, httpResponse in
            guard let data = httpResponse?.data else {
                let noDataError = AzureError.client("Response data expected but not found.")
                dispatchQueue.async {
                    completionHandler(.failure(noDataError), httpResponse)
                }
                return
            }
            switch result {
            case .success:
                guard let statusCode = httpResponse?.statusCode else {
                    let noStatusCodeError = AzureError.client("Expected a status code in response but didn't find one.")
                    dispatchQueue.async {
                        completionHandler(.failure(noStatusCodeError), httpResponse)
                    }
                    return
                }
                if [
                    204
                ].contains(statusCode) {
                    dispatchQueue.async {
                        completionHandler(
                            .success(()),
                            httpResponse
                        )
                    }
                }
                if [401].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Unauthorized.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [403].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Forbidden.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [429].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Too many requests.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [503].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(
                                .failure(AzureError.service("Service unavailable.", decoded)),
                                httpResponse
                            )
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
            case let .failure(error):
                dispatchQueue.async {
                    completionHandler(.failure(error), httpResponse)
                }
            }
        }
    }

    /// Gets the participants of a thread.
    /// - Parameters:
    ///    - chatThreadId : Thread id to get participants for.
    ///    - options: A list of options for the operation
    ///    - completionHandler: A completion handler that receives a status code on
    ///     success.
    internal func listChatParticipants(
        chatThreadId: String,
        withOptions options: ListChatParticipantsOptions? = nil,
        completionHandler: @escaping HTTPResultHandler<PagedCollection<ChatParticipantInternal>>
    ) {
        let dispatchQueue = options?.dispatchQueue ?? client.commonOptions.dispatchQueue ?? DispatchQueue.main

        // Create request parameters
        let params = RequestParameters(
            (.path, "chatThreadId", chatThreadId, .encode), (.query, "maxPageSize", options?.maxPageSize, .encode),
            (.query, "skip", options?.skip, .encode), (.uri, "endpoint", client.endpoint.absoluteString, .skipEncoding),
            (.query, "api-version", client.options.apiVersion, .encode),
            (.header, "Accept", "application/json", .encode)
        )

        // Construct request
        let urlTemplate = "/chat/threads/{chatThreadId}/participants"
        guard let requestUrl = client.url(host: "{endpoint}", template: urlTemplate, params: params),
              let request = try? HTTPRequest(method: .get, url: requestUrl, headers: params.headers)
        else {
            client.options.logger.error("Failed to construct HTTP request.")
            return
        }

        // Send request
        let context = PipelineContext.of(keyValues: [
            ContextKey.allowedStatusCodes.rawValue: [200, 401, 403, 429, 503] as AnyObject
        ])
        context.add(cancellationToken: options?.cancellationToken, applying: client.options)
        context.merge(with: options?.context)
        client.request(request, context: context) { result, httpResponse in
            guard let data = httpResponse?.data else {
                let noDataError = AzureError.client("Response data expected but not found.")
                dispatchQueue.async {
                    completionHandler(.failure(noDataError), httpResponse)
                }
                return
            }
            switch result {
            case .success:
                guard let statusCode = httpResponse?.statusCode else {
                    let noStatusCodeError = AzureError.client("Expected a status code in response but didn't find one.")
                    dispatchQueue.async {
                        completionHandler(.failure(noStatusCodeError), httpResponse)
                    }
                    return
                }
                if [
                    200
                ].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let codingKeys = PagedCodingKeys(
                            items: "value",
                            continuationToken: "nextLink"
                        )
                        let paged = try PagedCollection<ChatParticipantInternal>(
                            client: self.client,
                            request: request,
                            context: context,
                            data: data,
                            codingKeys: codingKeys,
                            decoder: decoder
                        )
                        dispatchQueue.async {
                            completionHandler(.success(paged), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }

                if [401].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Unauthorized.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [403].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Forbidden.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [429].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Too many requests.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [503].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(
                                .failure(AzureError.service("Service unavailable.", decoded)),
                                httpResponse
                            )
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
            case let .failure(error):
                dispatchQueue.async {
                    completionHandler(.failure(error), httpResponse)
                }
            }
        }
    }

    /// Remove a participant from a thread.
    /// - Parameters:
    ///    - chatParticipant : Id of the thread participant to remove from the thread.
    ///    - chatThreadId : Thread id to remove the participant from.
    ///    - options: A list of options for the operation
    ///    - completionHandler: A completion handler that receives a status code on
    ///     success.
    internal func remove(
        chatParticipant: CommunicationIdentifierModelInternal,
        chatThreadId: String,
        withOptions options: RemoveChatParticipantOptions? = nil,
        completionHandler: @escaping HTTPResultHandler<Void>
    ) {
        let dispatchQueue = options?.dispatchQueue ?? client.commonOptions.dispatchQueue ?? DispatchQueue.main

        // Create request parameters
        let params = RequestParameters(
            (.path, "chatThreadId", chatThreadId, .encode),
            (.uri, "endpoint", client.endpoint.absoluteString, .skipEncoding),
            (.query, "api-version", client.options.apiVersion, .encode),
            (.header, "Content-Type", "application/json", .encode), (.header, "Accept", "application/json", .encode)
        )

        // Construct request
        guard let requestBody = try? JSONEncoder().encode(chatParticipant) else {
            client.options.logger.error("Failed to encode request body as json.")
            return
        }
        let urlTemplate = "/chat/threads/{chatThreadId}/participants/:remove"
        guard let requestUrl = client.url(host: "{endpoint}", template: urlTemplate, params: params),
              let request = try? HTTPRequest(method: .post, url: requestUrl, headers: params.headers, data: requestBody)
        else {
            client.options.logger.error("Failed to construct HTTP request.")
            return
        }

        // Send request
        let context = PipelineContext.of(keyValues: [
            ContextKey.allowedStatusCodes.rawValue: [204, 401, 403, 429, 503] as AnyObject
        ])
        context.add(cancellationToken: options?.cancellationToken, applying: client.options)
        context.merge(with: options?.context)
        client.request(request, context: context) { result, httpResponse in
            guard let data = httpResponse?.data else {
                let noDataError = AzureError.client("Response data expected but not found.")
                dispatchQueue.async {
                    completionHandler(.failure(noDataError), httpResponse)
                }
                return
            }
            switch result {
            case .success:
                guard let statusCode = httpResponse?.statusCode else {
                    let noStatusCodeError = AzureError.client("Expected a status code in response but didn't find one.")
                    dispatchQueue.async {
                        completionHandler(.failure(noStatusCodeError), httpResponse)
                    }
                    return
                }
                if [
                    204
                ].contains(statusCode) {
                    dispatchQueue.async {
                        completionHandler(
                            .success(()),
                            httpResponse
                        )
                    }
                }
                if [401].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Unauthorized.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [403].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Forbidden.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [429].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Too many requests.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [503].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(
                                .failure(AzureError.service("Service unavailable.", decoded)),
                                httpResponse
                            )
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
            case let .failure(error):
                dispatchQueue.async {
                    completionHandler(.failure(error), httpResponse)
                }
            }
        }
    }

    /// Adds thread participants to a thread. If participants already exist, no change occurs.
    /// - Parameters:
    ///    - chatParticipants : Thread participants to be added to the thread.
    ///    - chatThreadId : Id of the thread to add participants to.
    ///    - options: A list of options for the operation
    ///    - completionHandler: A completion handler that receives a status code on
    ///     success.
    internal func add(
        chatParticipants: AddChatParticipantsRequestInternal,
        chatThreadId: String,
        withOptions options: AddChatParticipantsOptions? = nil,
        completionHandler: @escaping HTTPResultHandler<AddChatParticipantsResult>
    ) {
        let dispatchQueue = options?.dispatchQueue ?? client.commonOptions.dispatchQueue ?? DispatchQueue.main

        // Create request parameters
        let params = RequestParameters(
            (.path, "chatThreadId", chatThreadId, .encode),
            (.uri, "endpoint", client.endpoint.absoluteString, .skipEncoding),
            (.query, "api-version", client.options.apiVersion, .encode),
            (.header, "Content-Type", "application/json", .encode), (.header, "Accept", "application/json", .encode)
        )

        // Construct request
        guard let requestBody = try? JSONEncoder().encode(chatParticipants) else {
            client.options.logger.error("Failed to encode request body as json.")
            return
        }
        let urlTemplate = "/chat/threads/{chatThreadId}/participants/:add"
        guard let requestUrl = client.url(host: "{endpoint}", template: urlTemplate, params: params),
              let request = try? HTTPRequest(method: .post, url: requestUrl, headers: params.headers, data: requestBody)
        else {
            client.options.logger.error("Failed to construct HTTP request.")
            return
        }

        // Send request
        let context = PipelineContext.of(keyValues: [
            ContextKey.allowedStatusCodes.rawValue: [201, 401, 403, 429, 503] as AnyObject
        ])
        context.add(cancellationToken: options?.cancellationToken, applying: client.options)
        context.merge(with: options?.context)
        client.request(request, context: context) { result, httpResponse in
            guard let data = httpResponse?.data else {
                let noDataError = AzureError.client("Response data expected but not found.")
                dispatchQueue.async {
                    completionHandler(.failure(noDataError), httpResponse)
                }
                return
            }
            switch result {
            case .success:
                guard let statusCode = httpResponse?.statusCode else {
                    let noStatusCodeError = AzureError.client("Expected a status code in response but didn't find one.")
                    dispatchQueue.async {
                        completionHandler(.failure(noStatusCodeError), httpResponse)
                    }
                    return
                }
                if [
                    201
                ].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(AddChatParticipantsResult.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.success(decoded), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [401].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Unauthorized.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [403].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Forbidden.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [429].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Too many requests.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [503].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(
                                .failure(AzureError.service("Service unavailable.", decoded)),
                                httpResponse
                            )
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
            case let .failure(error):
                dispatchQueue.async {
                    completionHandler(.failure(error), httpResponse)
                }
            }
        }
    }

    /// Updates a thread's properties.
    /// - Parameters:
    ///    - chatThreadProperties : Request payload for updating a chat thread.
    ///    - chatThreadId : The id of the thread to update.
    ///    - options: A list of options for the operation
    ///    - completionHandler: A completion handler that receives a status code on
    ///     success.
    internal func update(
        chatThreadProperties: UpdateChatThreadRequestInternal,
        chatThreadId: String,
        withOptions options: UpdateChatThreadPropertiesOptions? = nil,
        completionHandler: @escaping HTTPResultHandler<Void>
    ) {
        let dispatchQueue = options?.dispatchQueue ?? client.commonOptions.dispatchQueue ?? DispatchQueue.main

        // Create request parameters
        let params = RequestParameters(
            (.path, "chatThreadId", chatThreadId, .encode),
            (.uri, "endpoint", client.endpoint.absoluteString, .skipEncoding),
            (.query, "api-version", client.options.apiVersion, .encode),
            (.header, "Content-Type", "application/merge-patch+json", .encode),
            (.header, "Accept", "application/json", .encode)
        )

        // Construct request
        guard let requestBody = try? JSONEncoder().encode(chatThreadProperties) else {
            client.options.logger.error("Failed to encode request body as json.")
            return
        }
        let urlTemplate = "/chat/threads/{chatThreadId}"
        guard let requestUrl = client.url(host: "{endpoint}", template: urlTemplate, params: params),
              let request = try? HTTPRequest(
                  method: .patch,
                  url: requestUrl,
                  headers: params.headers,
                  data: requestBody
              )
        else {
            client.options.logger.error("Failed to construct HTTP request.")
            return
        }

        // Send request
        let context = PipelineContext.of(keyValues: [
            ContextKey.allowedStatusCodes.rawValue: [204, 401, 403, 429, 503] as AnyObject
        ])
        context.add(cancellationToken: options?.cancellationToken, applying: client.options)
        context.merge(with: options?.context)
        client.request(request, context: context) { result, httpResponse in
            guard let data = httpResponse?.data else {
                let noDataError = AzureError.client("Response data expected but not found.")
                dispatchQueue.async {
                    completionHandler(.failure(noDataError), httpResponse)
                }
                return
            }
            switch result {
            case .success:
                guard let statusCode = httpResponse?.statusCode else {
                    let noStatusCodeError = AzureError.client("Expected a status code in response but didn't find one.")
                    dispatchQueue.async {
                        completionHandler(.failure(noStatusCodeError), httpResponse)
                    }
                    return
                }
                if [
                    204
                ].contains(statusCode) {
                    dispatchQueue.async {
                        completionHandler(
                            .success(()),
                            httpResponse
                        )
                    }
                }
                if [401].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Unauthorized.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [403].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Forbidden.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [429].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Too many requests.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [503].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(
                                .failure(AzureError.service("Service unavailable.", decoded)),
                                httpResponse
                            )
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
            case let .failure(error):
                dispatchQueue.async {
                    completionHandler(.failure(error), httpResponse)
                }
            }
        }
    }

    /// Gets a chat thread's properties.
    /// - Parameters:
    ///    - chatThreadId : Id of the thread.
    ///    - options: A list of options for the operation
    ///    - completionHandler: A completion handler that receives a status code on
    ///     success.
    internal func getChatThreadProperties(
        chatThreadId: String,
        withOptions options: GetChatThreadPropertiesOptions? = nil,
        completionHandler: @escaping HTTPResultHandler<ChatThreadPropertiesInternal>
    ) {
        let dispatchQueue = options?.dispatchQueue ?? client.commonOptions.dispatchQueue ?? DispatchQueue.main

        // Create request parameters
        let params = RequestParameters(
            (.path, "chatThreadId", chatThreadId, .encode),
            (.uri, "endpoint", client.endpoint.absoluteString, .skipEncoding),
            (.query, "api-version", client.options.apiVersion, .encode),
            (.header, "Accept", "application/json", .encode)
        )

        // Construct request
        let urlTemplate = "/chat/threads/{chatThreadId}"
        guard let requestUrl = client.url(host: "{endpoint}", template: urlTemplate, params: params),
              let request = try? HTTPRequest(method: .get, url: requestUrl, headers: params.headers)
        else {
            client.options.logger.error("Failed to construct HTTP request.")
            return
        }

        // Send request
        let context = PipelineContext.of(keyValues: [
            ContextKey.allowedStatusCodes.rawValue: [200, 401, 403, 429, 503] as AnyObject
        ])
        context.add(cancellationToken: options?.cancellationToken, applying: client.options)
        context.merge(with: options?.context)
        client.request(request, context: context) { result, httpResponse in
            guard let data = httpResponse?.data else {
                let noDataError = AzureError.client("Response data expected but not found.")
                dispatchQueue.async {
                    completionHandler(.failure(noDataError), httpResponse)
                }
                return
            }
            switch result {
            case .success:
                guard let statusCode = httpResponse?.statusCode else {
                    let noStatusCodeError = AzureError.client("Expected a status code in response but didn't find one.")
                    dispatchQueue.async {
                        completionHandler(.failure(noStatusCodeError), httpResponse)
                    }
                    return
                }
                if [
                    200
                ].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(ChatThreadPropertiesInternal.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.success(decoded), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [401].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Unauthorized.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [403].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Forbidden.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [429].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Too many requests.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [503].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(
                                .failure(AzureError.service("Service unavailable.", decoded)),
                                httpResponse
                            )
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
            case let .failure(error):
                dispatchQueue.async {
                    completionHandler(.failure(error), httpResponse)
                }
            }
        }
    }

    /// Posts a typing event to a thread, on behalf of a user.
    /// - Parameters:
    ///    - typingNotification : Details of the typing notification request.
    ///    - chatThreadId : Id of the thread.
    ///    - options: A list of options for the operation
    ///    - completionHandler: A completion handler that receives a status code on
    ///     success.
    internal func send(
        typingNotification: SendTypingNotificationRequest?,
        chatThreadId: String,
        withOptions options: SendTypingNotificationOptions? = nil,
        completionHandler: @escaping HTTPResultHandler<Void>
    ) {
        let dispatchQueue = options?.dispatchQueue ?? client.commonOptions.dispatchQueue ?? DispatchQueue.main

        // Create request parameters
        let params = RequestParameters(
            (.path, "chatThreadId", chatThreadId, .encode),
            (.uri, "endpoint", client.endpoint.absoluteString, .skipEncoding),
            (.query, "api-version", client.options.apiVersion, .encode),
            (.header, "Content-Type", "application/json", .encode), (.header, "Accept", "application/json", .encode)
        )

        // Construct request
        var requestBody: Data?
        if typingNotification != nil {
            guard let encodedRequestBody = try? JSONEncoder().encode(typingNotification) else {
                client.options.logger.error("Failed to encode request body as json.")
                return
            }
            requestBody = encodedRequestBody
        }
        let urlTemplate = "/chat/threads/{chatThreadId}/typing"
        guard let requestUrl = client.url(host: "{endpoint}", template: urlTemplate, params: params),
              let request = try? HTTPRequest(method: .post, url: requestUrl, headers: params.headers, data: requestBody)
        else {
            client.options.logger.error("Failed to construct HTTP request.")
            return
        }

        // Send request
        let context = PipelineContext.of(keyValues: [
            ContextKey.allowedStatusCodes.rawValue: [200, 401, 403, 429, 503] as AnyObject
        ])
        context.add(cancellationToken: options?.cancellationToken, applying: client.options)
        context.merge(with: options?.context)
        client.request(request, context: context) { result, httpResponse in
            guard let data = httpResponse?.data else {
                let noDataError = AzureError.client("Response data expected but not found.")
                dispatchQueue.async {
                    completionHandler(.failure(noDataError), httpResponse)
                }
                return
            }
            switch result {
            case .success:
                guard let statusCode = httpResponse?.statusCode else {
                    let noStatusCodeError = AzureError.client("Expected a status code in response but didn't find one.")
                    dispatchQueue.async {
                        completionHandler(.failure(noStatusCodeError), httpResponse)
                    }
                    return
                }
                if [
                    200
                ].contains(statusCode) {
                    dispatchQueue.async {
                        completionHandler(
                            .success(()),
                            httpResponse
                        )
                    }
                }
                if [401].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Unauthorized.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [403].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Forbidden.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [429].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.service("Too many requests.", decoded)), httpResponse)
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
                if [503].contains(statusCode) {
                    do {
                        let decoder = JSONDecoder()
                        let decoded = try decoder.decode(CommunicationErrorResponse.self, from: data)
                        dispatchQueue.async {
                            completionHandler(
                                .failure(AzureError.service("Service unavailable.", decoded)),
                                httpResponse
                            )
                        }
                    } catch {
                        dispatchQueue.async {
                            completionHandler(.failure(AzureError.client("Decoding error.", error)), httpResponse)
                        }
                    }
                }
            case let .failure(error):
                dispatchQueue.async {
                    completionHandler(.failure(error), httpResponse)
                }
            }
        }
    }
}
