Source: utils/GraphManager.js

/**
 * GraphManager - Utility for common RDF graph operations
 * 
 * This component provides standardized operations for managing RDF graphs
 * including clearing, dropping, creating, and validating graph operations.
 * 
 * API: clearGraph(input, resources, options)
 */

import logger from 'loglevel';

export default class GraphManager {
    /**
     * Clear all triples from a named graph
     * 
     * @param {Object} input - Clear operation input data
     * @param {string} input.graphURI - URI of the graph to clear
     * @param {boolean} input.confirm - Skip confirmation if true (default: false)
     * @param {Object} resources - External dependencies
     * @param {Object} resources.sparqlHelper - SPARQL helper for database operations
     * @param {Object} options - Configuration options
     * @param {boolean} options.validateExists - Check if graph exists before clearing (default: true)
     * @param {boolean} options.dryRun - Only simulate the operation (default: false)
     * @returns {Promise<Object>} Operation results
     */
    async clearGraph(input, resources, options = {}) {
        try {
            const { graphURI, confirm = false } = input;
            const { sparqlHelper } = resources;
            
            const operationConfig = {
                validateExists: options.validateExists !== false,
                dryRun: options.dryRun || false,
                ...options
            };

            // Validate inputs
            if (!graphURI) {
                throw new Error('Graph URI is required');
            }
            
            if (!sparqlHelper) {
                throw new Error('SPARQL helper is required');
            }

            // Check if graph exists (if validation enabled)
            let existsCheck = null;
            if (operationConfig.validateExists) {
                existsCheck = await this._checkGraphExists(graphURI, sparqlHelper);
                if (!existsCheck.exists) {
                    return {
                        success: true,
                        message: 'Graph does not exist, no action needed',
                        graphURI,
                        metadata: {
                            operation: 'clear',
                            triplesRemoved: 0,
                            graphExisted: false,
                            timestamp: new Date().toISOString()
                        }
                    };
                }
            }

            // Count triples before clearing (for reporting)
            const tripleCount = await this._countTriples(graphURI, sparqlHelper);

            // Dry run mode
            if (operationConfig.dryRun) {
                return {
                    success: true,
                    message: 'Dry run completed - would clear graph',
                    graphURI,
                    metadata: {
                        operation: 'clear-dry-run',
                        wouldRemoveTriples: tripleCount.count,
                        graphExists: existsCheck?.exists || true,
                        timestamp: new Date().toISOString()
                    }
                };
            }

            // Perform the clear operation
            const clearQuery = `CLEAR GRAPH <${graphURI}>`;
            const result = await sparqlHelper.executeUpdate(clearQuery);

            if (result.success) {
                return {
                    success: true,
                    message: `Successfully cleared graph: ${graphURI}`,
                    graphURI,
                    metadata: {
                        operation: 'clear',
                        triplesRemoved: tripleCount.count,
                        graphExisted: existsCheck?.exists || true,
                        timestamp: new Date().toISOString(),
                        queryExecuted: clearQuery
                    }
                };
            } else {
                throw new Error(result.error || 'Failed to clear graph');
            }

        } catch (error) {
            logger.error('Failed to clear graph:', error.message);
            return {
                success: false,
                error: error.message,
                graphURI: input.graphURI,
                metadata: {
                    operation: 'clear',
                    errorOccurred: true,
                    timestamp: new Date().toISOString()
                }
            };
        }
    }

