import {
    subscribe as smSubscribe,
    unsubscribe as smUnsubscribe,
    publish as smPublish,
    releaseMessageContext as smReleaseMessageContext,
} from './subscriptionManager';
import { generateUUID } from './utils';

import {
    getDefaultContext,
    validateContext,
    unregisterContext,
    isRegisteredContext,
} from './context';
export { MessageContext } from './context';

import { APPLICATION_SCOPE } from './scope';
export { APPLICATION_SCOPE };

export const PUBLIC_MODULE_NAME = 'lightning/messageService';
const allMessageChannels = new Set();

export function createMessageChannel() {
    const messageChannel = generateUUID();
    allMessageChannels.add(messageChannel);
    return messageChannel;
}

export function createMessageContext() {
    return getDefaultContext();
}

export function releaseMessageContext(messageContext) {
    if (!isRegisteredContext(messageContext)) {
        return; // quietly ignore, inspired by `clearTimeout`
    }
    unregisterContext(messageContext);
    smReleaseMessageContext(messageContext);
}

export function subscribe(
    messageContext,
    messageChannel,
    listener,
    subscriberOptions
) {
    validateContext(messageContext);
    validateMessageChannel(messageChannel);
    validateListener(listener);
    validateSubscriberOptions(subscriberOptions);

    return smSubscribe(
        messageContext,
        messageChannel,
        listener,
        subscriberOptions
    );
}

export function unsubscribe(subscription) {
    smUnsubscribe(subscription);
}

export function publish(
    messageContext,
    messageChannel,
    message,
    publisherOptions
) {
    validateContext(messageContext);
    validateMessageChannel(messageChannel);
    validatePublisherOptions(publisherOptions);
    message = serializeAndValidateMessage(message);

    smPublish(messageContext, messageChannel, message, publisherOptions);
}

function validateMessageChannel(messageChannel) {
    if (!allMessageChannels.has(messageChannel)) {
        throw new Error(`${PUBLIC_MODULE_NAME}: unknown message channel`);
    }
}

function validateListener(listener) {
    if (typeof listener !== 'function') {
        throw new Error(`${PUBLIC_MODULE_NAME}: invalid listener function`);
    }
}

function validateSubscriberOptions(subscriberOptions) {
    // Beta Validation
    if (
        subscriberOptions == null ||
        typeof subscriberOptions !== 'object' ||
        subscriberOptions.scope !== APPLICATION_SCOPE
    ) {
        throw new Error(
            PUBLIC_MODULE_NAME +
                ': The `scope` property is required. When you call `subscribe`, ' +
                'include the `scope` property and set the value to `APPLICATION_SCOPE`. ' +
                'Format the call to `subscribe` as `subscribe(<context>, <channel>, <listener>, { scope: APPLICATION_SCOPE })`. ' +
                'Import `APPLICATION_SCOPE` from `lightning/messageService`.'
        );
    }
}

// serialize/deserialize so message is consistent when sent across windows
function serializeAndValidateMessage(message) {
    if (message !== undefined) {
        const _message = JSON.parse(JSON.stringify(message));
        if (typeof _message !== 'object') {
            throw new Error(
                `${PUBLIC_MODULE_NAME}: invalid message. message must be serializable object or null`
            );
        }
        return _message;
    }
    return undefined;
}

function validatePublisherOptions(publisherOptions) {
    if (
        publisherOptions !== undefined &&
        typeof publisherOptions !== 'object'
    ) {
        throw new Error(`${PUBLIC_MODULE_NAME}: invalid publisherOptions`);
    }
}
