Source: Router/actionTemplate/service.js

/**
 * Action Template Service Layer
 * 
 * This service manages action templates in the CommTool system. Action templates
 * define the structure and behavior of actions that can be executed by bots.
 * 
 * Key concepts:
 * - Action Templates: Reusable templates that define action structure and UI
 * - Organizations: Templates belong to specific organizations
 * - Bots: Can be linked to action templates to execute actions
 * - Multi-tenancy: Each organization has isolated templates
 * 
 * Database structure:
 * - ObjectBase: Main table storing templates with Type='actionT'
 * - Links: Junction table linking bots to action templates
 * 
 * @module ActionTemplateService
 */

import { query, UUID2hex, HEX2uuid } from '@commtool/sql-query';
import { renderObject } from '../../utils/renderTemplates.js';
import { Templates } from '../../utils/compileTemplates.js';
import { getUID } from '../../utils/UUIDs.js';
import { updateTemplate } from '../../server.ws.js';
import { publishEvent } from '../../utils/events.js';
import { buildTranslateObject, mergeTemplateData } from './helpers.js';
import { errorLoggerRead, errorLoggerUpdate } from '../../utils/requestLogger.js';

/**
 * Get a single action template by UID
 * 
 * Retrieves a specific action template with its complete data structure.
 * Used for template editing, viewing details, and bot execution contexts.
 * 
 * @param {string} uid - Action template UUID
 * @param {string} orgUID - Organization UUID (for security validation)
 * @returns {Promise<Object>} Response object with success flag and template data
 * 
 * Security features:
 * - Validates organization ownership (UIDBelongsTo=orgUID)
 * - Prevents cross-organization template access
 * 
 * Returns complete template data including:
 * - Metadata (UID, Display, SortName, Title)
 * - Template structure and configuration (Data)
 * - Organization context
 */
export const getActionTemplate = async (uid, orgUID) => {
    try {
        // Query specific action template with organization validation
        // Security check ensures template belongs to the requesting organization
        const result = await query(`
            SELECT
                UID,
                Display,
                SortName,
                Title,
                Data,
                dindex
            FROM
                ObjectBase
            WHERE
                UID=? AND Type='actionT' AND UIDBelongsTo=?
        `, [UUID2hex(uid), UUID2hex(orgUID)], { cast: ['UUID', 'json'] });
        
        if (result.length === 0) {
            return { success: false, error: 'Action template not found or access denied' };
        }
        
        return { success: true, result: result[0] };
    } catch (e) {
        errorLoggerRead(e);
        return { success: false, error: e.message };
    }
};
/**
 * Get all action templates for a specific organization
 * 
 * Retrieves action templates that belong to an organization. These templates
 * define the structure and behavior of actions that can be created within
 * the organization's context.
 * 
 * @param {string} orgUID - Organization UUID
 * @param {Object} req - Express request object (for query parameters)
 * @returns {Promise<Object>} Response object with success flag and results
 * 
 * Query parameters:
 * - includeData: 'true' to include template data, otherwise only metadata
 * 
 * Database schema:
 * - ObjectBase.Type='actionT' for action templates
 * - UIDBelongsTo links template to organization
 * - Ordered by dindex for consistent display order
 */
export const getActionTemplatesForOrg = async (orgUID, req) => {
    try {
        // Conditionally include template data based on query parameter
        // Template data can be large, so only include when explicitly requested
        let mainData = '';
        if (req.query.includeData === 'true') {
            mainData = ', Main.Data';
        }
        
        // Query organization-specific action templates
        // Uses GROUP BY to ensure unique templates (in case of duplicates)
        const result = await query(`
            SELECT
                Main.UID, 
                Main.Display, 
                Main.SortName 
                ${mainData}
            FROM
                ObjectBase AS Main
            WHERE
                Main.UIDBelongsTo=? AND Main.Type='actionT'
            GROUP BY
                Main.UID
            ORDER BY
                Main.dindex
        `, [UUID2hex(orgUID)], { cast: ['UUID', 'json'] });
        
        return { success: true, result };
    } catch (e) {
        // Log read errors for monitoring and debugging
        errorLoggerRead(e);
        return { success: false, error: e.message };
    }
};