    /**
     * Drop a named graph completely
     * 
     * @param {Object} input - Drop operation input data
     * @param {string} input.graphURI - URI of the graph to drop
     * @param {boolean} input.ifExists - Only drop if exists (default: true)
     * @param {Object} resources - External dependencies
     * @param {Object} resources.sparqlHelper - SPARQL helper for database operations
     * @param {Object} options - Configuration options
     * @param {boolean} options.dryRun - Only simulate the operation (default: false)
     * @returns {Promise<Object>} Operation results
     */
    async dropGraph(input, resources, options = {}) {
        try {
            const { graphURI, ifExists = true } = input;
            const { sparqlHelper } = resources;
            
            const operationConfig = {
                dryRun: options.dryRun || false,
                ...options
            };

            if (!graphURI || !sparqlHelper) {
                throw new Error('Graph URI and SPARQL helper are required');
            }

            // Check if graph exists
            const existsCheck = await this._checkGraphExists(graphURI, sparqlHelper);
            
            if (!existsCheck.exists && ifExists) {
                return {
                    success: true,
                    message: 'Graph does not exist, no action needed',
                    graphURI,
                    metadata: {
                        operation: 'drop',
                        graphExisted: false,
                        timestamp: new Date().toISOString()
                    }
                };
            }

            // Count triples before dropping
            const tripleCount = await this._countTriples(graphURI, sparqlHelper);

            // Dry run mode
            if (operationConfig.dryRun) {
                return {
                    success: true,
                    message: 'Dry run completed - would drop graph',
                    graphURI,
                    metadata: {
                        operation: 'drop-dry-run',
                        wouldRemoveTriples: tripleCount.count,
                        graphExists: existsCheck.exists,
                        timestamp: new Date().toISOString()
                    }
                };
            }

            // Perform the drop operation
            const dropQuery = ifExists ? 
                `DROP GRAPH IF EXISTS <${graphURI}>` : 
                `DROP GRAPH <${graphURI}>`;
                
            const result = await sparqlHelper.executeUpdate(dropQuery);

            if (result.success) {
                return {
                    success: true,
                    message: `Successfully dropped graph: ${graphURI}`,
                    graphURI,
                    metadata: {
                        operation: 'drop',
                        triplesRemoved: tripleCount.count,
                        graphExisted: existsCheck.exists,
                        timestamp: new Date().toISOString(),
                        queryExecuted: dropQuery
                    }
                };
            } else {
                throw new Error(result.error || 'Failed to drop graph');
            }

        } catch (error) {
            logger.error('Failed to drop graph:', error.message);
            return {
                success: false,
                error: error.message,
                graphURI: input.graphURI,
                metadata: {
                    operation: 'drop',
                    errorOccurred: true,
                    timestamp: new Date().toISOString()
                }
            };
        }
    }

    /**
     * Get statistics about a graph
     * 
     * @param {Object} input - Statistics input data
     * @param {string} input.graphURI - URI of the graph to analyze
     * @param {Object} resources - External dependencies
     * @param {Object} resources.sparqlHelper - SPARQL helper for database operations
     * @param {Object} options - Configuration options
     * @param {boolean} options.includeTypes - Include type counts (default: true)
     * @param {boolean} options.includePredicates - Include predicate counts (default: true)
     * @returns {Promise<Object>} Graph statistics
     */
    async getGraphStatistics(input, resources, options = {}) {
        try {
            const { graphURI } = input;
            const { sparqlHelper } = resources;
            
            const statsConfig = {
                includeTypes: options.includeTypes !== false,
                includePredicates: options.includePredicates !== false,
                ...options
            };

            if (!graphURI || !sparqlHelper) {
                throw new Error('Graph URI and SPARQL helper are required');
            }

            const statistics = {
                graphURI,
                exists: false,
                tripleCount: 0,
                subjectCount: 0,
                predicateCount: 0,
                objectCount: 0,
                types: [],
                predicates: []
            };

            // Check if graph exists
            const existsCheck = await this._checkGraphExists(graphURI, sparqlHelper);
            statistics.exists = existsCheck.exists;
            
            if (!existsCheck.exists) {
                return {
                    success: true,
                    statistics,
                    metadata: {
                        operation: 'statistics',
                        graphExists: false,
                        timestamp: new Date().toISOString()
                    }
                };
            }

            // Get basic counts
            const basicStats = await this._getBasicStatistics(graphURI, sparqlHelper);
            Object.assign(statistics, basicStats);

            // Get type counts if requested
            if (statsConfig.includeTypes) {
                statistics.types = await this._getTypeCounts(graphURI, sparqlHelper);
            }

            // Get predicate counts if requested
            if (statsConfig.includePredicates) {
                statistics.predicates = await this._getPredicateCounts(graphURI, sparqlHelper);
            }

            return {
                success: true,
                statistics,
                metadata: {
                    operation: 'statistics',
                    graphExists: true,
                    includeTypes: statsConfig.includeTypes,
                    includePredicates: statsConfig.includePredicates,
                    timestamp: new Date().toISOString()
                }
            };

        } catch (error) {
            logger.error('Failed to get graph statistics:', error.message);
            return {
                success: false,
                error: error.message,
                graphURI: input.graphURI,
                metadata: {
                    operation: 'statistics',
                    errorOccurred: true,
                    timestamp: new Date().toISOString()
                }
            };
        }
    }

