Source: utils/rebuildFilter.js

import {query,transaction, HEX2uuid} from '@commtool/sql-query'
import { publishEvent } from './events.js'
import { addFilter, removeFilter } from '../tree/executeFilters/executeFilters.js'
import { errorLoggerUpdate } from './requestLogger.js'


/**
 * Comparator function for sorting filters in ascending order during filter rebuilding.
 * Sorts by filter type priority: include > exclude > intersect
 *
 * @function compareUP
 * @param {Object} filterA - First filter object with Type property
 * @param {Object} filterB - Second filter object with Type property
 * @returns {number} -1 if filterA < filterB, 0 if equal, 1 if filterA > filterB
 */
const compareUP= ( filterA,filterB)=>
{
    return filterA.Type===filterB.Type ?
        0 : filterA.Type==='include' ? 1 :
        filterA.Type==='intersect' ? -1 :
        filterB.Type==='include' ?  -1 :
        1
}

/**
 * Comparator function for sorting filters in descending order during filter removal.
 * Sorts by filter type priority: include > exclude > intersect (reverse order)
 *
 * @function compareDOWN
 * @param {Object} filterA - First filter object with Type property
 * @param {Object} filterB - Second filter object with Type property
 * @returns {number} -1 if filterA < filterB, 0 if equal, 1 if filterA > filterB
 */
const compareDOWN= ( filterA,filterB)=>
    {
        return filterA.Type===filterB.Type ?
            0 : filterA.Type==='include' ? -1 :
            filterA.Type==='intersect' ? 1 :
            filterB.Type==='include' ?  1 :
            -1
    }


/**
 * Rebuilds dynamic list filters and publishes appropriate events for membership changes.
 * This function removes and re-adds filters within a transaction, then compares the before/after
 * state to publish add/remove events for affected list members.
 *
 * @async
 * @function rebuildFiltersDlist
 * @param {Array} rebuildFilters - Array of filter objects to rebuild, each containing UIDFilter, UIDList, and Type
 * @param {string|Buffer} UIDOrga - The unique identifier of the organization (required)
 * @returns {Promise<Object>} Result object containing beAdded and removed arrays
 * @throws Will log an error if any operation fails during execution.
 */