/**
 * Get all action templates associated with a specific bot
 * 
 * Retrieves action templates that are linked to a bot through the Links table.
 * This enables bots to discover which action templates they can execute.
 * Includes organization information for multi-tenant context.
 * 
 * @param {string} botUID - Bot UUID
 * @param {Object} req - Express request object (for query parameters)
 * @returns {Promise<Object>} Response object with success flag and results
 * 
 * Database relationships:
 * - Links.UID = botUID, Links.UIDTarget = actionTemplateUID
 * - Links.Type = 'actionT' identifies action template links
 * - Joins with organization data for context
 * 
 * Results include:
 * - Template metadata (UID, Display, SortName)
 * - Optional template data (if includeData=true)
 * - Organization context (OrgUID, OrgDisplay)
 */
export const getActionTemplatesForBot = async (botUID, req) => {
    try {
        // Conditionally include template data - can be large
        let mainData = '';
        if (req.query.includeData === 'true') {
            mainData = ', ActionT.Data';
        }
        
        // Query bot-linked action templates with organization context
        // INNER JOIN with Links ensures only linked templates are returned
        // LEFT JOIN with Org provides organization context (may be null)
        const result = await query(`
            SELECT 
                ActionT.UID,
                ActionT.Display,
                ActionT.SortName
                ${mainData},
                ActionT.UIDBelongsTo AS OrgUID,
                OrgMember.Display AS OrgDisplay,
                gid.UID AS OrgLoginUID
            FROM ObjectBase AS ActionT
            INNER JOIN Links ON (Links.UIDTarget = ActionT.UID AND Links.Type = 'actionT')
           INNER JOIN Member AS OrgMember ON (OrgMember.UID=ActionT.UIDBelongsTo)
           LEFT JOIN Links AS gid ON (gid.UIDTarget=OrgMember.UID AND gid.Type='identifyer')
            WHERE Links.UID = ? AND ActionT.Type = 'actionT'
            ORDER BY OrgMember.Display, ActionT.Display
        `, [UUID2hex(botUID)], { cast: ['UUID', 'json'] });
        
        return { success: true, result };
    } catch (e) {
        errorLoggerRead(e);
        return { success: false, error: e.message };
    }
};

/**
 * Get organizations by their UIDs
 * 
 * Utility function to retrieve organization details for multiple organizations.
 * Used for batch operations and providing organization context in templates.
 * 
 * @param {string[]} organizationUIDs - Array of organization UUIDs
 * @returns {Promise<Object>} Response object with organization details
 * 
 * Returns organization data including:
 * - UID: Organization identifier
 * - Display: Human-readable organization name
 * - Data: Organization-specific configuration (JSON)
 */
export const getOrganizationsByUIDs = async (organizationUIDs) => {
    try {
        // Convert UUIDs to hex format for database query
        const orgUIDs = organizationUIDs.map(uid => UUID2hex(uid));
        
        // Query multiple organizations by UID
        // Type='orga' filters for organization records
        const result = await query(`
            SELECT ObjectBase.UID, Member.Display, Member.Data, gid.UID AS OrgLoginUID
            FROM ObjectBase 
            INNER JOIN Member ON (Member.UID=ObjectBase.UIDBelongsTo)
            INNER JOIN Links ON (Links.UIDTarget=ObjectBase.UID AND Links.Type='memberSys' AND Links.UID=ObjectBase.UID)
            LEFT JOIN Links AS gid ON (gid.UIDTarget=ObjectBase.UID AND gid.Type='identifyer')
            WHERE ObjectBase.Type='group' AND ObjectBase.UID IN (?)
        `, [orgUIDs], { cast: ['UUID', 'json'] });
        
        return { success: true, result };
    } catch (e) {
        errorLoggerRead(e);
        return { success: false, error: e.message };
    }
};

/**
 * Create or update an action template
 * 
 * This is the core function for managing action templates. It handles both
 * creation of new templates and updates to existing ones. The function
 * processes template data, applies organization-specific rendering, and
 * manages internationalization support.
 * 
 * @param {Object} req - Express request object (contains session and query data)
 * @param {Object} templateData - Template configuration and structure
 * @returns {Promise<Object>} Response with created/updated template data
 * 
 * Process flow:
 * 1. Generate or retrieve template UID
 * 2. Check if template exists (for updates vs creation)
 * 3. Merge new data with existing data (if updating)
 * 4. Render template with organization context
 * 5. Build internationalization structure
 * 6. Save to database with upsert operation
 * 7. Notify UI of template changes via WebSocket
 * 
 * Query parameters:
 * - initialize: If undefined, always use new data; if defined, merge with existing
 * 
 * Features:
 * - Template rendering with organization context
 * - Data merging for incremental updates
 * - Internationalization support (translate object)
 * - Real-time UI updates via WebSocket notifications
 * - Full-text indexing for search functionality
 */

