/**
 * GigLez Pulse Width Analysis using K-means clustering
 * JavaScript port of Python pattern_decoder.py pulse analysis
 *
 * @module pulse-analyzer
 */

/**
 * Simple K-means clustering for 1D data
 * Used to identify SHORT and LONG pulse widths
 *
 * @param {number[]} data - Array of pulse widths
 * @param {number} k - Number of clusters (default: 2 for SHORT/LONG)
 * @param {number} maxIterations - Maximum iterations (default: 100)
 * @returns {number[]} Array of cluster centers
 */
export function kMeans(data, k = 2, maxIterations = 100) {
  if (data.length < k) {
    return data.map(v => v);
  }

  // Initialize centroids using min and max values
  const sorted = [...data].sort((a, b) => a - b);
  let centroids = k === 2
    ? [sorted[0], sorted[sorted.length - 1]]
    : Array.from({ length: k }, (_, i) =>
        sorted[Math.floor((i / k) * sorted.length)]
      );

  for (let iter = 0; iter < maxIterations; iter++) {
    // Assign points to nearest centroid
    const clusters = Array.from({ length: k }, () => []);

    for (const point of data) {
      const distances = centroids.map(c => Math.abs(point - c));
      const nearestIdx = distances.indexOf(Math.min(...distances));
      clusters[nearestIdx].push(point);
    }

    // Update centroids (mean of each cluster)
    const newCentroids = clusters.map(cluster => {
      if (cluster.length === 0) return centroids[0]; // Handle empty cluster
      return cluster.reduce((sum, val) => sum + val, 0) / cluster.length;
    });

    // Check convergence
    const converged = centroids.every((c, i) =>
      Math.abs(c - newCentroids[i]) < 1
    );

    centroids = newCentroids;

    if (converged) break;
  }

  return centroids.sort((a, b) => a - b);
}

/**
 * Identify SHORT and LONG pulse widths from pulse train
 * Uses K-means clustering to automatically detect the two pulse durations
 *
 * @param {number[]} pulses - Raw pulse data (positive = HIGH, negative = LOW)
 * @returns {Object} { shortPulse, longPulse, shortGap, longGap }
 */
export function identifyPulseWidths(pulses) {
  // Separate HIGH pulses (positive) and LOW gaps (negative)
  const highPulses = pulses.filter(p => p > 0).map(Math.abs);
  const lowPulses = pulses.filter(p => p < 0).map(Math.abs);

  // Cluster into SHORT/LONG
  let shortPulse = 0, longPulse = 0;
  if (highPulses.length >= 2) {
    const [short, long] = kMeans(highPulses, 2);
    shortPulse = short;
    longPulse = long;
  } else if (highPulses.length === 1) {
    shortPulse = longPulse = highPulses[0];
  }

  let shortGap = 0, longGap = 0;
  if (lowPulses.length >= 2) {
    const [short, long] = kMeans(lowPulses, 2);
    shortGap = short;
    longGap = long;
  } else if (lowPulses.length === 1) {
    shortGap = longGap = lowPulses[0];
  }

  return { shortPulse, longPulse, shortGap, longGap };
}

/**
 * Decode pulse train to binary string using PWM encoding
 * SHORT pulse = 0, LONG pulse = 1
 *
 * @param {number[]} pulses - Raw pulse data
 * @param {number} shortPulse - SHORT pulse width (microseconds)
 * @param {number} longPulse - LONG pulse width (microseconds)
 * @returns {string} Binary string (e.g., "101010110")
 */
export function decodeToBits(pulses, shortPulse, longPulse) {
  const threshold = (shortPulse + longPulse) / 2;
  const bits = [];

  for (const pulse of pulses) {
    if (pulse > 0) {  // Only decode HIGH pulses
      const duration = Math.abs(pulse);
      bits.push(duration < threshold ? '0' : '1');
    }
  }

  return bits.join('');
}

