/**
 * GigLez Pattern-Based Decoder
 * JavaScript port of Python pattern_decoder.py
 *
 * Main decoder class combining timing analysis, fingerprinting,
 * and protocol matching for single-transmission RF captures
 *
 * @module pattern-decoder
 */

import { protocolDb } from './protocol-database.js';
import { identifyPulseWidths, decodeToBits, analyzePulseTrain, validatePulseData, estimateSNR } from './pulse-analyzer.js';
import { extractFingerprint, calculateSimilarity, compareToProtocol, classifySignal } from './fingerprint.js';

/**
 * Device Match Result
 * Standardized format matching Python MatchResult
 */
export class DeviceMatch {
  constructor({
    deviceName,
    manufacturer = 'Unknown',
    confidence = 0.0,
    matchMethod = 'pattern',
    matchDetails = {}
  }) {
    this.deviceName = deviceName;
    this.manufacturer = manufacturer;
    this.confidence = confidence;
    this.matchMethod = matchMethod;
    this.matchDetails = matchDetails;
  }

  /**
   * Convert to plain object for JSON serialization
   */
  toObject() {
    return {
      device_name: this.deviceName,
      manufacturer: this.manufacturer,
      confidence: this.confidence,
      match_method: this.matchMethod,
      match_details: this.matchDetails
    };
  }

  /**
   * Human-readable string representation
   */
  toString() {
    return `${this.manufacturer} ${this.deviceName} (${(this.confidence * 100).toFixed(1)}% via ${this.matchMethod})`;
  }
}

/**
 * Pattern-Based RF Signal Decoder
 *
 * Decodes single-transmission Sub-GHz captures from Flipper Zero,
 * LilyGo T-Embed CC1101, and other devices
 */
export class PatternDecoder {
  constructor(options = {}) {
    this.minConfidence = options.minConfidence || 0.4;
    this.maxResults = options.maxResults || 10;
    this.debug = options.debug || false;

    // Load protocol database
    this.protocolDb = protocolDb;

    // Statistics
    this.stats = {
      totalDecodes: 0,
      successfulDecodes: 0,
      failedDecodes: 0,
      averageConfidence: 0
    };
  }

  /**
   * Main decoding method - analyze .sub file RAW_Data
   *
   * @param {Object} metadata - Parsed .sub file metadata
   * @param {number} metadata.frequency - Frequency in Hz
   * @param {string} metadata.preset - Modulation preset
   * @param {string} metadata.protocol - Protocol name (may be "RAW")
   * @param {number[]} metadata.raw_data - Array of pulse timings (positive=HIGH, negative=LOW)
   * @returns {DeviceMatch[]} Array of device matches sorted by confidence
   */
  decode(metadata) {
    this.stats.totalDecodes++;

    try {
      // Validate input
      if (!metadata.raw_data || metadata.raw_data.length === 0) {
        if (this.debug) console.log('❌ No RAW_Data found in metadata');
        this.stats.failedDecodes++;
        return [];
      }

      // Validate pulse data quality
      const validation = validatePulseData(metadata.raw_data);
      if (!validation.valid) {
        if (this.debug) {
          console.log('⚠️  Pulse data quality issues:');
          validation.issues.forEach(issue => console.log(`   - ${issue}`));
        }
        // Continue anyway, but flag low quality
      }

      // Estimate signal quality
      const snr = estimateSNR(metadata.raw_data);
      if (this.debug) {
        console.log(`📊 Signal Quality: ${(snr * 100).toFixed(1)}%`);
      }

      const matches = [];

      // Strategy 1: Timing Pattern Analysis
      const timingMatches = this._decodeTimingPatterns(metadata);
      matches.push(...timingMatches);

      // Strategy 2: Statistical Fingerprint Matching
      const fingerprintMatches = this._matchFingerprint(metadata);
      matches.push(...fingerprintMatches);

      // Strategy 3: Heuristic Classification (fallback)
      if (matches.length === 0) {
        const heuristicMatches = this._classifyByHeuristics(metadata);
        matches.push(...heuristicMatches);
      }

      // Deduplicate and rank matches
      const rankedMatches = this._rankMatches(matches);

      // Filter by minimum confidence
      const filteredMatches = rankedMatches.filter(m => m.confidence >= this.minConfidence);

      // Limit results
      const finalMatches = filteredMatches.slice(0, this.maxResults);

      // Update statistics
      if (finalMatches.length > 0) {
        this.stats.successfulDecodes++;
        const avgConf = finalMatches.reduce((sum, m) => sum + m.confidence, 0) / finalMatches.length;
        this.stats.averageConfidence = (this.stats.averageConfidence * (this.stats.successfulDecodes - 1) + avgConf) / this.stats.successfulDecodes;
      } else {
        this.stats.failedDecodes++;
      }

      if (this.debug) {
        console.log(`✅ Found ${finalMatches.length} matches`);
        finalMatches.forEach((m, i) => {
          console.log(`   ${i + 1}. ${m.toString()}`);
        });
      }

      return finalMatches;

    } catch (error) {
      if (this.debug) {
        console.error('❌ Decode error:', error);
      }
      this.stats.failedDecodes++;
      return [];
    }
  }

