/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module NativeApp
 */
import { BackendError, IModelError, IModelStatus, ipcAppChannels, iTwinChannel, } from "@itwin/core-common";
import { IModelApp } from "./IModelApp";
import { _callIpcChannel } from "./common/internal/Symbols";
/**
 * The frontend of apps with a dedicated backend that can use [Ipc]($docs/learning/IpcInterface.md).
 * @public
 */
export class IpcApp {
    /** Get the implementation of the [[IpcSocketFrontend]] interface. */
    static get ipc() { return this._ipc; }
    /** Determine whether Ipc is available for this frontend. This will only be true if [[startup]] has been called on this class. */
    static get isValid() { return undefined !== this._ipc; }
    /**
     * Establish a message handler function for the supplied channel over Ipc. The handler will be called when messages are sent for
     * the channel via  [[BackendIpc.send]].
     * @param channel the name of the channel
     * @param handler the message handler
     * @returns A function to remove the handler
     * @note Ipc is only supported if [[isValid]] is true.
     */
    static addListener(channel, handler) {
        return this.ipc.addListener(iTwinChannel(channel), handler);
    }
    /**
     * Remove a previously registered listener
     * @param channel The name of the channel for the listener previously registered with [[addListener]]
     * @param listener The function passed to [[addListener]]
     */
    static removeListener(channel, listener) {
        this.ipc.removeListener(iTwinChannel(channel), listener);
    }
    /**
     * Send a message to the backend via `channel` and expect a result asynchronously. The handler must be established on the backend via [[BackendIpc.handle]]
     * @param channel The name of the channel for the method.
     * @see Electron [ipcRenderer.invoke](https://www.electronjs.org/docs/api/ipc-renderer) documentation for details.
     * Note that this interface may be implemented via Electron for desktop apps, or via
     * [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) for mobile or web-based
     * Ipc connections. In either case, the Electron documentation provides the specifications for how it works.
     * @note `args` are serialized with the [Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm), so only
     * primitive types and `ArrayBuffers` are allowed.
     */
    static async invoke(channel, ...args) {
        return this.ipc.invoke(iTwinChannel(channel), ...args);
    }
    /**
     * Send a message over the socket.
     * @param channel The name of the channel for the message.
     * @param data The optional data of the message.
     * @note `data` is serialized with the [Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm), so only
     * primitive types and `ArrayBuffers` are allowed.
     */
    static send(channel, ...data) {
        return this.ipc.send(iTwinChannel(channel), ...data);
    }
    /**
     * Call a method on the backend through an Ipc channel.
     * @param channelName the channel registered by the backend handler.
     * @param methodName  the name of a method implemented by the backend handler.
     * @param args arguments to `methodName`
     * @return a Promise with the return value from `methodName`
     * @note If the backend implementation throws an exception, this method will throw a [[BackendError]] exception
     * with the `errorNumber` and `message` from the backend.
     * @note Ipc is only supported if [[isValid]] is true.
     * @internal Use [[makeIpcProxy]] for a type-safe interface.
     */
    static async [_callIpcChannel](channelName, methodName, ...args) {
        const retVal = (await this.invoke(channelName, methodName, ...args));
        if (undefined !== retVal.iTwinError) {
            const error = new Error();
            if (retVal.iTwinError.stack === undefined)
                delete retVal.iTwinError.stack;
            Object.assign(error, retVal.iTwinError);
            throw error;
        }
        else if (undefined !== retVal.error) {
            const err = new BackendError(retVal.error.errorNumber, retVal.error.name, retVal.error.message);
            err.stack = retVal.error.stack;
            throw err;
        }
        return retVal.result;
    }
    /** @internal
     * @deprecated in 4.8. Use [[makeIpcProxy]] for a type-safe interface.
     */
    static async callIpcChannel(channelName, methodName, ...args) {
        return this[_callIpcChannel](channelName, methodName, ...args);
    }
    /** Create a type safe Proxy object to make IPC calls to a registered backend interface.
     * @param channelName the channel registered by the backend handler.
     */
    static makeIpcProxy(channelName) {
        return new Proxy({}, {
            get(_target, methodName) {
                return async (...args) => IpcApp[_callIpcChannel](channelName, methodName, ...args);
            },
        });
    }
    /** Create a type safe Proxy object to call an IPC function on a of registered backend handler that accepts a "methodName" argument followed by optional arguments
     * @param channelName the channel registered by the backend handler.
     * @param functionName the function to call on the handler.
     */
    static makeIpcFunctionProxy(channelName, functionName) {
        return new Proxy({}, {
            get(_target, methodName) {
                return async (...args) => IpcApp[_callIpcChannel](channelName, functionName, methodName, ...args);
            },
        });
    }
    /** @deprecated in 3.x. use [[appFunctionIpc]] */
    static async callIpcHost(methodName, ...args) {
        return this[_callIpcChannel](ipcAppChannels.functions, methodName, ...args);
    }
    /** start an IpcApp.
     * @note this should not be called directly. It is called by NativeApp.startup */
    static async startup(ipc, opts) {
        this._ipc = ipc;
        IpcAppNotifyHandler.register(); // receives notifications from backend
        await IModelApp.startup(opts?.iModelApp);
    }
    /** @internal */
    static async shutdown() {
        this._ipc = undefined;
        await IModelApp.shutdown();
    }
}
/** A Proxy to call one of the [IpcAppFunctions]($common) functions via IPC. */
IpcApp.appFunctionIpc = IpcApp.makeIpcProxy(ipcAppChannels.functions);
/**
 * Base class for all implementations of an Ipc notification response interface. This class is implemented on your frontend to supply
 * methods to receive notifications from your backend.
 *
 * Create a subclass to implement your Ipc response interface. Your class should be declared like this:
 * ```ts
 * class MyNotificationHandler extends NotificationHandler implements MyNotifications
 * ```
 * to ensure all method names and signatures are correct. Your methods cannot have a return value.
 *
 * Then, call `MyNotificationHandler.register` at startup to connect your class to your channel.
 * @public
 * @extensions
 */
export class NotificationHandler {
    registerImpl() {
        return IpcApp.addListener(this.channelName, (_evt, funcName, ...args) => {
            const func = this[funcName];
            if (typeof func !== "function")
                throw new IModelError(IModelStatus.FunctionNotFound, `Method "${this.constructor.name}.${funcName}" not found on NotificationHandler registered for channel: ${this.channelName}`);
            func.call(this, ...args);
        });
    }
    /**
     * Register this class as the handler for notifications on its channel. This static method creates a new instance
     * that becomes the notification handler and is `this` when its methods are called.
     * @returns A function that can be called to remove the handler.
     * @note this method should only be called once per channel. If it is called multiple times, multiple handlers are established.
     */
    static register() {
        return new this().registerImpl(); // create an instance of subclass. "as any" is necessary because base class is abstract
    }
}
/** IpcApp notifications from backend */
class IpcAppNotifyHandler extends NotificationHandler {
    get channelName() { return ipcAppChannels.appNotify; }
    notifyApp() { }
}
//# sourceMappingURL=IpcApp.js.map