/**
 * Aggregate result type definition
 */
export interface AggregateResult {
    key: string;
    count: number;
    values: any[];
    average?: number;
}

interface AggregateOptions {
    collectValues?: boolean;
}

/**
 * DataProcessor: 
 * Core aggregation engine for maximizing algorithmic efficiency and maintainability.
 * Guarantees O(N) complexity with data distribution-sensitive optimization strategy.
 */
export class DataProcessor {
    /**
     * Core data aggregation function (Optimized O(N))
     * @param data Array to aggregate
     * @param keyPath Property path to group by
     */
    public static aggregate(data: any[], keyPath: string, options: AggregateOptions = {}): AggregateResult[] {
        if (!data || data.length === 0) return [];
        if (!Array.isArray(data)) {
            throw new TypeError('DataProcessor.aggregate expects data to be an array.');
        }

        const pathSegments = this.parseKeyPath(keyPath);
        const collectValues = options.collectValues !== false;
        
        const map = new Map<string, AggregateResult>();

        for (const item of data) {
            try {
                const keyValue = this.getNestedValue(item, pathSegments);
                if (keyValue === undefined || keyValue === null) continue;

                const key = String(keyValue);
                let entry = map.get(key);

                if (!entry) {
                    entry = {
                        key,
                        count: 0,
                        values: []
                    };
                    map.set(key, entry);
                }

                entry.count++;
                if (collectValues) {
                    entry.values.push(item);
                }

            } catch (error) {
                // Isolate item processing failures to prevent entire aggregation from aborting
                console.warn(`[DataProcessor] Skip item due to error: ${error}`);
            }
        }

        return Array.from(map.values());
    }

    /**
     * Nested object property accessor (safety handling).
     * keyPath is parsed once outside the loop to avoid repeated split costs on large datasets.
     */
    private static getNestedValue(obj: any, pathSegments: string[]): any {
        return pathSegments.reduce((prev, curr) => {
            if (prev === undefined || prev === null) return undefined;
            return prev[curr];
        }, obj);
    }

    private static parseKeyPath(keyPath: string): string[] {
        if (typeof keyPath !== 'string' || keyPath.trim().length === 0) {
            throw new TypeError('DataProcessor.aggregate expects keyPath to be a non-empty string.');
        }

        const segments = keyPath.split('.').map(segment => segment.trim());
        if (segments.some(segment => segment.length === 0)) {
            throw new TypeError('DataProcessor.aggregate received an invalid keyPath.');
        }

        const forbiddenSegments = new Set(['__proto__', 'prototype', 'constructor']);
        if (segments.some(segment => forbiddenSegments.has(segment))) {
            throw new TypeError('DataProcessor.aggregate received an unsafe keyPath.');
        }

        return segments;
    }
}