export const rebuildFiltersDlist=async (rebuildFilters, UIDOrga)=>
{
    try {

        // store the actual members
        const aLinks= test ? [] : await query(`SELECT ObjectBase.UID,ObjectBase.UIDBelongsTo,ObjectBase.Title,Links.Type
                FROM Links 
                INNER JOIN ObjectBase ON(ObjectBase.UID=Links.UID AND ObjectBase.Type='entry')
                WHERE Links.UIDTarget=? AND Links.Type IN ('member','memberA','member0')
                GROUP BY ObjectBase.UIDBelongsTo `,
                [rebuildFilters[0].UIDList],{log:false}
        )
        let nLinks

        


        // remove the filter and add it within a virtual transaction (this avoids unnessecarry history clutter, as everything is rolled back before closeing)

        await transaction(async (connection)=>{
            rebuildFilters.sort(compareDOWN)
            for(const filter of rebuildFilters)
            {
                // Ensure UIDOrga is in hex format for publishEvent
                if (Buffer.isBuffer(UIDOrga)) {
                    UIDOrga = HEX2uuid(UIDOrga);
                }

                await removeFilter(filter.UIDFilter,filter.UIDList,{connection, virtual: true}) // as we supply the connection, this will be a virtual removal
            }
            rebuildFilters.sort(compareUP)
            for(const filter of rebuildFilters)
            {
                await addFilter(filter.UIDFilter,filter.UIDList,filter.Type,{connection,virtual:true})
            }
            nLinks=await connection.query(`SELECT ObjectBase.UID,ObjectBase.UIDBelongsTo,ObjectBase.Title,Links.Type
                FROM Links 
                INNER JOIN ObjectBase ON(ObjectBase.UID=Links.UID AND ObjectBase.Type='entry')
                WHERE Links.UIDTarget=? AND Links.Type IN ('member','memberA','member0')  `,
                [filter.UIDList])
        },
        {virtual:true,log:false, beforeTransaction:async (connection)=> await connection.query(`SET TRANSACTION ISOLATION LEVEL REPEATABLE READ`/* READ UNCOMMITTED;*/)})


        // compare now nLinks and aLinks and execute the add/removal and publish the events
        let beAdded=nLinks.filter(nL=>{
            const aLink= aLinks.find(aL=>aL.UIDBelongsTo.equals(nL.UIDBelongsTo))
            if(!aLink)
            {
                //return the nL
                return true
            }
            else
            {
                // check, if we had before link type member0, if yes, convert it to new member type and publish add
                if(aLink.Type==='member0' && nL.Type!=='member0')
                {
                    query(`UPDATE Links SET Type=? WHERE UID=? AND  UIDTarget=? AND Type=?`,[nL.Type,aLink.UID,filter.UIDList,aLink.Type])
                    publishEvent(`/add/dlist/entry/${HEX2uuid(filter.UIDList)}`, UIDOrga, [HEX2uuid(nL.UID)], Date.now()/1000)
                    publishEvent(`/add/dlist/person/${HEX2uuid(filter.UIDList)}`, UIDOrga, [HEX2uuid(nL.UIDBelongsTo)], Date.now()/1000)
                } 
            }
                return false
        })
        // add the new entry objects and link them
        if(beAdded.length>0 && !test)
        {
            await query(`INSERT INTO ObjectBase (UID,Type,UIDBelongsTo,Title) VALUES (?,'entry',?,?)`,
                beAdded.map(o=>([o.UID,o.UIDBelongsTo,o.Title])),
                {batch:true})
            await query(
                `INSERT IGNORE INTO Links (UID,Type,UIDTarget)
                    VALUES (?,'dynamic',?)`,
                    beAdded.map(o=>[o.UID,filter.UIDFilter]),
                    {batch:true})
            await query(
                `INSERT IGNORE INTO Links (UID,Type,UIDTarget)
                    VALUES (?,'memberA',?)`,
                    beAdded.map(o=>[o.UID,filter.UIDList]),
                    {batch:true})
        }
        // now the other way around. Check what is not any more menber
        const oldDiff= aLinks.filter(aL=>{
            const nL=nLinks.find(o=>o.UIDBelongsTo.equals(aL.UIDBelongsTo))
            if(!nL || (nL.Type ==='member0' &&  aL.Type!=='member0') )
            {
                return true
            }
        })
        const reAdded=oldDiff.filter(o=>o.Type==='member0')
        beAdded=[...beAdded,...reAdded]
        if(beAdded.length>0)
        {
            publishEvent(`/add/dlist/entry/${HEX2uuid(filter.UIDList)}`, UIDOrga, beAdded.map(o=>HEX2uuid(o.UID)), Date.now()/1000)
            publishEvent(`/add/dlist/person/${HEX2uuid(filter.UIDList)}`, UIDOrga, beAdded.map(o=>HEX2uuid(o.UIDBelongsTo)), Date.now()/1000)
        }
        const removed=oldDiff.filter(o=>o.Type!=='member0')
        if(removed.length>0)
        {
            // delete the entries and dynamic and memberA links
            await query(`
                DELETE Links,ObjectBase FROM ObjectBase INNER JOIN Links ON (Links.UID=ObjectBase.UID) WHERE ObjectBase.UID IN(?)
                `,[removed.map(o=>(o.UID))],
            {log:true})
            publishEvent(`/remove/dlist/person/${HEX2uuid(filter.UIDList)}`, UIDOrga, removed.map(o=>HEX2uuid(o.UIDBelongsTo)), Date.now()/1000)

        }
        return {beAdded,removed}
    }
    catch(e)
    {
        errorLoggerUpdate(e)
    }


}