Source: models/EventStd.js

const CRUD = require('@CRUD');

/**
 * Model to set the events that will trigger actions throughout the app.
 * @module EventStd
 * @namespace Models
 */
class EventStd {
    /**
     * Creates a new instance of the EventStd class.
     * @param {Object} setup - The setup object.
     * @param {string} setup.name - String with the event's name.
     * @param {function} setup.handler - Function with the handler to be executed when the event is triggered.
     * @param {Object} setup.target - The object that is wrapping the event (e.g., Workflow, Collection, etc.).
     * @throws {Error} - Throws an error if the setup object is not valid.
     */
    constructor(setup) {
        try {
            const { name, handler, target } = Object(setup);

            /**
             * String with the event's name.
             * @property {string}
             */
            this.name = name;

            /**
             * Function with the handler to be executed when the event is triggered.
             * @property {function}
             */
            this.handler = handler || Function();

            /**
             * The object that is wrapping the event. For example: Workflow, Collection, etc.
             * @property {Object}
             */
            this.target = target;

            this.add(target);
        } catch (err) {
            throw new Error.Log(err);
        }
    }

    /**
     * Triggers the event, emitting it to the specified target.
     * @param {Object} target - The target object on which the event is triggered.
     * @returns {*} - Returns the result of the event emission.
     * @throws {Error} - Throws an error if event triggering fails.
     */
    trigger(target) {
        const Status = require('@models/settings/Status');

        try {
            if (target instanceof Status) {
                this.name += ':' + this.target.statusID;
            }

            return process.emit(this.name, target);
        } catch (err) {
            throw new Error.Log(err);
        }
    }
    
    /**
     * Adds an event listener to the specified context and handles event triggering.
     * @param {Object} context - The context object to which the event listener is added.
     * @returns {Object} - The event listener added to the process.
     * @throws {Error} - Throws an error if event listener addition fails.
     */
    add(context) {
        const Status = require('@models/settings/Status');
        const self = this;

        try {
            if (this.target instanceof Status) {
                this.name += ':' + this.target.statusID;
            }

            /**
             * Event listener function handling the triggered event.
             * @param {*} args - Arguments passed with the event.
             */
            this.listener = process.on(this.name, async function (...args) {
                try {
                    const populatedTarget = await self.populateTarget.call(context, ...args);

                    // Replacing the target argument by its populated document. args[0] = target
                    if (args.length) {
                        args[0].populated = populatedTarget;
                    }

                    if (context instanceof Status && context.workflow.preventStatus.call(context, ...args)) {
                        return;
                    }

                    self.handler.call(context, ...args);
                } catch (err) {
                    throw new Error.Log(err);
                }
            });

            return this.listener;
        } catch (err) {
            throw new Error.Log(err);
        }
    }

    /**
     * Populates the target object with data from the database.
     * @param {Object} target - The target object to be populated.
     * @returns {Object} - The populated target object.
     * @throws {Error} - Throws an error if target population fails.
     */
    async populateTarget(target) {
        let collectionName = Object(target).getSafe('_collection.collectionName');
    
        try {
            if (!collectionName && !target) {
                return;
            }

            if (target._id && target._id.oid()) {
                const docQuery = await CRUD.getDoc({ collectionName: target.collection.collectionName, filter: { _id: target.id } }).defaultPopulate();
                return docQuery.initialize();
            }
            
            const docQuery = await CRUD.getDoc({ collectionName, filter: target.getFilter() }).defaultPopulate();
            const doc = docQuery.initialize();
            
            return doc;
        } catch (err) {
            throw new Error.Log(err);
        }
    }
}

module.exports = EventStd;