  /**
   * Strategy 1: Timing Pattern Analysis
   * Uses K-means clustering to identify pulse widths and match against protocol library
   *
   * @private
   */
  _decodeTimingPatterns(metadata) {
    const matches = [];

    try {
      // Identify SHORT and LONG pulse widths using K-means
      const { shortPulse, longPulse, shortGap, longGap } = identifyPulseWidths(metadata.raw_data);

      if (this.debug) {
        console.log(`🔍 Timing Analysis:`);
        console.log(`   Short Pulse: ${shortPulse.toFixed(1)} μs`);
        console.log(`   Long Pulse:  ${longPulse.toFixed(1)} μs`);
        console.log(`   Short Gap:   ${shortGap.toFixed(1)} μs`);
        console.log(`   Long Gap:    ${longGap.toFixed(1)} μs`);
      }

      // Match against protocol database
      const protocolMatches = this.protocolDb.findByTiming(
        shortPulse,
        longPulse,
        metadata.frequency
      );

      if (this.debug && protocolMatches.length > 0) {
        console.log(`📚 Protocol Database Matches: ${protocolMatches.length}`);
      }

      // Convert protocol matches to DeviceMatch objects
      for (const proto of protocolMatches) {
        // Calculate confidence based on timing accuracy
        const shortError = Math.abs(shortPulse - proto.shortPulseUs) / proto.shortPulseUs;
        const longError = Math.abs(longPulse - proto.longPulseUs) / proto.longPulseUs;
        const avgError = (shortError + longError) / 2;
        const timingConfidence = Math.max(0, 1 - avgError);

        // Frequency match bonus
        const freqMatch = proto.matchesFrequency(metadata.frequency);
        const freqBonus = freqMatch ? 0.1 : 0;

        const confidence = Math.min(1.0, timingConfidence + freqBonus);

        // Decode bit pattern
        const bitPattern = decodeToBits(metadata.raw_data, shortPulse, longPulse);

        matches.push(new DeviceMatch({
          deviceName: proto.name,
          manufacturer: proto.manufacturer || proto.category,
          confidence: confidence,
          matchMethod: 'timing_pattern',
          matchDetails: {
            protocol_name: proto.name,
            category: proto.category,
            short_pulse_us: shortPulse,
            long_pulse_us: longPulse,
            expected_short: proto.shortPulseUs,
            expected_long: proto.longPulseUs,
            timing_error: `${(avgError * 100).toFixed(2)}%`,
            frequency_match: freqMatch,
            bit_pattern: bitPattern,
            bit_count: bitPattern.length
          }
        }));
      }

    } catch (error) {
      if (this.debug) {
        console.error('⚠️  Timing pattern analysis failed:', error);
      }
    }

    return matches;
  }

