/**
 * @module medium/nchan.js
 * @author Krzyszot Putyra
 * @date Apr 01, 2025
 * @description A collection of functions to communicate with nchan
 */

/**
 * @typedef NChanMessageInfo
 * @property {number} timestamp
 * @property {number} channel
 */

/**
 * Extracts the timestamp and source channel of a message from its id.
 * 
 * The id field accompanying a message is composed of several parts. The number before
 * the colon is the time the message was received by the server (as a unix timestamp).
 * The string after the colon resembles the list of channels in the order they were
 * subscribed to. The channel receiving the current message is indicated by a number in
 * brackets. The number resembles the count of received messages on this channel during
 * the current second (indicated by the timestamp). Channels not having received a message
 * in this 1-second time slot are represented with a dash, whereas counters without a dash
 * indicate that there has been another message on a different channel in the same time slot.
 * @see {@link https://github.com/slact/nchan/issues/562#issuecomment-612924853}
 * 
 * @param {string} msgId
 * @returns {NChanMessageInfo}
 */
export const parseMessageId = (msgId) => {
    const [ timestamp, counters ] = msgId.split(':', 2);
    const result = { timestamp: parseInt(timestamp) };
    if (counters) {
        const parts = counters.split(',');
        if (parts.length === 1) {
            result.channel = 0;
        } else {
            const channelNr = parts.findIndex(ch => ch.charAt(0) === '[');
            if (channelNr >= 0) result.channel = channelNr;
        }
    }
    return result;
}


/**
 * @typedef NChanWebSocketMessage
 * @extends NChanMessageInfo
 * @property {object} headers
 * @property {string} payload
 */

/**
 * Parses header lines from a message assuming the ws+meta.nchan subprotocol
 * @function
 * @param {string} msg    the message received on a websocket
 * @returns {NChanWebSocketMessage}
 */
export const parseWebSocketMessage = (msg) => {
    const [ rawHeader, payload ] = msg.split('\n\n', 2);
    const headers = Object.fromEntries(
        rawHeader.split('\n').map(line => line.split(': ', 2))
    );
    return {
        headers,
        ...parseMessageId(headers.id),
        payload
    }
}


/**
 * @typedef NchanChannelInfo
 * @property {number} messages    - the amount of queued messages
 * @property {number} requested   - seconds since last request
 * @property {number} subscribers - the amount of subscribers to the channel
 * @property {string} last_message_id  - the ID of the lats message
 */

/**
 * Posts a message to the provided location and returns a JSON object
 * with channel statistics
 * @function
 * @param {string} url      target location
 * @param {string} payload  data to be posted
 * @param {object} param2.headers     a dictionary of additional HTTP headers
 * @param {string} param2.eventName   an optional event name
 * @param {AbortSignal} param2.signal
 * @returns {NchanChannelInfo}
 */
export const publish = async (url, payload, { headers={}, eventName, signal, authorization } = {}) => {
    // Prepare options
    const options = {
        method: 'POST',
        body: payload,
        credentials: 'include',
        headers: {
            'Content-Type': 'text/json',// content type can be overriden
            ...headers,
            'Accept': 'text/json'       // the response must be JSON
        }
    }
    if (signal instanceof AbortSignal) options.signal = signal;
    if (authorization) options.headers['Authorization'] = authorization;
    if (eventName) options.headers['X-EventSource-Event'] = eventName;
    // Make a request
    const response = await fetch(url, options);
    // Correct responses are
    //   HTTP 201 Created  - there are already listeners
    //   HTTP 202 Accepted - no listeners yet
    if (response.status !== 201 && response.status !== 202) {
        throw Error(`HTTP ${response.status}: access rejected`)
    }
    // The response is a JSON object
    return await response.json();
};
