<?php

/*
 * Copyright 2018 Google LLC
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
namespace SEOPressPro\Vendor\Google\ApiCore;

use SEOPressPro\Vendor\Google\ApiCore\LongRunning\OperationsClient;
use SEOPressPro\Vendor\Google\ApiCore\Middleware\CredentialsWrapperMiddleware;
use SEOPressPro\Vendor\Google\ApiCore\Middleware\FixedHeaderMiddleware;
use SEOPressPro\Vendor\Google\ApiCore\Middleware\OperationsMiddleware;
use SEOPressPro\Vendor\Google\ApiCore\Middleware\OptionsFilterMiddleware;
use SEOPressPro\Vendor\Google\ApiCore\Middleware\PagedMiddleware;
use SEOPressPro\Vendor\Google\ApiCore\Middleware\RetryMiddleware;
use SEOPressPro\Vendor\Google\ApiCore\Options\CallOptions;
use SEOPressPro\Vendor\Google\ApiCore\Options\ClientOptions;
use SEOPressPro\Vendor\Google\ApiCore\Options\TransportOptions;
use SEOPressPro\Vendor\Google\ApiCore\Transport\GrpcFallbackTransport;
use SEOPressPro\Vendor\Google\ApiCore\Transport\GrpcTransport;
use SEOPressPro\Vendor\Google\ApiCore\Transport\RestTransport;
use SEOPressPro\Vendor\Google\ApiCore\Transport\TransportInterface;
use SEOPressPro\Vendor\Google\Auth\FetchAuthTokenInterface;
use SEOPressPro\Vendor\Google\LongRunning\Operation;
use SEOPressPro\Vendor\Google\Protobuf\Internal\Message;
use SEOPressPro\Vendor\GuzzleHttp\Promise\PromiseInterface;
/**
 * Common functions used to work with various clients.
 *
 * @internal
 */