    /**
     * Check if a graph exists
     * @private
     */
    async _checkGraphExists(graphURI, sparqlHelper) {
        try {
            const query = `ASK WHERE { GRAPH <${graphURI}> { ?s ?p ?o } }`;
            const result = await sparqlHelper.executeSelect(query);
            
            return {
                exists: result.success && result.data.boolean === true,
                query
            };
        } catch (error) {
            logger.debug('Graph existence check failed:', error.message);
            return { exists: false };
        }
    }

    /**
     * Count triples in a graph
     * @private
     */
    async _countTriples(graphURI, sparqlHelper) {
        try {
            const query = `SELECT (COUNT(*) as ?count) WHERE { GRAPH <${graphURI}> { ?s ?p ?o } }`;
            const result = await sparqlHelper.executeSelect(query);
            
            if (result.success && result.data.results.bindings.length > 0) {
                return {
                    count: parseInt(result.data.results.bindings[0].count.value),
                    query
                };
            }
            
            return { count: 0 };
        } catch (error) {
            logger.debug('Triple count failed:', error.message);
            return { count: 0 };
        }
    }

    /**
     * Get basic graph statistics
     * @private
     */
    async _getBasicStatistics(graphURI, sparqlHelper) {
        try {
            const query = `SELECT 
    (COUNT(*) as ?tripleCount)
    (COUNT(DISTINCT ?s) as ?subjectCount)
    (COUNT(DISTINCT ?p) as ?predicateCount)
    (COUNT(DISTINCT ?o) as ?objectCount)
WHERE { 
    GRAPH <${graphURI}> { 
        ?s ?p ?o 
    } 
}`;

            const result = await sparqlHelper.executeSelect(query);
            
            if (result.success && result.data.results.bindings.length > 0) {
                const binding = result.data.results.bindings[0];
                return {
                    tripleCount: parseInt(binding.tripleCount?.value || 0),
                    subjectCount: parseInt(binding.subjectCount?.value || 0),
                    predicateCount: parseInt(binding.predicateCount?.value || 0),
                    objectCount: parseInt(binding.objectCount?.value || 0)
                };
            }
            
            return {
                tripleCount: 0,
                subjectCount: 0,
                predicateCount: 0,
                objectCount: 0
            };
        } catch (error) {
            logger.debug('Basic statistics failed:', error.message);
            return {
                tripleCount: 0,
                subjectCount: 0,
                predicateCount: 0,
                objectCount: 0
            };
        }
    }

    /**
     * Get type counts in graph
     * @private
     */
    async _getTypeCounts(graphURI, sparqlHelper) {
        try {
            const query = `SELECT ?type (COUNT(*) as ?count) WHERE { 
    GRAPH <${graphURI}> { 
        ?s a ?type 
    } 
} 
GROUP BY ?type 
ORDER BY DESC(?count)
LIMIT 20`;

            const result = await sparqlHelper.executeSelect(query);
            
            if (result.success) {
                return result.data.results.bindings.map(binding => ({
                    type: binding.type.value,
                    count: parseInt(binding.count.value)
                }));
            }
            
            return [];
        } catch (error) {
            logger.debug('Type counts failed:', error.message);
            return [];
        }
    }

    /**
     * Get predicate counts in graph
     * @private
     */
    async _getPredicateCounts(graphURI, sparqlHelper) {
        try {
            const query = `SELECT ?predicate (COUNT(*) as ?count) WHERE { 
    GRAPH <${graphURI}> { 
        ?s ?predicate ?o 
    } 
} 
GROUP BY ?predicate 
ORDER BY DESC(?count)
LIMIT 20`;

            const result = await sparqlHelper.executeSelect(query);
            
            if (result.success) {
                return result.data.results.bindings.map(binding => ({
                    predicate: binding.predicate.value,
                    count: parseInt(binding.count.value)
                }));
            }
            
            return [];
        } catch (error) {
            logger.debug('Predicate counts failed:', error.message);
            return [];
        }
    }
}