  /**
   * Strategy 2: Statistical Fingerprint Matching
   * Extracts statistical characteristics and compares against protocol library
   *
   * @private
   */
  _matchFingerprint(metadata) {
    const matches = [];

    try {
      // Extract statistical fingerprint
      const fingerprint = extractFingerprint(metadata.raw_data);

      if (this.debug) {
        console.log(`🔬 Fingerprint: ${fingerprint.toString()}`);
      }

      // Compare against all protocols
      for (const proto of this.protocolDb.getAll()) {
        const comparison = compareToProtocol(fingerprint, {
          shortPulseUs: proto.shortPulseUs,
          longPulseUs: proto.longPulseUs,
          typicalPulseCount: proto.typicalPulseCount
        });

        // Only include if confidence is reasonable
        if (comparison.overall >= 0.3) {
          matches.push(new DeviceMatch({
            deviceName: proto.name,
            manufacturer: proto.manufacturer || proto.category,
            confidence: comparison.overall,
            matchMethod: 'fingerprint',
            matchDetails: {
              protocol_name: proto.name,
              category: proto.category,
              pulse_similarity: comparison.pulseSimilarity.toFixed(3),
              count_similarity: comparison.countSimilarity.toFixed(3),
              pulse_error: comparison.pulseError,
              count_error: comparison.countError,
              mean_pulse_width: fingerprint.meanPulseWidth.toFixed(1),
              duty_cycle: `${(fingerprint.dutyCycle * 100).toFixed(1)}%`,
              pulse_count: fingerprint.pulseCount
            }
          }));
        }
      }

    } catch (error) {
      if (this.debug) {
        console.error('⚠️  Fingerprint matching failed:', error);
      }
    }

    return matches;
  }

  /**
   * Strategy 3: Heuristic Classification (Fallback)
   * Uses signal characteristics to guess device type when no protocol match
   *
   * @private
   */
  _classifyByHeuristics(metadata) {
    const matches = [];

    try {
      const fingerprint = extractFingerprint(metadata.raw_data);
      const classification = classifySignal(fingerprint);

      if (classification.confidence >= 0.3) {
        matches.push(new DeviceMatch({
          deviceName: classification.type,
          manufacturer: 'Unknown',
          confidence: classification.confidence,
          matchMethod: 'heuristic',
          matchDetails: {
            classification: classification.type,
            reason: classification.reason,
            mean_pulse_width: fingerprint.meanPulseWidth.toFixed(1),
            pulse_count: fingerprint.pulseCount,
            duty_cycle: `${(fingerprint.dutyCycle * 100).toFixed(1)}%`
          }
        }));
      }

    } catch (error) {
      if (this.debug) {
        console.error('⚠️  Heuristic classification failed:', error);
      }
    }

    return matches;
  }

  /**
   * Deduplicate and rank matches by confidence
   *
   * @private
   * @param {DeviceMatch[]} matches - Array of device matches
   * @returns {DeviceMatch[]} Sorted and deduplicated matches
   */
  _rankMatches(matches) {
    // Group by device name + manufacturer
    const grouped = {};

    for (const match of matches) {
      const key = `${match.manufacturer}::${match.deviceName}`;

      if (!grouped[key]) {
        grouped[key] = match;
      } else {
        // Keep match with higher confidence
        if (match.confidence > grouped[key].confidence) {
          grouped[key] = match;
        }
      }
    }

    // Convert back to array and sort by confidence
    const deduplicated = Object.values(grouped);
    deduplicated.sort((a, b) => b.confidence - a.confidence);

    return deduplicated;
  }

  /**
   * Analyze pulse train for debugging
   * Returns detailed timing information
   *
   * @param {number[]} pulses - Raw pulse data
   * @returns {Object} Detailed analysis
   */
  analyze(pulses) {
    const analysis = analyzePulseTrain(pulses);
    const fingerprint = extractFingerprint(pulses);
    const validation = validatePulseData(pulses);
    const snr = estimateSNR(pulses);

    return {
      timing: analysis,
      fingerprint: fingerprint.toObject(),
      validation,
      snr,
      quality: snr > 0.8 ? 'excellent' : snr > 0.6 ? 'good' : snr > 0.4 ? 'fair' : 'poor'
    };
  }

  /**
   * Get decoder statistics
   * @returns {Object} Decoder stats
   */
  getStats() {
    return {
      ...this.stats,
      successRate: this.stats.totalDecodes > 0
        ? (this.stats.successfulDecodes / this.stats.totalDecodes * 100).toFixed(1) + '%'
        : '0%'
    };
  }

  /**
   * Reset statistics
   */
  resetStats() {
    this.stats = {
      totalDecodes: 0,
      successfulDecodes: 0,
      failedDecodes: 0,
      averageConfidence: 0
    };
  }
}

/**
 * Convenience function to decode a .sub file metadata object
 *
 * @param {Object} metadata - Parsed .sub file
 * @param {Object} options - Decoder options
 * @returns {DeviceMatch[]} Device matches
 */
export function decode(metadata, options = {}) {
  const decoder = new PatternDecoder(options);
  return decoder.decode(metadata);
}