trait GapicClientTrait
{
    use ClientOptionsTrait;
    use ValidationTrait {
        ValidationTrait::validate as traitValidate;
    }
    use GrpcSupportTrait;
    private ?TransportInterface $transport = null;
    private ?CredentialsWrapper $credentialsWrapper = null;
    /** @var RetrySettings[] $retrySettings */
    private array $retrySettings = [];
    private string $serviceName = '';
    private array $agentHeader = [];
    private array $descriptors = [];
    /** @var array<callable> $middlewareCallables */
    private array $middlewareCallables = [];
    private array $transportCallMethods = [Call::UNARY_CALL => 'startUnaryCall', Call::BIDI_STREAMING_CALL => 'startBidiStreamingCall', Call::CLIENT_STREAMING_CALL => 'startClientStreamingCall', Call::SERVER_STREAMING_CALL => 'startServerStreamingCall'];
    private bool $backwardsCompatibilityMode;
    /**
     * Add a middleware to the call stack by providing a callable which will be
     * invoked at the start of each call, and will return an instance of
     * {@see MiddlewareInterface} when invoked.
     *
     * The callable must have the following method signature:
     *
     *     callable(MiddlewareInterface): MiddlewareInterface
     *
     * An implementation may look something like this:
     * ```
     * $client->addMiddleware(function (MiddlewareInterface $handler) {
     *     return new class ($handler) implements MiddlewareInterface {
     *         public function __construct(private MiddlewareInterface $handler) {
     *         }
     *
     *         public function __invoke(Call $call, array $options) {
     *             // modify call and options (pre-request)
     *             $response = ($this->handler)($call, $options);
     *             // modify the response (post-request)
     *             return $response;
     *         }
     *     };
     * });
     * ```
     *
     * @param callable $middlewareCallable A callable which returns an instance
     *                 of {@see MiddlewareInterface} when invoked with a
     *                 MiddlewareInterface instance as its first argument.
     * @return void
     */
    public function addMiddleware(callable $middlewareCallable): void
    {
        $this->middlewareCallables[] = $middlewareCallable;
    }
    /**
     * Initiates an orderly shutdown in which preexisting calls continue but new
     * calls are immediately cancelled.
     *
     * @experimental
     */
    public function close()
    {
        $this->transport->close();
    }
    /**
     * Get the transport for the client. This method is protected to support
     * use by customized clients.
     *
     * @access private
     * @return TransportInterface
     */
    protected function getTransport()
    {
        return $this->transport;
    }
    /**
     * Get the credentials for the client. This method is protected to support
     * use by customized clients.
     *
     * @access private
     * @return CredentialsWrapper
     */
    protected function getCredentialsWrapper()
    {
        return $this->credentialsWrapper;
    }
    /**
     * Configures the GAPIC client based on an array of options.
     *
     * @param array $options {
     *     An array of required and optional arguments.
     *
     *     @type string $apiEndpoint
     *           The address of the API remote host, for example "example.googleapis.com. May also
     *           include the port, for example "example.googleapis.com:443"
     *     @type bool $disableRetries
     *           Determines whether or not retries defined by the client configuration should be
     *           disabled. Defaults to `false`.
     *     @type string|array $clientConfig
     *           Client method configuration, including retry settings. This option can be either a
     *           path to a JSON file, or a PHP array containing the decoded JSON data.
     *           By default this settings points to the default client config file, which is provided
     *           in the resources folder.
     *     @type string|array|FetchAuthTokenInterface|CredentialsWrapper $credentials
     *           The credentials to be used by the client to authorize API calls. This option
     *           accepts either a path to a credentials file, or a decoded credentials file as a
     *           PHP array.
     *           *Advanced usage*: In addition, this option can also accept a pre-constructed
     *           \Google\Auth\FetchAuthTokenInterface object or \Google\ApiCore\CredentialsWrapper
     *           object. Note that when one of these objects are provided, any settings in
     *           $authConfig will be ignored.
     *     @type array $credentialsConfig
     *           Options used to configure credentials, including auth token caching, for the client.
     *           For a full list of supporting configuration options, see
     *           \Google\ApiCore\CredentialsWrapper::build.
     *     @type string|TransportInterface $transport
     *           The transport used for executing network requests. May be either the string `rest`,
     *           `grpc`, or 'grpc-fallback'. Defaults to `grpc` if gRPC support is detected on the system.
     *           *Advanced usage*: Additionally, it is possible to pass in an already instantiated
     *           TransportInterface object. Note that when this objects is provided, any settings in
     *           $transportConfig, and any `$apiEndpoint` setting, will be ignored.
     *     @type array $transportConfig
     *           Configuration options that will be used to construct the transport. Options for
     *           each supported transport type should be passed in a key for that transport. For
     *           example:
     *           $transportConfig = [
     *               'grpc' => [...],
     *               'rest' => [...],
     *               'grpc-fallback' => [...],
     *           ];
     *           See the GrpcTransport::build and RestTransport::build
     *           methods for the supported options.
     *     @type string $versionFile
     *           The path to a file which contains the current version of the client.
     *     @type string $descriptorsConfigPath
     *           The path to a descriptor configuration file.
     *     @type string $serviceName
     *           The name of the service.
     *     @type string $libName
     *           The name of the client application.
     *     @type string $libVersion
     *           The version of the client application.
     *     @type string $gapicVersion
     *           The code generator version of the GAPIC library.
     *     @type callable $clientCertSource
     *           A callable which returns the client cert as a string.
     * }
     * @throws ValidationException
     */
    private function setClientOptions(array $options)
    {
        // serviceAddress is now deprecated and acts as an alias for apiEndpoint
        if (isset($options['serviceAddress'])) {
            $options['apiEndpoint'] = $this->pluck('serviceAddress', $options, \false);
        }
        $this->validateNotNull($options, ['apiEndpoint', 'serviceName', 'descriptorsConfigPath', 'clientConfig', 'disableRetries', 'credentialsConfig', 'transportConfig']);
        $this->traitValidate($options, ['credentials', 'transport', 'gapicVersion', 'libName', 'libVersion']);
        if ($this->isBackwardsCompatibilityMode()) {
            if (is_string($options['clientConfig'])) {
                // perform validation for V1 surfaces which is done in the
                // ClientOptions class for v2 surfaces.
                $options['clientConfig'] = json_decode(file_get_contents($options['clientConfig']), \true);
                self::validateFileExists($options['descriptorsConfigPath']);
            }
        } else {
            // cast to ClientOptions for new surfaces only
            $options = new ClientOptions($options);
        }
        $this->serviceName = $options['serviceName'];
        $this->retrySettings = RetrySettings::load($this->serviceName, $options['clientConfig'], $options['disableRetries']);
        $headerInfo = ['libName' => $options['libName'], 'libVersion' => $options['libVersion'], 'gapicVersion' => $options['gapicVersion']];
        // Edge case: If the client has the gRPC extension installed, but is
        // a REST-only library, then the grpcVersion header should not be set.
        if ($this->transport instanceof GrpcTransport) {
            $headerInfo['grpcVersion'] = phpversion('grpc');
        } elseif ($this->transport instanceof RestTransport || $this->transport instanceof GrpcFallbackTransport) {
            $headerInfo['restVersion'] = Version::getApiCoreVersion();
        }
        $this->agentHeader = AgentHeader::buildAgentHeader($headerInfo);
        // Set "client_library_name" depending on client library surface being used
        $userAgentHeader = sprintf('gcloud-php-%s/%s', $this->isBackwardsCompatibilityMode() ? 'legacy' : 'new', $options['gapicVersion']);
        $this->agentHeader['User-Agent'] = [$userAgentHeader];
        self::validateFileExists($options['descriptorsConfigPath']);
        $descriptors = require $options['descriptorsConfigPath'];
        $this->descriptors = $descriptors['interfaces'][$this->serviceName];
        $this->credentialsWrapper = $this->createCredentialsWrapper($options['credentials'], $options['credentialsConfig'], $options['universeDomain']);
        $transport = $options['transport'] ?: self::defaultTransport();
        $this->transport = $transport instanceof TransportInterface ? $transport : $this->createTransport($options['apiEndpoint'], $transport, $options['transportConfig'], $options['clientCertSource']);
    }
    /**
     * @param string $apiEndpoint
     * @param string $transport
     * @param TransportOptions|array $transportConfig
     * @param callable $clientCertSource
     * @return TransportInterface
     * @throws ValidationException
     */
    private function createTransport(string $apiEndpoint, $transport, $transportConfig, ?callable $clientCertSource = null)
    {
        if (!is_string($transport)) {
            throw new ValidationException("'transport' must be a string, instead got:" . print_r($transport, \true));
        }
        $supportedTransports = self::supportedTransports();
        if (!in_array($transport, $supportedTransports)) {
            throw new ValidationException(sprintf('Unexpected transport option "%s". Supported transports: %s', $transport, implode(', ', $supportedTransports)));
        }
        $configForSpecifiedTransport = $transportConfig[$transport] ?? [];
        if (is_array($configForSpecifiedTransport)) {
            $configForSpecifiedTransport['clientCertSource'] = $clientCertSource;
        } else {
            $configForSpecifiedTransport->setClientCertSource($clientCertSource);
            $configForSpecifiedTransport = $configForSpecifiedTransport->toArray();
        }
        switch ($transport) {
            case 'grpc':
                // Setting the user agent for gRPC requires special handling
                if (isset($this->agentHeader['User-Agent'])) {
                    if ($configForSpecifiedTransport['stubOpts']['grpc.primary_user_agent'] ??= '') {
                        $configForSpecifiedTransport['stubOpts']['grpc.primary_user_agent'] .= ' ';
                    }
                    $configForSpecifiedTransport['stubOpts']['grpc.primary_user_agent'] .= $this->agentHeader['User-Agent'][0];
                }
                return GrpcTransport::build($apiEndpoint, $configForSpecifiedTransport);
            case 'grpc-fallback':
                return GrpcFallbackTransport::build($apiEndpoint, $configForSpecifiedTransport);
            case 'rest':
                if (!isset($configForSpecifiedTransport['restClientConfigPath'])) {
                    throw new ValidationException("The 'restClientConfigPath' config is required for 'rest' transport.");
                }
                $restConfigPath = $configForSpecifiedTransport['restClientConfigPath'];
                return RestTransport::build($apiEndpoint, $restConfigPath, $configForSpecifiedTransport);
            default:
                throw new ValidationException("Unexpected 'transport' option: {$transport}. " . "Supported values: ['grpc', 'rest', 'grpc-fallback']");
        }
    }
    /**
     * @param array $options
     * @return OperationsClient
     */
    private function createOperationsClient(array $options)
    {
        $this->pluckArray(['serviceName', 'clientConfig', 'descriptorsConfigPath'], $options);
        // User-supplied operations client
        if ($operationsClient = $this->pluck('operationsClient', $options, \false)) {
            return $operationsClient;
        }
        // operationsClientClass option
        $operationsClientClass = $this->pluck('operationsClientClass', $options, \false) ?: OperationsCLient::class;
        return new $operationsClientClass($options);
    }
    /**
     * @return string
     */
    private static function defaultTransport()
    {
        return self::getGrpcDependencyStatus() ? 'grpc' : 'rest';
    }
    private function validateCallConfig(string $methodName)
    {
        // Ensure a method descriptor exists for the target method.
        if (!isset($this->descriptors[$methodName])) {
            throw new ValidationException("Requested method '{$methodName}' does not exist in descriptor configuration.");
        }
        $methodDescriptors = $this->descriptors[$methodName];
        // Ensure required descriptor configuration exists.
        if (!isset($methodDescriptors['callType'])) {
            throw new ValidationException("Requested method '{$methodName}' does not have a callType " . 'in descriptor configuration.');
        }
        $callType = $methodDescriptors['callType'];
        // Validate various callType specific configurations.
        if ($callType == Call::LONGRUNNING_CALL) {
            if (!isset($methodDescriptors['longRunning'])) {
                throw new ValidationException("Requested method '{$methodName}' does not have a longRunning config " . 'in descriptor configuration.');
            }
            // @TODO: check if the client implements `OperationsClientInterface` instead
            if (!method_exists($this, 'getOperationsClient')) {
                throw new ValidationException('Client missing required getOperationsClient ' . "for longrunning call '{$methodName}'");
            }
        } elseif ($callType == Call::PAGINATED_CALL) {
            if (!isset($methodDescriptors['pageStreaming'])) {
                throw new ValidationException("Requested method '{$methodName}' with callType PAGINATED_CALL does not " . 'have a pageStreaming in descriptor configuration.');
            }
        }
        // LRO are either Standard LRO response type or custom, which are handled by
        // startOperationCall, so no need to validate responseType for those callType.
        if ($callType != Call::LONGRUNNING_CALL) {
            if (!isset($methodDescriptors['responseType'])) {
                throw new ValidationException("Requested method '{$methodName}' does not have a responseType " . 'in descriptor configuration.');
            }
        }
        return $methodDescriptors;
    }
    /**
     * @param string $methodName
     * @param Message $request
     * @param array $optionalArgs {
     *     Call Options
     *
     *     @type array $headers                     [optional] key-value array containing headers
     *     @type int $timeoutMillis                 [optional] the timeout in milliseconds for the call
     *     @type array $transportOptions            [optional] transport-specific call options
     *     @type RetrySettings|array $retrySettings [optional] A retry settings override for the call.
     * }
     *
     * @experimental
     *
     * @return PromiseInterface
     */
    private function startAsyncCall(string $methodName, Message $request, array $optionalArgs = [])
    {
        // Convert method name to the UpperCamelCase of RPC names from lowerCamelCase of GAPIC method names
        // in order to find the method in the descriptor config.
        $methodName = ucfirst($methodName);
        $methodDescriptors = $this->validateCallConfig($methodName);
        $callType = $methodDescriptors['callType'];
        switch ($callType) {
            case Call::PAGINATED_CALL:
                return $this->getPagedListResponseAsync($methodName, $optionalArgs, $methodDescriptors['responseType'], $request, $methodDescriptors['interfaceOverride'] ?? $this->serviceName);
            case Call::SERVER_STREAMING_CALL:
            case Call::CLIENT_STREAMING_CALL:
            case Call::BIDI_STREAMING_CALL:
                throw new ValidationException("Call type '{$callType}' of requested method " . "'{$methodName}' is not supported for async execution.");
        }
        return $this->startApiCall($methodName, $request, $optionalArgs);
    }
    /**
     * @param string $methodName
     * @param Message $request
     * @param array $optionalArgs {
     *     Call Options
     *
     *     @type array $headers [optional] key-value array containing headers
     *     @type int $timeoutMillis [optional] the timeout in milliseconds for the call
     *     @type array $transportOptions [optional] transport-specific call options
     *     @type RetrySettings|array $retrySettings [optional] A retry settings
     *           override for the call.
     * }
     *
     * @experimental
     *
     * @return PromiseInterface|PagedListResponse|BidiStream|ClientStream|ServerStream
     */
    private function startApiCall(string $methodName, ?Message $request = null, array $optionalArgs = [])
    {
        $methodDescriptors = $this->validateCallConfig($methodName);
        $callType = $methodDescriptors['callType'];
        // Prepare request-based headers, merge with user-provided headers,
        // which take precedence.
        $headerParams = $methodDescriptors['headerParams'] ?? [];
        $requestHeaders = $this->buildRequestParamsHeader($headerParams, $request);
        $optionalArgs['headers'] = array_merge($requestHeaders, $optionalArgs['headers'] ?? []);
        // Default the interface name, if not set, to the client's protobuf service name.
        $interfaceName = $methodDescriptors['interfaceOverride'] ?? $this->serviceName;
        // Handle call based on call type configured in the method descriptor config.
        if ($callType == Call::LONGRUNNING_CALL) {
            return $this->startOperationsCall(
                $methodName,
                $optionalArgs,
                $request,
                $this->getOperationsClient(),
                $interfaceName,
                // Custom operations will define their own operation response type, whereas standard
                // LRO defaults to the same type.
                $methodDescriptors['responseType'] ?? null
            );
        }
        // Fully-qualified name of the response message PHP class.
        $decodeType = $methodDescriptors['responseType'];
        if ($callType == Call::PAGINATED_CALL) {
            return $this->getPagedListResponse($methodName, $optionalArgs, $decodeType, $request, $interfaceName);
        }
        // Unary, and all Streaming types handled by startCall.
        return $this->startCall($methodName, $decodeType, $optionalArgs, $request, $callType, $interfaceName);
    }
    /**
     * @param string $methodName
     * @param string $decodeType
     * @param array $optionalArgs {
     *     Call Options
     *
     *     @type array $headers [optional] key-value array containing headers
     *     @type int $timeoutMillis [optional] the timeout in milliseconds for the call
     *     @type array $transportOptions [optional] transport-specific call options
     *     @type RetrySettings|array $retrySettings [optional] A retry settings
     *           override for the call.
     * }
     * @param Message $request
     * @param int $callType
     * @param string $interfaceName
     *
     * @return PromiseInterface|BidiStream|ClientStream|ServerStream
     */
    private function startCall(string $methodName, string $decodeType, array $optionalArgs = [], ?Message $request = null, int $callType = Call::UNARY_CALL, ?string $interfaceName = null)
    {
        $optionalArgs = $this->configureCallOptions($optionalArgs);
        $callStack = $this->createCallStack($this->configureCallConstructionOptions($methodName, $optionalArgs));
        $descriptor = $this->descriptors[$methodName]['grpcStreaming'] ?? null;
        $call = new Call($this->buildMethod($interfaceName, $methodName), $decodeType, $request, $descriptor, $callType);
        switch ($callType) {
            case Call::UNARY_CALL:
                $this->modifyUnaryCallable($callStack);
                break;
            case Call::BIDI_STREAMING_CALL:
            case Call::CLIENT_STREAMING_CALL:
            case Call::SERVER_STREAMING_CALL:
                $this->modifyStreamingCallable($callStack);
                break;
        }
        return $callStack($call, $optionalArgs + array_filter(['audience' => self::getDefaultAudience()]));
    }
    /**
     * @param array $callConstructionOptions {
     *     Call Construction Options
     *
     *     @type RetrySettings $retrySettings [optional] A retry settings override
     *           For the call.
     * }
     *
     * @return callable
     */
    private function createCallStack(array $callConstructionOptions)
    {
        $quotaProject = $this->credentialsWrapper->getQuotaProject();
        $fixedHeaders = $this->agentHeader;
        if ($quotaProject) {
            $fixedHeaders += ['X-Goog-User-Project' => [$quotaProject]];
        }
        $callStack = function (Call $call, array $options) {
            $startCallMethod = $this->transportCallMethods[$call->getCallType()];
            return $this->transport->{$startCallMethod}($call, $options);
        };
        $callStack = new CredentialsWrapperMiddleware($callStack, $this->credentialsWrapper);
        $callStack = new FixedHeaderMiddleware($callStack, $fixedHeaders, \true);
        $callStack = new RetryMiddleware($callStack, $callConstructionOptions['retrySettings']);
        $callStack = new OptionsFilterMiddleware($callStack, ['headers', 'timeoutMillis', 'transportOptions', 'metadataCallback', 'audience', 'metadataReturnType']);
        foreach (\array_reverse($this->middlewareCallables) as $fn) {
            /** @var MiddlewareInterface $callStack */
            $callStack = $fn($callStack);
        }
        return $callStack;
    }
    /**
     * @param string $methodName
     * @param array $optionalArgs {
     *     Optional arguments
     *
     *     @type RetrySettings|array $retrySettings [optional] A retry settings
     *           override for the call.
     * }
     *
     * @return array
     */
    private function configureCallConstructionOptions(string $methodName, array $optionalArgs)
    {
        $retrySettings = $this->retrySettings[$methodName];
        // Allow for retry settings to be changed at call time
        if (isset($optionalArgs['retrySettings'])) {
            if ($optionalArgs['retrySettings'] instanceof RetrySettings) {
                $retrySettings = $optionalArgs['retrySettings'];
            } else {
                $retrySettings = $retrySettings->with($optionalArgs['retrySettings']);
            }
        }
        return ['retrySettings' => $retrySettings];
    }
    /**
     * @return array
     */
    private function configureCallOptions(array $optionalArgs): array
    {
        if ($this->isBackwardsCompatibilityMode()) {
            return $optionalArgs;
        }
        // cast to CallOptions for new surfaces only
        return (new CallOptions($optionalArgs))->toArray();
    }
    /**
     * @param string $methodName
     * @param array $optionalArgs {
     *     Call Options
     *
     *     @type array $headers [optional] key-value array containing headers
     *     @type int $timeoutMillis [optional] the timeout in milliseconds for the call
     *     @type array $transportOptions [optional] transport-specific call options
     * }
     * @param Message $request
     * @param OperationsClient|object $client
     * @param string $interfaceName
     * @param string $operationClass If provided, will be used instead of the default
     *                               operation response class of {@see \Google\LongRunning\Operation}.
     *
     * @return PromiseInterface
     */
    private function startOperationsCall(string $methodName, array $optionalArgs, Message $request, $client, ?string $interfaceName = null, ?string $operationClass = null)
    {
        $optionalArgs = $this->configureCallOptions($optionalArgs);
        $callStack = $this->createCallStack($this->configureCallConstructionOptions($methodName, $optionalArgs));
        $descriptor = $this->descriptors[$methodName]['longRunning'];
        $metadataReturnType = null;
        // Call the methods supplied in "additionalArgumentMethods" on the request Message object
        // to build the "additionalOperationArguments" option for the operation response.
        if (isset($descriptor['additionalArgumentMethods'])) {
            $additionalArgs = [];
            foreach ($descriptor['additionalArgumentMethods'] as $additionalArgsMethodName) {
                $additionalArgs[] = $request->{$additionalArgsMethodName}();
            }
            $descriptor['additionalOperationArguments'] = $additionalArgs;
            unset($descriptor['additionalArgumentMethods']);
        }
        if (isset($descriptor['metadataReturnType'])) {
            $metadataReturnType = $descriptor['metadataReturnType'];
        }
        $callStack = new OperationsMiddleware($callStack, $client, $descriptor);
        $call = new Call($this->buildMethod($interfaceName, $methodName), $operationClass ?: Operation::class, $request, [], Call::UNARY_CALL);
        $this->modifyUnaryCallable($callStack);
        return $callStack($call, $optionalArgs + array_filter(['metadataReturnType' => $metadataReturnType, 'audience' => self::getDefaultAudience()]));
    }
    /**
     * @param string $methodName
     * @param array $optionalArgs
     * @param string $decodeType
     * @param Message $request
     * @param string $interfaceName
     *
     * @return PagedListResponse
     */
    private function getPagedListResponse(string $methodName, array $optionalArgs, string $decodeType, Message $request, ?string $interfaceName = null)
    {
        return $this->getPagedListResponseAsync($methodName, $optionalArgs, $decodeType, $request, $interfaceName)->wait();
    }
    /**
     * @param string $methodName
     * @param array $optionalArgs
     * @param string $decodeType
     * @param Message $request
     * @param string $interfaceName
     *
     * @return PromiseInterface
     */
    private function getPagedListResponseAsync(string $methodName, array $optionalArgs, string $decodeType, Message $request, ?string $interfaceName = null)
    {
        $optionalArgs = $this->configureCallOptions($optionalArgs);
        $callStack = $this->createCallStack($this->configureCallConstructionOptions($methodName, $optionalArgs));
        $descriptor = new PageStreamingDescriptor($this->descriptors[$methodName]['pageStreaming']);
        $callStack = new PagedMiddleware($callStack, $descriptor);
        $call = new Call($this->buildMethod($interfaceName, $methodName), $decodeType, $request, [], Call::UNARY_CALL);
        $this->modifyUnaryCallable($callStack);
        return $callStack($call, $optionalArgs + array_filter(['audience' => self::getDefaultAudience()]));
    }
    /**
     * @param string $interfaceName
     * @param string $methodName
     *
     * @return string
     */
    private function buildMethod(?string $interfaceName = null, ?string $methodName = null)
    {
        return sprintf('%s/%s', $interfaceName ?: $this->serviceName, $methodName);
    }
    /**
     * @param array $headerParams
     * @param Message|null $request
     *
     * @return array
     */
    private function buildRequestParamsHeader(array $headerParams, ?Message $request = null)
    {
        $headers = [];
        // No request message means no request-based headers.
        if (!$request) {
            return $headers;
        }
        foreach ($headerParams as $headerParam) {
            $msg = $request;
            $value = null;
            foreach ($headerParam['fieldAccessors'] as $accessor) {
                $value = $msg->{$accessor}();
                // In case the field in question is nested in another message,
                // skip the header param when the nested message field is unset.
                $msg = $value;
                if (is_null($msg)) {
                    break;
                }
            }
            $keyName = $headerParam['keyName'];
            // If there are value pattern matchers configured and the target
            // field was set, evaluate the matchers in the order that they were
            // annotated in with last one matching wins.
            $original = $value;
            $matchers = isset($headerParam['matchers']) && !is_null($value) ? $headerParam['matchers'] : [];
            foreach ($matchers as $matcher) {
                $matches = [];
                if (preg_match($matcher, $original, $matches)) {
                    $value = $matches[$keyName];
                }
            }
            // If there are no matches or the target field was unset, skip this
            // header param.
            if (!$value) {
                continue;
            }
            $headers[$keyName] = $value;
        }
        $requestParams = new RequestParamsHeaderDescriptor($headers);
        return $requestParams->getHeader();
    }
    /**
     * The SERVICE_ADDRESS constant is set by GAPIC clients
     */
    private static function getDefaultAudience()
    {
        if (!defined('SEOPressPro\Vendor\self::SERVICE_ADDRESS')) {
            return null;
        }
        return 'https://' . self::SERVICE_ADDRESS . '/';
        // @phpstan-ignore-line
    }
    /**
     * Modify the unary callable.
     *
     * @param callable $callable
     * @access private
     */
    protected function modifyUnaryCallable(callable &$callable)
    {
        // Do nothing - this method exists to allow callable modification by partial veneers.
    }
    /**
     * Modify the streaming callable.
     *
     * @param callable $callable
     * @access private
     */
    protected function modifyStreamingCallable(callable &$callable)
    {
        // Do nothing - this method exists to allow callable modification by partial veneers.
    }
    /**
     * @internal
     */
    private function isBackwardsCompatibilityMode(): bool
    {
        return $this->backwardsCompatibilityMode ?? $this->backwardsCompatibilityMode = substr(__CLASS__, -11) === 'GapicClient';
    }
}
