/**
 * QMT-based OWA Fusion - Theoretically Sound Capacity Combination
 * 
 * This module implements OWA-like operators that work directly on Qualitative Möbius Transforms (QMTs),
 * ensuring that the resulting set-function is always a valid qualitative capacity (monotonic).
 * 
 * This addresses the theoretical issues with pointwise OWA fusion by operating on the canonical
 * representation of capacities rather than their output values.
 */

import { QualitativeScale, DEFAULT_QUALITATIVE_SCALE } from './QualitativeScale.js';
import { QualitativeCapacity } from './QualitativeCapacity.js';

export class QMTOWAFusion {
  
  /**
   * Optimistic QMT Fusion
   * 
   * Combines QMTs in an optimistic manner by taking the maximum weight for each focal set
   * across all capacities. This preserves monotonicity since we're working with focal sets
   * and their weights directly.
   * 
   * For each focal set E that appears in any capacity:
   * γ#_optimistic(E) = max_{i=1}^k γ#_i(E)
   * 
   * @param {Array<QualitativeCapacity>} capacities - Array of capacities to combine
   * @returns {QualitativeCapacity} Optimistically fused capacity
   */
  static optimisticFusion(capacities) {
    if (!capacities.length) {
      throw new Error('At least one capacity is required');
    }
    
    const scale = capacities[0].scale;
    const stateSpace = capacities[0].stateSpace;
    
    // Validate all capacities use the same scale and state space
    for (const capacity of capacities) {
      if (!capacity.scale.equals(scale)) {
        throw new Error('All capacities must use the same qualitative scale');
      }
      if (capacity.stateSpace.length !== stateSpace.length) {
        throw new Error('All capacities must use the same state space');
      }
    }
    
    const resultQMT = new Map();
    
    // Collect all focal sets from all capacities using string keys for comparison
    const allFocalSetKeys = new Set();
    const focalSetMap = new Map(); // Map from string key to actual Set
    
    for (const capacity of capacities) {
      for (const focalSet of capacity.getFocalSets()) {
        const key = Array.from(focalSet).sort().join(',');
        allFocalSetKeys.add(key);
        focalSetMap.set(key, focalSet);
      }
    }
    
    // For each focal set, take the maximum weight across all capacities
    for (const key of allFocalSetKeys) {
      const focalSet = focalSetMap.get(key);
      let maxWeight = scale.bottom;
      
      for (const capacity of capacities) {
        const weight = capacity.getQMT(focalSet);
        maxWeight = scale.max(maxWeight, weight);
      }
      
      if (maxWeight !== scale.bottom) {
        resultQMT.set(focalSet, maxWeight);
      }
    }
    
    return new QualitativeCapacity(stateSpace, scale, resultQMT);
  }
  
  /**
   * Pessimistic QMT Fusion
   * 
   * Combines QMTs in a pessimistic manner by taking the minimum weight for each focal set
   * that appears in ALL capacities. This is more conservative than optimistic fusion.
   * 
   * For each focal set E that appears in ALL capacities:
   * γ#_pessimistic(E) = min_{i=1}^k γ#_i(E)
   * 
   * @param {Array<QualitativeCapacity>} capacities - Array of capacities to combine
   * @returns {QualitativeCapacity} Pessimistically fused capacity
   */
  static pessimisticFusion(capacities) {
    if (!capacities.length) {
      throw new Error('At least one capacity is required');
    }
    
    const scale = capacities[0].scale;
    const stateSpace = capacities[0].stateSpace;
    
    // Validate all capacities use the same scale and state space
    for (const capacity of capacities) {
      if (!capacity.scale.equals(scale)) {
        throw new Error('All capacities must use the same qualitative scale');
      }
      if (capacity.stateSpace.length !== stateSpace.length) {
        throw new Error('All capacities must use the same state space');
      }
    }
    
    const resultQMT = new Map();
    
    // Find focal sets that appear in ALL capacities
    const firstCapacityFocalSets = capacities[0].getFocalSets();
    
    for (const focalSet of firstCapacityFocalSets) {
      // Check if this focal set appears in all capacities
      let appearsInAll = true;
      let minWeight = scale.top;
      
      for (const capacity of capacities) {
        // Check if this focal set exists in the capacity by comparing with all focal sets
        let found = false;
        for (const capFocalSet of capacity.getFocalSets()) {
          if (this._setsEqual(focalSet, capFocalSet)) {
            found = true;
            const weight = capacity.getQMT(capFocalSet);
            minWeight = scale.min(minWeight, weight);
            break;
          }
        }
        if (!found) {
          appearsInAll = false;
          break;
        }
      }
      
      if (appearsInAll && minWeight !== scale.bottom) {
        resultQMT.set(focalSet, minWeight);
      }
    }
    
    return new QualitativeCapacity(stateSpace, scale, resultQMT);
  }
  