/**
 * Analyze pulse train and extract timing information
 * Returns comprehensive timing analysis for debugging
 *
 * @param {number[]} pulses - Raw pulse data
 * @returns {Object} Timing analysis including pulse widths, counts, patterns
 */
export function analyzePulseTrain(pulses) {
  const highPulses = pulses.filter(p => p > 0).map(Math.abs);
  const lowPulses = pulses.filter(p => p < 0).map(Math.abs);

  const { shortPulse, longPulse, shortGap, longGap } = identifyPulseWidths(pulses);
  const bitPattern = decodeToBits(pulses, shortPulse, longPulse);

  return {
    // Pulse width identification
    shortPulse,
    longPulse,
    shortGap,
    longGap,

    // Bit pattern
    bitPattern,
    bitCount: bitPattern.length,

    // Counts
    totalPulses: pulses.length,
    highPulseCount: highPulses.length,
    lowPulseCount: lowPulses.length,

    // High pulse statistics
    highPulseStats: {
      min: Math.min(...highPulses),
      max: Math.max(...highPulses),
      mean: highPulses.reduce((a, b) => a + b, 0) / highPulses.length,
      median: highPulses.sort((a, b) => a - b)[Math.floor(highPulses.length / 2)]
    },

    // Low pulse (gap) statistics
    lowPulseStats: {
      min: Math.min(...lowPulses),
      max: Math.max(...lowPulses),
      mean: lowPulses.reduce((a, b) => a + b, 0) / lowPulses.length,
      median: lowPulses.sort((a, b) => a - b)[Math.floor(lowPulses.length / 2)]
    }
  };
}

/**
 * Validate pulse data quality
 * Checks for common issues in RF captures
 *
 * @param {number[]} pulses - Raw pulse data
 * @returns {Object} { valid: boolean, issues: string[] }
 */
export function validatePulseData(pulses) {
  const issues = [];

  if (!pulses || pulses.length === 0) {
    return { valid: false, issues: ['No pulse data provided'] };
  }

  if (pulses.length < 20) {
    issues.push(`Very short capture (${pulses.length} pulses) - may not be decodable`);
  }

  const allPositive = pulses.every(p => p > 0);
  const allNegative = pulses.every(p => p < 0);

  if (allPositive || allNegative) {
    issues.push('Pulse data is all positive or all negative - should alternate');
  }

  const highPulses = pulses.filter(p => p > 0).map(Math.abs);
  const lowPulses = pulses.filter(p => p < 0).map(Math.abs);

  if (highPulses.length < 5) {
    issues.push('Too few HIGH pulses for reliable analysis');
  }

  if (lowPulses.length < 5) {
    issues.push('Too few LOW pulses (gaps) for reliable analysis');
  }

  // Check for outliers (pulses > 100ms are suspicious)
  const outliers = pulses.filter(p => Math.abs(p) > 100000);
  if (outliers.length > 0) {
    issues.push(`${outliers.length} suspiciously long pulses (>100ms) detected`);
  }

  return {
    valid: issues.length === 0,
    issues
  };
}

/**
 * Calculate Signal-to-Noise Ratio (SNR) estimate
 * Based on pulse width consistency
 *
 * @param {number[]} pulses - Raw pulse data
 * @returns {number} SNR estimate (0-1, higher is better)
 */
export function estimateSNR(pulses) {
  const { shortPulse, longPulse } = identifyPulseWidths(pulses);
  const highPulses = pulses.filter(p => p > 0).map(Math.abs);

  if (highPulses.length < 10) return 0;

  // Count how many pulses are close to SHORT or LONG
  const threshold = (shortPulse + longPulse) / 2;
  const shortTolerance = shortPulse * 0.3;
  const longTolerance = longPulse * 0.3;

  const consistentPulses = highPulses.filter(p => {
    const nearShort = Math.abs(p - shortPulse) <= shortTolerance;
    const nearLong = Math.abs(p - longPulse) <= longTolerance;
    return nearShort || nearLong;
  });

  return consistentPulses.length / highPulses.length;
}
