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

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

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

export function createMessageContext() {
    const context = Object.freeze({});
    return context;
}

export function releaseMessageContext(messageContext) {
    validateContext(messageContext);
    smReleaseMessageContext(messageContext);
}

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

    return smSubscribe(messageContext, messageChannel, listener);
}

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 validateContext(messageContext) {
    // TODO is there better validation of context we can do?
    if (messageContext !== null && typeof messageContext !== 'object') {
        throw new Error(`${PUBLIC_MODULE_NAME}: invalid message context`);
    }
}

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

// 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`);
    }
}

function generateUUID() {
    let d = new Date().getTime();
    if (performance && typeof performance.now === 'function') {
        d += performance.now();
    }

    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
        const r = ((d + Math.random() * 16) % 16) | 0;
        d = Math.floor(d / 16);
        return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
    });
}
