// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { deviceManagerDeclaratify } from './DeviceManagerDeclarative';
import { CallClient, Features } from '@azure/communication-calling';
import { CallContext } from './CallContext';
import { callAgentDeclaratify } from './CallAgentDeclarative';
import { InternalCallContext } from './InternalCallContext';
import { createView, disposeView } from './StreamUtils';
import { getIdentifierKind } from '@azure/communication-common';
import { toFlatCommunicationIdentifier, _getApplicationId } from "../../acs-ui-common/src";
import { callingStatefulLogger } from './Logger';
import { teamsCallAgentDeclaratify } from './TeamsCallAgentDeclarative';
import { videoStreamRendererViewDeclaratify } from './VideoStreamRendererViewDeclarative';
import { createView as createCallFeatureView, disposeView as disposeCallFeatureView } from './CallFeatureStreamUtils';
/**
 * ProxyCallClient proxies CallClient {@link @azure/communication-calling#CallClient} and subscribes to all events that
 * affect state. ProxyCallClient keeps its own copy of the call state and when state is updated, ProxyCallClient emits
 * the event 'stateChanged'.
 */
class ProxyCallClient {
    constructor(context, internalContext) {
        this._context = context;
        this._internalContext = internalContext;
    }
    get(target, prop) {
        switch (prop) {
            case 'createCallAgent':
                {
                    return this._context.withAsyncErrorTeedToState((...args) => __awaiter(this, void 0, void 0, function* () {
                        // createCallAgent will throw an exception if the previous callAgent was not disposed. If the previous
                        // callAgent was disposed then it would have unsubscribed to events so we can just create a new declarative
                        // callAgent if the createCallAgent succeeds.
                        const callAgent = yield target.createCallAgent(...args);
                        this._callAgent = callAgentDeclaratify(callAgent, this._context, this._internalContext);
                        this._context.setCallAgent({
                            displayName: this._callAgent.displayName
                        });
                        return this._callAgent;
                    }), 'CallClient.createCallAgent');
                }
            case 'createTeamsCallAgent':
                {
                    return this._context.withAsyncErrorTeedToState((...args) => __awaiter(this, void 0, void 0, function* () {
                        // createCallAgent will throw an exception if the previous callAgent was not disposed. If the previous
                        // callAgent was disposed then it would have unsubscribed to events so we can just create a new declarative
                        // callAgent if the createCallAgent succeeds.
                        const callAgent = yield target.createTeamsCallAgent(...args);
                        this._callAgent = teamsCallAgentDeclaratify(callAgent, this._context, this._internalContext);
                        this._context.setCallAgent({
                            displayName: undefined
                        });
                        return this._callAgent;
                    }), 'CallClient.createTeamsCallAgent');
                }
            case 'getDeviceManager':
                {
                    return this._context.withAsyncErrorTeedToState(() => __awaiter(this, void 0, void 0, function* () {
                        // As of writing, the SDK always returns the same instance of DeviceManager so we keep a reference of
                        // DeviceManager and if it does not change we return the cached DeclarativeDeviceManager. If it does not we'll
                        // throw an error that indicate we need to fix this issue as our implementation has diverged from the SDK.
                        const deviceManager = yield target.getDeviceManager();
                        if (this._sdkDeviceManager) {
                            if (this._sdkDeviceManager === deviceManager) {
                                return this._deviceManager;
                            }
                            else {
                                throw new Error('Multiple DeviceManager not supported. This means a incompatible version of communication-calling is ' + 'used OR calling declarative was not properly updated to communication-calling version.');
                            }
                        }
                        else {
                            this._sdkDeviceManager = deviceManager;
                        }
                        this._deviceManager = deviceManagerDeclaratify(deviceManager, this._context, this._internalContext);
                        return this._deviceManager;
                    }), 'CallClient.getDeviceManager');
                }
            case 'feature':
                {
                    return this._context.withErrorTeedToState((...args) => {
                        if (args[0] === Features.DebugInfo) {
                            const feature = target.feature(Features.DebugInfo);
                            /**
                             * add to this object if we want to proxy anything else off the DebugInfo feature object.
                             */
                            return Object.assign(Object.assign({}, feature), { getEnvironmentInfo: () => __awaiter(this, void 0, void 0, function* () {
                                    const environmentInfo = yield feature.getEnvironmentInfo();
                                    this._context.setEnvironmentInfo(environmentInfo);
                                    return environmentInfo;
                                }) });
                        }
                        return Reflect.get(target, prop);
                    }, 'CallClient.feature');
                }
            default:
                return Reflect.get(target, prop);
        }
    }
}
/**
 * Creates a StatefulCallClient {@link StatefulCallClient} by proxying CallClient
 * {@link @azure/communication-calling#CallClient} with ProxyCallClient {@link ProxyCallClient} which then allows access
 * to state in a declarative way.
 *
 * It is important to use the {@link @azure/communication-calling#DeviceManager} and
 * {@link @azure/communication-calling#CallAgent} and {@link @azure/communication-calling#Call} (and etc.) that are
 * obtained from the StatefulCallClient in order for their state changes to be proxied properly.
 *
 * @param args - {@link StatefulCallClientArgs}
 * @param options - {@link StatefulCallClientOptions}
 *
 * @public
 */
