/**
 * Evidence Aggregation System
 * 
 * This module provides a unified system for aggregating evidence using OWA (Ordered Weighted Averaging)
 * operators, separate from reconciliation logic. It supports both qualitative and quantitative modes
 * with the same aggregation lexicon for consistency.
 * 
 * Key Features:
 * - Unified aggregation lexicon for both qualitative and quantitative modes
 * - OWA weight generation for sophisticated aggregation
 * - Support for custom weights and reliability weighting
 * - Integration with both qualitative and quantitative fusion systems
 */

import { OWAFusion } from '../utils/OWAFusion.js';
import { OWAQualitativeFusion, getOWAQualitativeWeights } from './OWAQualitativeFusion.js';
import { QualitativeScale } from './QualitativeScale.js';

export class EvidenceAggregation {
  /**
   * Aggregate evidence using OWA operators
   * @param {Array} values - Array of values to aggregate
   * @param {Array} metas - Array of metadata for each value
   * @param {Object} options - Aggregation options
   * @returns {Object} Aggregation result
   */
  static aggregate(values, metas = [], options = {}) {
    const {
      mode = 'quantitative',        // 'qualitative' or 'quantitative'
      aggregator = 'max',           // OWA aggregation method
      weights = null,               // Custom weights (optional)
      reliabilityWeighting = false, // Whether to weight by reliability
      scale = null,                 // QualitativeScale for qualitative mode
      customWeights = null          // Custom OWA weights
    } = options;

    if (!values || values.length === 0) {
      return {
        value: mode === 'qualitative' ? (scale?.bottom || 0) : 0,
        possibility: mode === 'qualitative' ? (scale?.bottom || 0) : 0,
        hasValue: false,
        aggregationMethod: aggregator,
        mode
      };
    }

    if (mode === 'qualitative') {
      return this._aggregateQualitative(values, metas, {
        aggregator,
        weights,
        reliabilityWeighting,
        scale: scale || QualitativeScale.fivePoint(),
        customWeights
      });
    } else {
      return this._aggregateQuantitative(values, metas, {
        aggregator,
        weights,
        reliabilityWeighting,
        customWeights
      });
    }
  }

  /**
   * Aggregate qualitative evidence using OWA operators
   * @private
   */
  static _aggregateQualitative(values, metas, options) {
    const {
      aggregator,
      weights,
      reliabilityWeighting,
      scale,
      customWeights
    } = options;

    // Generate OWA weights
    let owaWeights;
    if (customWeights) {
      owaWeights = customWeights;
    } else {
      owaWeights = getOWAQualitativeWeights(aggregator, values.length, null, scale);
    }

    // Apply reliability weighting if requested
    let weightedValues = values;
    if (reliabilityWeighting && metas.length > 0) {
      weightedValues = values.map((value, index) => {
        const meta = metas[index] || {};
        const reliability = meta.reliability || 1.0;
        // In qualitative mode, we use min operation for reliability weighting
        return scale.min(value, reliability);
      });
    }

    // Perform qualitative OWA fusion
    const result = OWAQualitativeFusion.fuseWithMeta(
      weightedValues,
      owaWeights,
      owaWeights,
      aggregator,
      scale
    );

    return {
      value: result.value,
      possibility: result.value, // In qualitative mode, value and possibility are the same
      hasValue: true,
      aggregationMethod: aggregator,
      mode: 'qualitative',
      weights: owaWeights,
      reliabilityWeighted: reliabilityWeighting
    };
  }

  /**
   * Aggregate quantitative evidence using OWA operators
   * @private
   */
  static _aggregateQuantitative(values, metas, options) {
    const {
      aggregator,
      weights,
      reliabilityWeighting,
      customWeights
    } = options;

    // Generate OWA weights
    let owaWeights;
    if (customWeights) {
      owaWeights = customWeights;
    } else {
      owaWeights = this._generateQuantitativeOWAWeights(aggregator, values.length, metas);
    }

    // Apply reliability weighting if requested
    let weightedValues = values;
    if (reliabilityWeighting && metas.length > 0) {
      weightedValues = values.map((value, index) => {
        const meta = metas[index] || {};
        const reliability = meta.reliability || 1.0;
        return value * reliability;
      });
    }

    // Perform quantitative OWA fusion
    const result = OWAFusion.fuseWithMeta(
      weightedValues,
      metas,
      owaWeights,
      aggregator
    );

    return {
      value: result.value,
      possibility: result.value, // In quantitative mode, we use the aggregated value
      hasValue: true,
      aggregationMethod: aggregator,
      mode: 'quantitative',
      weights: owaWeights,
      reliabilityWeighted: reliabilityWeighting
    };
  }

