class DynamicClusterDeviation {
    constructor() {
        this.clusters = []; // Stores clusters as { center, spread, count }
    }

    update(value) {
        console.log(`\nProcessing value: ${value}`);

        // If no clusters exist, create the first one
        if (this.clusters.length === 0) {
            this.clusters.push({ center: value, spread: 0, count: 1 });
            console.log(`  → First cluster created at ${value}`);
            return { value, isOutlier: false };
        }

        // Step 1: Find the closest cluster
        let bestMatch = null;
        let minDistance = Infinity;

        for (const cluster of this.clusters) {
            const distance = Math.abs(value - cluster.center);
            console.log(`  Checking against cluster at ${cluster.center} (spread: ${cluster.spread}, count: ${cluster.count}) → distance: ${distance}`);

            if (distance < minDistance) {
                bestMatch = cluster;
                minDistance = distance;
            }
        }

        console.log(`  Closest cluster found at ${bestMatch.center} with distance: ${minDistance}`);

        // Step 2: Compute dynamic threshold
        const dynamicThreshold = 1 + 5 / Math.sqrt(bestMatch.count + 1);
        const allowedDeviation = dynamicThreshold * (bestMatch.spread || 1);

        console.log(`  Dynamic threshold: ${dynamicThreshold.toFixed(2)}, Allowed deviation: ${allowedDeviation.toFixed(2)}`);

        // Step 3: Check if value fits within the dynamically adjusted cluster spread
        if (minDistance <= allowedDeviation) {
            // Update cluster dynamically
            const newCenter = (bestMatch.center * bestMatch.count + value) / (bestMatch.count + 1);
            const newSpread = Math.max(bestMatch.spread, minDistance);
            bestMatch.center = newCenter;
            bestMatch.spread = newSpread;
            bestMatch.count += 1;

            console.log(`  ✅ Value fits in cluster! Updating cluster:`);
            console.log(`    → New center: ${newCenter.toFixed(2)}`);
            console.log(`    → New spread: ${newSpread.toFixed(2)}`);
            console.log(`    → New count: ${bestMatch.count}`);
            
            return { value, isOutlier: false };
        } else {
            // If too far, create a new cluster
            this.clusters.push({ center: value, spread: 0, count: 1 });

            console.log(`  ❌ Outlier detected! New cluster created at ${value}`);

            return { value, isOutlier: true };
        }
    }
}

// Rolling window simulation with outlier detection
/*
const detector = new DynamicClusterDeviation();
const dataStream = [10, 10.2, 10.5, 9.8, 11, 50, 10.3, 200, 201, 200.1, 205, 202, 250, 260, 270, 280, 290, 300];

// Define the number of elements per rolling window chunk.
const windowSize = 5;
let rollingWindow = [];

dataStream.forEach((value, index) => {
    console.log(`\n=== Processing value ${index + 1} ===`);
    rollingWindow.push(value);
    const result = detector.update(value);
    console.log(`Current rolling window: [${rollingWindow.join(', ')}]`);
    console.log(`Result: value=${result.value} (${result.isOutlier ? 'Outlier' : 'Inlier'})`);
    
    // Once the window size is reached, show current cluster states and reset the window for the next chunk.
    if (rollingWindow.length === windowSize) {
        console.log("\n--- Rolling window chunk finished ---");
        console.log("Detector cluster states:", JSON.stringify(detector.clusters, null, 2));
        rollingWindow = [];
    }
});

console.log("\nFinal detector cluster states:", JSON.stringify(detector.clusters, null, 2));
*/