export const createStatefulCallClient = (args, options) => {
    return _createStatefulCallClientInner(args, options);
};
/**
 * This inner function is used to allow injection of TelemetryImplementationHint without changing the public API.
 *
 * @internal
 */
export const _createStatefulCallClientInner = (args, options, telemetryImplementationHint = 'StatefulComponents') => {
    callingStatefulLogger.info(`Creating calling stateful client using library version: ${_getApplicationId(telemetryImplementationHint)}`);
    return createStatefulCallClientWithDeps(new CallClient(withTelemetryTag(telemetryImplementationHint, options === null || options === void 0 ? void 0 : options.callClientOptions)), new CallContext(getIdentifierKind(args.userId), options === null || options === void 0 ? void 0 : options.maxStateChangeListeners), new InternalCallContext());
};
/**
 * Package-internal version of createStatefulCallClient that allows dependency injection.
 *
 * This function should not be exported from the package.
 */
export const createStatefulCallClientWithDeps = (callClient, context, internalContext) => {
    Object.defineProperty(callClient, 'getState', {
        configurable: false,
        value: () => context.getState()
    });
    Object.defineProperty(callClient, 'onStateChange', {
        configurable: false,
        value: (handler) => context.onStateChange(handler)
    });
    Object.defineProperty(callClient, 'offStateChange', {
        configurable: false,
        value: (handler) => context.offStateChange(handler)
    });
    Object.defineProperty(callClient, 'createView', {
        configurable: false,
        value: (callId, participantId, stream, options) => __awaiter(void 0, void 0, void 0, function* () {
            if ('feature' in stream) {
                return yield createCallFeatureView(context, internalContext, callId, stream, options);
            }
            const participantIdKind = participantId ? getIdentifierKind(participantId) : undefined;
            const result = yield createView(context, internalContext, callId, participantIdKind, stream, options);
            // We only need to declaratify the VideoStreamRendererView object for remote participants. Because the updateScalingMode only needs to be called on remote participant stream views.
            if ('id' in stream && callId && participantId && result) {
                const participantKey = toFlatCommunicationIdentifier(participantId);
                result.view = videoStreamRendererViewDeclaratify(result.view, context, callId, participantKey, stream.id);
            }
            return result;
        })
    });
    Object.defineProperty(callClient, 'disposeView', {
        configurable: false,
        value: (callId, participantId, stream) => {
            if ('feature' in stream) {
                disposeCallFeatureView(context, internalContext, callId, stream);
            }
            const participantIdKind = participantId ? getIdentifierKind(participantId) : undefined;
            disposeView(context, internalContext, callId, participantIdKind, stream);
        }
    });
    const newStatefulCallClient = new Proxy(callClient, new ProxyCallClient(context, internalContext));
    // Populate initial state
    newStatefulCallClient.feature(Features.DebugInfo).getEnvironmentInfo();
    return newStatefulCallClient;
};
const withTelemetryTag = (telemetryImplementationHint, options) => {
    var _a, _b;
    const tags = (_b = (_a = options === null || options === void 0 ? void 0 : options.diagnostics) === null || _a === void 0 ? void 0 : _a.tags) !== null && _b !== void 0 ? _b : [];
    tags.push(_getApplicationId(telemetryImplementationHint));
    return Object.assign(Object.assign({}, options), { diagnostics: Object.assign(Object.assign({}, options === null || options === void 0 ? void 0 : options.diagnostics), { tags }) });
};
//# sourceMappingURL=StatefulCallClient.js.map