  /**
   * Generate OWA weights for quantitative aggregation
   * @private
   */
  static _generateQuantitativeOWAWeights(aggregator, count, metas = []) {
    const weights = new Array(count).fill(0);

    switch (aggregator.toLowerCase()) {
      case 'max':
        weights[0] = 1.0; // First (highest) value gets full weight
        break;

      case 'min':
        weights[count - 1] = 1.0; // Last (lowest) value gets full weight
        break;

      case 'average':
      case 'avg':
      case 'mean':
        // Equal weights for all values
        const equalWeight = 1.0 / count;
        weights.fill(equalWeight);
        break;

      case 'sum':
        // All values get full weight (additive)
        weights.fill(1.0);
        break;

      case 'majority':
        // Focus on top 60% of values
        const majorityCount = Math.ceil(count * 0.6);
        const majorityWeight = 1.0 / majorityCount;
        for (let i = 0; i < majorityCount; i++) {
          weights[i] = majorityWeight;
        }
        break;

      case 'median':
        // Middle value(s) get full weight
        if (count % 2 === 1) {
          weights[Math.floor(count / 2)] = 1.0;
        } else {
          const mid1 = count / 2 - 1;
          const mid2 = count / 2;
          weights[mid1] = 0.5;
          weights[mid2] = 0.5;
        }
        break;

      case 'optimistic':
        // Exponential decay favoring higher values
        for (let i = 0; i < count; i++) {
          weights[i] = Math.exp(-i * 0.5);
        }
        // Normalize
        const optimisticSum = weights.reduce((sum, w) => sum + w, 0);
        weights.forEach((w, i) => weights[i] = w / optimisticSum);
        break;

      case 'pessimistic':
        // Exponential decay favoring lower values
        for (let i = 0; i < count; i++) {
          weights[count - 1 - i] = Math.exp(-i * 0.5);
        }
        // Normalize
        const pessimisticSum = weights.reduce((sum, w) => sum + w, 0);
        weights.forEach((w, i) => weights[i] = w / pessimisticSum);
        break;

      case 'top2':
        // Equal weight on top 2 values
        const top2Weight = 0.5;
        weights[0] = top2Weight;
        weights[1] = top2Weight;
        break;

      case 'top3':
        // Equal weight on top 3 values
        const top3Weight = 1.0 / 3;
        weights[0] = top3Weight;
        weights[1] = top3Weight;
        weights[2] = top3Weight;
        break;

      case 'priority':
        // Weight by priority values in metadata
        if (metas.length > 0) {
          const priorities = metas.map(meta => meta.priority || 1);
          const totalPriority = priorities.reduce((sum, p) => sum + p, 0);
          priorities.forEach((priority, i) => {
            weights[i] = priority / totalPriority;
          });
        } else {
          // Fallback to equal weights
          weights.fill(1.0 / count);
        }
        break;

      case 'custom':
        // Custom weights should be provided via customWeights parameter
        weights.fill(1.0 / count); // Fallback to equal weights
        break;

      default:
        // Default to max
        weights[0] = 1.0;
        break;
    }

    return weights;
  }

  /**
   * Get available aggregation methods
   * @returns {Array} List of available aggregation methods
   */
  static getAvailableMethods() {
    return [
      'max', 'min', 'average', 'avg', 'mean', 'sum',
      'majority', 'median', 'optimistic', 'pessimistic',
      'top2', 'top3', 'priority', 'custom'
    ];
  }

  /**
   * Validate aggregation method
   * @param {string} method - Aggregation method to validate
   * @returns {boolean} True if method is valid
   */
  static isValidMethod(method) {
    return this.getAvailableMethods().includes(method);
  }

  /**
   * Get method description
   * @param {string} method - Aggregation method
   * @returns {string} Description of the method
   */
  static getMethodDescription(method) {
    const descriptions = {
      'max': 'Maximum value (optimistic OR)',
      'min': 'Minimum value (pessimistic AND)',
      'average': 'Equal weight average',
      'avg': 'Equal weight average',
      'mean': 'Equal weight average',
      'sum': 'Additive evidence (each contributes full weight)',
      'majority': 'Focus on consensus (top 60% of values)',
      'median': 'Pure median value',
      'optimistic': 'Exponential decay favoring higher values',
      'pessimistic': 'Exponential decay favoring lower values',
      'top2': 'Equal weight on top 2 values',
      'top3': 'Equal weight on top 3 values',
      'priority': 'Weight by rule priority values',
      'custom': 'User-defined OWA weights'
    };

    return descriptions[method] || 'Unknown aggregation method';
  }

  /**
   * Compare aggregation results between qualitative and quantitative modes
   * @param {Array} values - Array of values to aggregate
   * @param {Array} metas - Array of metadata
   * @param {string} aggregator - Aggregation method
   * @param {Object} options - Additional options
   * @returns {Object} Comparison result
   */
  static compareModes(values, metas, aggregator, options = {}) {
    const qualitativeResult = this.aggregate(values, metas, {
      ...options,
      mode: 'qualitative',
      aggregator
    });

    const quantitativeResult = this.aggregate(values, metas, {
      ...options,
      mode: 'quantitative',
      aggregator
    });

    return {
      aggregator,
      qualitative: qualitativeResult,
      quantitative: quantitativeResult,
      comparison: {
        valueDifference: Math.abs(qualitativeResult.value - quantitativeResult.value),
        sameResult: qualitativeResult.value === quantitativeResult.value,
        qualitativeAdvantage: qualitativeResult.value > quantitativeResult.value,
        quantitativeAdvantage: quantitativeResult.value > qualitativeResult.value
      }
    };
  }
}