  /**
   * Majority QMT Fusion
   * 
   * Combines QMTs using a majority rule: for each focal set, take the median weight
   * across all capacities that contain it.
   * 
   * @param {Array<QualitativeCapacity>} capacities - Array of capacities to combine
   * @returns {QualitativeCapacity} Majority fused capacity
   */
  static majorityFusion(capacities) {
    if (!capacities.length) {
      throw new Error('At least one capacity is required');
    }
    
    const scale = capacities[0].scale;
    const stateSpace = capacities[0].stateSpace;
    
    // Validate all capacities use the same scale and state space
    for (const capacity of capacities) {
      if (!capacity.scale.equals(scale)) {
        throw new Error('All capacities must use the same qualitative scale');
      }
      if (capacity.stateSpace.length !== stateSpace.length) {
        throw new Error('All capacities must use the same state space');
      }
    }
    
    const resultQMT = new Map();
    
    // Collect all focal sets from all capacities using string keys for comparison
    const allFocalSetKeys = new Set();
    const focalSetMap = new Map(); // Map from string key to actual Set
    
    for (const capacity of capacities) {
      for (const focalSet of capacity.getFocalSets()) {
        const key = Array.from(focalSet).sort().join(',');
        allFocalSetKeys.add(key);
        focalSetMap.set(key, focalSet);
      }
    }
    
    // For each focal set, compute median weight
    for (const key of allFocalSetKeys) {
      const focalSet = focalSetMap.get(key);
      const weights = [];
      
      for (const capacity of capacities) {
        const weight = capacity.getQMT(focalSet);
        if (weight !== scale.bottom) {
          weights.push(weight);
        }
      }
      
      if (weights.length > 0) {
        // Sort weights and take median
        weights.sort((a, b) => scale.compare(a, b));
        const medianIndex = Math.floor(weights.length / 2);
        const medianWeight = weights[medianIndex];
        
        resultQMT.set(focalSet, medianWeight);
      }
    }
    
    return new QualitativeCapacity(stateSpace, scale, resultQMT);
  }
  
  /**
   * Priority-weighted QMT Fusion
   * 
   * Combines QMTs using priority weights. For each focal set, the result is the
   * weighted maximum where weights are determined by capacity priorities.
   * 
   * @param {Array<QualitativeCapacity>} capacities - Array of capacities to combine
   * @param {Array<number>} priorities - Priority weights for each capacity
   * @returns {QualitativeCapacity} Priority-weighted fused capacity
   */
  static priorityFusion(capacities, priorities) {
    if (!capacities.length) {
      throw new Error('At least one capacity is required');
    }
    
    if (!priorities || priorities.length !== capacities.length) {
      throw new Error('Priorities array must have same length as capacities array');
    }
    
    const scale = capacities[0].scale;
    const stateSpace = capacities[0].stateSpace;
    
    // Validate all capacities use the same scale and state space
    for (const capacity of capacities) {
      if (!capacity.scale.equals(scale)) {
        throw new Error('All capacities must use the same qualitative scale');
      }
      if (capacity.stateSpace.length !== stateSpace.length) {
        throw new Error('All capacities must use the same state space');
      }
    }
    
    const resultQMT = new Map();
    
    // Collect all focal sets from all capacities using string keys for comparison
    const allFocalSetKeys = new Set();
    const focalSetMap = new Map(); // Map from string key to actual Set
    
    for (const capacity of capacities) {
      for (const focalSet of capacity.getFocalSets()) {
        const key = Array.from(focalSet).sort().join(',');
        allFocalSetKeys.add(key);
        focalSetMap.set(key, focalSet);
      }
    }
    
    // For each focal set, compute priority-weighted result
    for (const key of allFocalSetKeys) {
      const focalSet = focalSetMap.get(key);
      let bestWeight = scale.bottom;
      let bestPriority = -1;
      
      for (let i = 0; i < capacities.length; i++) {
        const weight = capacities[i].getQMT(focalSet);
        const priority = priorities[i] || 0;
        
        if (weight !== scale.bottom && priority > bestPriority) {
          bestWeight = weight;
          bestPriority = priority;
        }
      }
      
      if (bestWeight !== scale.bottom) {
        resultQMT.set(focalSet, bestWeight);
      }
    }
    
    return new QualitativeCapacity(stateSpace, scale, resultQMT);
  }
  
  /**
   * Custom QMT Fusion with OWA-like Weights
   * 
   * This is a more sophisticated fusion method that attempts to implement
   * OWA-like behavior directly on QMTs. It's an experimental approach that
   * requires further theoretical development.
   * 
   * @param {Array<QualitativeCapacity>} capacities - Array of capacities to combine
   * @param {Array<number>} owaWeights - OWA weights for each capacity
   * @param {string} mode - Fusion mode ('optimistic', 'pessimistic', 'majority')
   * @returns {QualitativeCapacity} Custom fused capacity
   */
  static customOWAFusion(capacities, owaWeights, mode = 'optimistic') {
    if (!capacities.length) {
      throw new Error('At least one capacity is required');
    }
    
    if (!owaWeights || owaWeights.length !== capacities.length) {
      throw new Error('OWA weights array must have same length as capacities array');
    }
    
    const scale = capacities[0].scale;
    const stateSpace = capacities[0].stateSpace;
    
    // Validate all capacities use the same scale and state space
    for (const capacity of capacities) {
      if (!capacity.scale.equals(scale)) {
        throw new Error('All capacities must use the same qualitative scale');
      }
      if (capacity.stateSpace.length !== stateSpace.length) {
        throw new Error('All capacities must use the same state space');
      }
    }
    
    // For now, fall back to optimistic fusion with priority weighting
    // This is a placeholder for more sophisticated QMT-based OWA implementation
    const priorities = owaWeights.map(w => w * 100); // Convert to priority scale
    return this.priorityFusion(capacities, priorities);
  }
  
  /**
   * Helper method to check if two sets are equal
   * @param {Set} set1 - First set
   * @param {Set} set2 - Second set
   * @returns {boolean} True if sets are equal
   */
  static _setsEqual(set1, set2) {
    if (set1.size !== set2.size) return false;
    for (const item of set1) {
      if (!set2.has(item)) return false;
    }
    return true;
  }
}