export const createOrUpdateActionTemplate = async (req, templateData) => {
    try {
        // check if a an action template of this type is already registered for this organization
        const existingTemplate = await query(`SELECT Links.UIDTarget
                                              FROM Links
                                              INNER JOIN ObjectBase ON (Links.UIDTarget=ObjectBase.UID)
                                              WHERE Links.Type='actionT' AND ObjectBase.Type='actionT' AND Links.UID=?`,
                                              [templateData.UID ? UUID2hex(templateData.UID) : ''],{ cast: ['UUID'], log: true});
        let UIDaction;
        if (existingTemplate.length > 0) {
            // If a template exists, we can update it
            UIDaction = existingTemplate[0].UIDTarget;
        }
        else {
            // If no template exists, we create a new one
            [{UIDaction}] = await query(`SELECT UIDV1() AS UIDaction`, []);
            
        }
        // Get the template renderer for the current organization
        const template = Templates[req.session.root].actionTemplate;
        

        // Check if template already exists in database
        const action = await query(`SELECT Data FROM ObjectBase WHERE UID=? `, [UIDaction]);
        let aData = {};
        
        // Parse existing template data if found
        if (action.length === 1) {
            try {
                aData = JSON.parse(action[0].Data);
            } catch (e) {
                console.log('action without data');
                // Template exists but has no valid JSON data
            }
        }

        let Data;
        // Determine data merge strategy based on query parameters and existing data
        if (req.query.initialize === undefined || action.length === 0) {
            // Use new template data completely (creation or full replacement)
            Data = templateData;
        } else {
            // Merge new template data with existing data (incremental update)
            Data = mergeTemplateData(templateData, aData);
        }

        // Render template with organization-specific context and data
        const object = await renderObject(template, Data, req);
        
        // Prepare data for database storage (remove UID from data to avoid duplication)
        object.Data = { ...Data, UID: undefined };
        
        // Set up translation structure for internationalization
        if (object.Data.UIaction) {
            // Initialize translation object if not present
            object.Data.translate = object.Data.translate || {};
            
            // Build translation keys from UI action structure
            buildTranslateObject(object.Data.UIaction, object.Data.translate);
        }

        // Insert template to database
        // Uses ON DUPLICATE KEY UPDATE for atomic create-or-update operation
        await query(`
            INSERT INTO ObjectBase(UID,Type,UIDBelongsTo,Title,Display,SortName,FullTextIndex,dindex,Data)
            VALUES (?,'actionT',?,?,?,?,?,?,?)
            ON DUPLICATE KEY UPDATE Title=VALUE(Title),Display=VALUE(Display),SortName=VALUE(SortName),FullTextIndex=VALUE(FullTextIndex),dindex=VALUE(dindex),Data=VALUE(Data)
        `, [UUID2hex(UIDaction), UUID2hex(req.session.root), object.Title, object.Display, object.SortIndex, object.FullTextIndex, object.dindex, JSON.stringify(object.Data)]);

        // Notify UI clients that templates have been updated for this organization
        // Uses WebSocket to provide real-time updates to connected clients
        updateTemplate(req.session.root);

        return { 
            success: true, 
            result: { ...object, UID: HEX2uuid(UIDaction) },
            UIDaction 
        };
    } catch (e) {
        errorLoggerUpdate(e);
        return { success: false, error: e.message };
    }
};

