// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { assert } from './assert.js';
import { PromiseResolver } from './promise_resolver.js';
/** Counter for use with createUid */
let uidCounter = 1;
/** @return A new unique ID. */
function createUid() {
    return uidCounter++;
}
/**
 * The mapping used by the sendWithPromise mechanism to tie the Promise
 * returned to callers with the corresponding WebUI response. The mapping is
 * from ID to the PromiseResolver helper; the ID is generated by
 * sendWithPromise and is unique across all invocations of said method.
 */
const chromeSendResolverMap = {};
/**
 * The named method the WebUI handler calls directly in response to a
 * chrome.send call that expects a response. The handler requires no knowledge
 * of the specific name of this method, as the name is passed to the handler
 * as the first argument in the arguments list of chrome.send. The handler
 * must pass the ID, also sent via the chrome.send arguments list, as the
 * first argument of the JS invocation; additionally, the handler may
 * supply any number of other arguments that will be included in the response.
 * @param id The unique ID identifying the Promise this response is
 *     tied to.
 * @param isSuccess Whether the request was successful.
 * @param response The response as sent from C++.
 */
export function webUIResponse(id, isSuccess, response) {
    const resolver = chromeSendResolverMap[id];
    assert(resolver);
    delete chromeSendResolverMap[id];
    if (isSuccess) {
        resolver.resolve(response);
    }
    else {
        resolver.reject(response);
    }
}
/**
 * A variation of chrome.send, suitable for messages that expect a single
 * response from C++.
 * @param methodName The name of the WebUI handler API.
 * @param args Variable number of arguments to be forwarded to the
 *     C++ call.
 */
export function sendWithPromise(methodName, ...args) {
    const promiseResolver = new PromiseResolver();
    const id = methodName + '_' + createUid();
    chromeSendResolverMap[id] = promiseResolver;
    chrome.send(methodName, [id].concat(args));
    return promiseResolver.promise;
}
/**
 * A map of maps associating event names with listeners. The 2nd level map
 * associates a listener ID with the callback function, such that individual
 * listeners can be removed from an event without affecting other listeners of
 * the same event.
 */
const webUiListenerMap = {};
/**
 * The named method the WebUI handler calls directly when an event occurs.
 * The WebUI handler must supply the name of the event as the first argument
 * of the JS invocation; additionally, the handler may supply any number of
 * other arguments that will be forwarded to the listener callbacks.
 * @param event The name of the event that has occurred.
 * @param args Additional arguments passed from C++.
 */
export function webUIListenerCallback(event, ...args) {
    const eventListenersMap = webUiListenerMap[event];
    if (!eventListenersMap) {
        // C++ event sent for an event that has no listeners.
        // TODO(dpapad): Should a warning be displayed here?
        return;
    }
    for (const listenerId in eventListenersMap) {
        eventListenersMap[listenerId].apply(null, args);
    }
}
/**
 * Registers a listener for an event fired from WebUI handlers. Any number of
 * listeners may register for a single event.
 * @param eventName The event to listen to.
 * @param callback The callback run when the event is fired.
 * @return An object to be used for removing a listener via
 *     removeWebUiListener. Should be treated as read-only.
 */
export function addWebUiListener(eventName, callback) {
    webUiListenerMap[eventName] = webUiListenerMap[eventName] || {};
    const uid = createUid();
    webUiListenerMap[eventName][uid] = callback;
    return { eventName: eventName, uid: uid };
}
/**
 * Removes a listener. Does nothing if the specified listener is not found.
 * @param listener The listener to be removed (as returned by addWebUiListener).
 * @return Whether the given listener was found and actually removed.
 */
export function removeWebUiListener(listener) {
    const listenerExists = webUiListenerMap[listener.eventName] &&
        webUiListenerMap[listener.eventName][listener.uid];
    if (listenerExists) {
        const map = webUiListenerMap[listener.eventName];
        delete map[listener.uid];
        return true;
    }
    return false;
}
assert(!window.cr);
Object.assign(window, { cr: { webUIResponse, webUIListenerCallback } });
