/* $Header$ */
// Contains the interface that allows a method called via the Omnis JavaScript worker to send its response back to Omnis
// Copyright (C) OLS Holdings Ltd 2018

/*********************************** Changes History **********************************
Date			Edit					Bug					Description
19-Aug-25	jmg1573                     ST/EC/1966	Reworked JS Worker to use Server-Sent Events and added $methodprogress callback.
21-Aug-24	jmg_unmarked	            ST/PF/1455	Throwing an error from an sync method killed Node.
21-May-24	jmg_unmarked							Added omnisModuleDefaultExport.
21-May-24	jmg_unmarked	            ST/EC/1877	sendResponse() failed for data other than JS Objects.
23-Mar-19	jmg_unmarked							Updated sendResponse to base its response on the data that is passed in.
21-Nov-18	rmm_jsw								    OW3 component - JavaScript Worker.
**************************************************************************************/
const http2 = require('node:http2');
const {errorCodes, newErrorWithCode} = require("./errors");

// jmg1532
const EVENT_TYPE = {
	COMPLETED: "__omnis__internal__completed__",
    ERROR: "__omnis__internal__error__",
};

const omnis_calls = {

	/**
	 * Send any data as the response, attempting to automatically detect content type and convert data as necessary.
	 *
	 * @param data				The data to send back to Omnis.
	 * @param response		The 'response' object which was passed in to a module's 'call()' function.
	 */
	sendResponse: function(data, response) {
		// Start jmg1532
		const ret = this.sendEvent(data, response, EVENT_TYPE.COMPLETED);
		response.end();
		return ret;
		// End jmg1532
	},

	/**
	 * @deprecated use sendResponse() or sendEvent().
	 * 
	 * Send a buffer with any specified content type as the response.
	 * Use to explicitly specify the content type and data to send.
	 *
	 * @param data					The data to send.
	 * @param contentType		The MIME type of the data to send. E.g. "image/png"
	 * @param {http2.Http2Stream} response			The 'response' object which was passed in to a module's 'call()' function.
	 */
	sendResponseBuffer: function(data, contentType, response) { // jmg1532a: maintain old signature
		return this.sendResponse(data, response); // jmg1532: Now just an alias to sendResponse: content-type is inferred from data
	}, 

	/**
	 * Send an event message, which will trigger $methodevent in Omnis.
 	 * @param data				The data to send back to Omnis.
	 * @param {http2.Http2Stream} response			The 'response' object which was passed in to a module's 'call()' function.
	 * @param eventName 		The name of the event. Defaults to 'message'.
	 */
	sendEvent: function(data, response, eventName='message') {
		if (!response) return false;

		// Start jmg1532
		if (!response.sentSSEHeaders) {
			let headers = {
				':status': 200,
				"Content-Type": "text/event-stream",
				'Cache-Control': 'no-cache'
			};
			response.respond(headers);

			response.sentSSEHeaders = true;
		}

		let responseData = "";		
		if (data === null || typeof data === "undefined") //  Send empty data for null/undefined
			responseData = "";
		else if (data instanceof Buffer) // Binary data (sent as data URL)
			responseData = `data:application/octet-stream;base64,${data.toString('base64')}`;
		else if (data instanceof Object) // Object or Array
			responseData = data;
		else // primitive type
			responseData = data;

		const payload = {"payload": responseData};
		responseData = `event: ${eventName}\ndata: ${JSON.stringify(payload)}\n\n`;

		response.write(responseData);
		// End jmg1532
		return true;
	},

	/**
	 * Send an error status response.
	 *
	 * @param {http2.Http2Stream} response		The 'response' object which was passed in to a module's 'call()' function.
	 * @param errorCode		An HTTP status code.
	 * @param errorText		A description of the error.
	 */
	sendError: function (response, errorCode, errorText) {
    const ret = this.sendEvent({"errorCode": errorCode, "errorInfo": errorText}, response, EVENT_TYPE.ERROR);
		response.end();
		return ret;
	},

	/**
	 * Returns an object suitable for returning as the default export of an Omnis Module.
	 * 
	 * Supports async functions. Handles sending response to Omnis.
	 * Methods should just return the result object (or whatever) to send back to Omnis.
	 * 
	 * @param {Object} methodMap 	An object containing functions to expose as methods for your module. Key name is the name of the method.
	 */
	omnisModuleDefaultExport: function(methodMap) {
		return {
			call: function(method, param, response) { // The only requirement of an Omnis module is that it implement this function.
		
				if (!methodMap[method])
					throw newErrorWithCode(errorCodes.METHOD_NOT_FOUND, "");
		
				const result = methodMap[method](param, response);
				if (result instanceof Promise) {
					result.then((promiseResult) => {
						if (!response._writableState?.ended) // If response hasn't already been manually sent
							omnis_calls.sendResponse(promiseResult, response);
					})
					.catch((reason) => {
						if (!response._writableState?.ended)
							omnis_calls.sendError(response, 400, reason?.message);
					});
				}
				else {
					if (!response._writableState?.ended) // If response hasn't already been manually sent
						omnis_calls.sendResponse(result, response);
				}
		
				return true;
			}
		}
	}
};

module.exports = omnis_calls;