/**
 * Create a link between a bot and an action template
 * 
 * This function establishes the relationship that allows a bot to execute
 * actions based on a specific action template. The relationship is stored
 * in the Links table and enables the bot discovery system.
 * 
 * @param {string} botUID - Bot UUID that will be linked to the template
 * @param {string} actionTemplateUID - Action template UUID to link
 * @returns {Promise<Object>} Response object with success status
 * 
 * Database operation:
 * - Uses ON DUPLICATE KEY UPDATE to handle existing links gracefully
 * - Links.UID = botUID (the bot that can execute actions)
 * - Links.UIDTarget = actionTemplateUID (the template that defines the action)
 * - Links.Type = 'actionT' (identifies this as an action template link)
 * 
 * Use cases:
 * - Bot registration: When a bot starts up and registers its capabilities
 * - Template assignment: When administrators assign templates to bots
 * - Multi-tenant isolation: Bots can only see templates they're linked to
 */
export const createBotLink = async (botUID, actionTemplateUID) => {
    try {
        // Create or update bot-to-action-template link
        // ON DUPLICATE KEY UPDATE ensures idempotent operation
        await query(`
            INSERT IGNORE INTO Links (UID, Type, UIDTarget) 
            VALUES (?, 'actionT', ?)
        `, [UUID2hex(botUID), UUID2hex(actionTemplateUID)]);
        
        return { success: true };
    } catch (e) {
        errorLoggerUpdate(e);
        return { success: false, error: e.message };
    }
};

/**
 * Delete an action template and all associated data
 * 
 * This function performs a cascading delete of an action template, removing
 * all related data to maintain database integrity. It's a comprehensive
 * cleanup operation that affects multiple tables.
 * 
 * @param {string} uid - Action template UUID to delete
 * @param {string} orgUID - Organization UUID (for security validation)
 * @returns {Promise<Object>} Response object with success status
 * 
 * Cascading delete operations:
 * 1. Delete the action template from ObjectBase
 * 2. Remove bot links to this template from Links table
 * 3. Delete all active actions created from this template
 * 4. Notify UI clients of template changes
 * 
 * Security measures:
 * - Validates organization ownership (UIDBelongsTo=orgUID)
 * - Prevents cross-organization template deletion
 * 
 * Side effects:
 * - All bots lose access to this template
 * - All active actions from this template are removed
 * - UI clients receive real-time update notifications
 */
export const deleteActionTemplate = async (uid, orgUID) => {
    try {
        const UID = UUID2hex(uid);
        
        // Delete the action template from ObjectBase
        // Security check: Only delete if template belongs to the specified organization
        await query(`DELETE FROM ObjectBase WHERE UID=? AND Type='actionT'
                    AND UIDBelongsTo=?`, [UID, UUID2hex(orgUID)]);
        
        // Remove bot links to this action template
        // This prevents bots from trying to execute actions from deleted templates
        await query(`DELETE FROM Links WHERE Type='actionT' AND UIDTarget=?`, [UID]);
        
        // Delete all active actions that were created from this template
        // Complex join operation to find and delete related action instances
        query(`DELETE Links,ObjectBase 
                FROM Links 
                INNER JOIN ObjectBase ON (Links.UIDTarget=ObjectBase.UID AND Links.Type='action' AND ObjectBase.Type='action')
                WHERE Links.UID=?`, [UID]);
                
        // Notify UI clients that templates have been updated for this organization
        updateTemplate(orgUID);
        
        return { success: true };
    } catch (e) {
        errorLoggerUpdate(e);
        return { success: false, error: e.message };
    }
};

/**
 * Restart an action template
 * 
 * This function sends a restart signal to the bots that defined
 * the specified action template. It's used to update call the onStartup function
 * of the bot part without requiring a full bot restart.
 * 
 * @param {string} uid - Action template UUID to restart
 * @returns {Promise<Object>} Response object with success status
 * 
 * Operation mechanism:
 * - Publishes a restart event to the Redis event system
 * - Event format: `/restart/actionT/{templateUID}`
 * - All subscribed bots receive the restart signal
 * 
 * Bot behavior upon receiving restart signal:
 * - Bots executed the onStartup function for the specified template

 * 
 * Use cases:
 * - Template logic has been modified
 * - Template parameters have changed
 * - Template permissions have been updated
 * - Forcing bot cleanup related to the template
 */
export const restartActionTemplate = async (uid) => {
    try {
        // Publish restart event to Redis for all subscribed bots
        // Event path follows pattern: /restart/actionT/{templateUID}
        publishEvent(`/restart/actionT/${uid}`, {});
        
        return { success: true };
    } catch (e) {
        errorLoggerUpdate(e);
        return { success: false, error: e.message };
    }
};