/**
 * HybridFusion - Fusion system that handles mixed possibilistic and qualitative values
 * 
 * This module provides fusion capabilities that can seamlessly work with:
 * - Pure possibilistic values [0,1]
 * - Pure qualitative scale values
 * - Mixed arrays of both types
 * - Linguistic expressions
 * 
 * The system automatically converts between representations as needed for fusion operations.
 */

import { OWAFusion } from '../utils/OWAFusion.js';
import { OWAQualitativeFusion } from './OWAQualitativeFusion.js';
import { QualitativeFusion } from './QualitativeFusion.js';
import { PossibilisticConverter, LINGUISTIC_MAPPING } from './PossibilisticConverter.js';
import { QualitativeScale } from './QualitativeScale.js';

export class HybridFusion {
  /**
   * Fuse mixed possibilistic and qualitative values
   * @param {Array} values - Array of values (mixed types)
   * @param {Object} options - Fusion options
   * @param {QualitativeScale} options.scale - Target qualitative scale (default: five-point)
   * @param {string} options.strategy - Conversion strategy: 'downgrade', 'upgrade', 'hybrid'
   * @param {string} options.method - Fusion method: 'max', 'min', 'average', 'majority', etc.
   * @param {Array} options.weights - Custom OWA weights
   * @param {boolean} options.preserveType - Whether to preserve original value types in result
   * @returns {Object} Fusion result with both possibilistic and qualitative representations
   */
  static fuse(values, options = {}) {
    const {
      scale = QualitativeScale.fivePoint(),
      strategy = 'downgrade',
      method = 'max',
      weights = null,
      preserveType = false
    } = options;

    if (!values || values.length === 0) {
      return this._createEmptyResult(scale);
    }

    // Analyze input types
    const analysis = this._analyzeValues(values);
    
    // Convert all values to a common representation
    const converted = this._convertValues(values, scale, strategy, analysis);
    
    // Perform fusion based on the common representation
    const result = this._performFusion(converted, method, weights, scale);
    
    // Create hybrid result
    return this._createHybridResult(result, values, scale, strategy, preserveType);
  }

  /**
   * Fuse possibilistic values using qualitative fusion methods
   * @param {number[]} possibilities - Array of possibility values
   * @param {Object} options - Fusion options
   * @returns {Object} Fusion result
   */
  static fusePossibilities(possibilities, options = {}) {
    const {
      scale = QualitativeScale.fivePoint(),
      method = 'max',
      weights = null
    } = options;

    // Convert to qualitative values
    const qualitativeValues = PossibilisticConverter.downgradePossibilities(
      possibilities, scale, 'closest'
    );

    // Use qualitative fusion
    const result = OWAQualitativeFusion.fuseWithMeta(
      qualitativeValues, 
      this._createDefaultMetas(qualitativeValues),
      weights,
      method,
      scale
    );

    return {
      possibilistic: result.value,
      qualitative: result.value,
      scale: scale,
      method: method,
      meta: result.meta,
      originalValues: possibilities,
      convertedValues: qualitativeValues
    };
  }

  /**
   * Fuse qualitative values using possibilistic fusion methods
   * @param {number[]} qualitativeValues - Array of qualitative scale values
   * @param {QualitativeScale} scale - Source qualitative scale
   * @param {Object} options - Fusion options
   * @returns {Object} Fusion result
   */
  static fuseQualitative(qualitativeValues, scale, options = {}) {
    const {
      method = 'max',
      weights = null
    } = options;

    // Convert to possibilistic values
    const possibilities = PossibilisticConverter.upgradePossibilities(
      qualitativeValues, scale, 'direct'
    );

    // Use possibilistic fusion
    const result = OWAFusion.fuseWithMeta(
      possibilities,
      this._createDefaultMetas(possibilities),
      weights,
      method
    );

    return {
      possibilistic: result.value,
      qualitative: result.value,
      scale: scale,
      method: method,
      meta: result.meta,
      originalValues: qualitativeValues,
      convertedValues: possibilities
    };
  }

  /**
   * Fuse linguistic expressions
   * @param {string[]} linguistics - Array of linguistic expressions
   * @param {Object} options - Fusion options
   * @returns {Object} Fusion result
   */
  static fuseLinguistic(linguistics, options = {}) {
    const {
      scale = QualitativeScale.fivePoint(),
      method = 'majority',
      weights = null,
      linguisticStrategy = 'median'
    } = options;

    // Convert linguistic expressions to qualitative values
    const qualitativeValues = linguistics.map(ling => 
      PossibilisticConverter.linguisticToQualitative(ling, scale, linguisticStrategy)
    );

    // Use qualitative fusion
    const result = OWAQualitativeFusion.fuseWithMeta(
      qualitativeValues,
      this._createLinguisticMetas(linguistics),
      weights,
      method,
      scale
    );

    // Convert back to linguistic
    const resultLinguistic = PossibilisticConverter.qualitativeToLinguistic(
      result.value, scale
    );

    return {
      possibilistic: result.value,
      qualitative: result.value,
      linguistic: resultLinguistic,
      scale: scale,
      method: method,
      meta: result.meta,
      originalValues: linguistics,
      convertedValues: qualitativeValues
    };
  }

  /**
   * Fuse intervals (possibilistic or qualitative)
   * @param {Array} intervals - Array of intervals
   * @param {Object} options - Fusion options
   * @returns {Object} Fusion result
   */
  static fuseIntervals(intervals, options = {}) {
    const {
      scale = QualitativeScale.fivePoint(),
      strategy = 'downgrade',
      method = 'union'
    } = options;

    // Analyze interval types
    const analysis = this._analyzeIntervals(intervals);
    
    // Convert to common representation
    const converted = this._convertIntervals(intervals, scale, strategy, analysis);
    
    // Perform interval fusion
    const result = this._performIntervalFusion(converted, method, scale);
    
    return {
      possibilistic: result.possibilistic,
      qualitative: result.qualitative,
      scale: scale,
      method: method,
      originalIntervals: intervals,
      convertedIntervals: converted
    };
  }

  /**
   * Create a capacity from mixed possibilistic and qualitative values
   * @param {Array} values - Array of values (mixed types)
   * @param {Object} options - Options
   * @returns {Object} Capacity representation
   */
  static createCapacity(values, options = {}) {
    const {
      scale = QualitativeScale.fivePoint(),
      strategy = 'downgrade',
      method = 'max'
    } = options;

    // Convert to qualitative values
    const converted = this._convertValues(values, scale, strategy, this._analyzeValues(values));
    
    // Create simple support capacity
    const capacity = QualitativeFusion.createSimpleSupport(
      converted.qualitativeValues,
      scale
    );

    return {
      capacity: capacity,
      scale: scale,
      originalValues: values,
      convertedValues: converted.qualitativeValues,
      method: method
    };
  }

  // ========== PRIVATE HELPER METHODS ==========

  /**
   * Analyze the types of values in the input array
   * @private
   */
  static _analyzeValues(values) {
    const analysis = {
      hasPossibilistic: false,
      hasQualitative: false,
      hasLinguistic: false,
      hasIntervals: false,
      types: new Set()
    };

    for (const value of values) {
      if (typeof value === 'number') {
        if (value >= 0 && value <= 1) {
          analysis.hasPossibilistic = true;
          analysis.types.add('possibilistic');
        } else {
          analysis.hasQualitative = true;
          analysis.types.add('qualitative');
        }
      } else if (typeof value === 'string') {
        if (LINGUISTIC_MAPPING[value]) {
          analysis.hasLinguistic = true;
          analysis.types.add('linguistic');
        }
      } else if (typeof value === 'object' && (value.min !== undefined || value.lower !== undefined)) {
        analysis.hasIntervals = true;
        analysis.types.add('interval');
      }
    }

    return analysis;
  }

  /**
   * Analyze the types of intervals in the input array
   * @private
   */
  static _analyzeIntervals(intervals) {
    const analysis = {
      hasPossibilistic: false,
      hasQualitative: false
    };

    for (const interval of intervals) {
      if (interval.min !== undefined && interval.max !== undefined) {
        analysis.hasPossibilistic = true;
      } else if (interval.lower !== undefined && interval.upper !== undefined) {
        analysis.hasQualitative = true;
      }
    }

    return analysis;
  }

  /**
   * Convert values to a common representation
   * @private
   */
  static _convertValues(values, scale, strategy, analysis) {
    const possibilisticValues = [];
    const qualitativeValues = [];

    for (const value of values) {
      if (typeof value === 'number') {
        if (value >= 0 && value <= 1) {
          // Possibilistic value
          possibilisticValues.push(value);
          if (strategy === 'downgrade') {
            qualitativeValues.push(PossibilisticConverter.downgradePossibility(value, scale, 'closest'));
          } else {
            qualitativeValues.push(value);
          }
        } else {
          // Qualitative value
          qualitativeValues.push(value);
          if (strategy === 'upgrade') {
            possibilisticValues.push(PossibilisticConverter.upgradePossibility(value, scale, 'direct'));
          } else {
            possibilisticValues.push(value);
          }
        }
      } else if (typeof value === 'string' && LINGUISTIC_MAPPING[value]) {
        // Linguistic expression
        const possibilistic = LINGUISTIC_MAPPING[value].median;
        const qualitative = PossibilisticConverter.linguisticToQualitative(value, scale, 'median');
        possibilisticValues.push(possibilistic);
        qualitativeValues.push(qualitative);
      }
    }

    return { possibilisticValues, qualitativeValues };
  }

  /**
   * Convert intervals to a common representation
   * @private
   */
  static _convertIntervals(intervals, scale, strategy, analysis) {
    const possibilisticIntervals = [];
    const qualitativeIntervals = [];

    for (const interval of intervals) {
      if (interval.min !== undefined && interval.max !== undefined) {
        // Possibilistic interval
        possibilisticIntervals.push(interval);
        if (strategy === 'downgrade') {
          qualitativeIntervals.push(PossibilisticConverter.downgradeInterval(interval, scale, 'closest'));
        } else {
          qualitativeIntervals.push(interval);
        }
      } else if (interval.lower !== undefined && interval.upper !== undefined) {
        // Qualitative interval
        qualitativeIntervals.push(interval);
        if (strategy === 'upgrade') {
          possibilisticIntervals.push(PossibilisticConverter.upgradeInterval(interval, scale, 'direct'));
        } else {
          possibilisticIntervals.push(interval);
        }
      }
    }

    return { possibilisticIntervals, qualitativeIntervals };
  }

  /**
   * Perform fusion on converted values
   * @private
   */
  static _performFusion(converted, method, weights, scale) {
    const { possibilisticValues, qualitativeValues } = converted;

    // Use qualitative fusion as the primary method
    const qualitativeResult = OWAQualitativeFusion.fuseWithMeta(
      qualitativeValues,
      this._createDefaultMetas(qualitativeValues),
      weights,
      method,
      scale
    );

    // Use possibilistic fusion for comparison
    const possibilisticResult = OWAFusion.fuseWithMeta(
      possibilisticValues,
      this._createDefaultMetas(possibilisticValues),
      weights,
      method
    );

    return {
      qualitative: qualitativeResult,
      possibilistic: possibilisticResult
    };
  }

  /**
   * Perform interval fusion
   * @private
   */
  static _performIntervalFusion(converted, method, scale) {
    const { possibilisticIntervals, qualitativeIntervals } = converted;

    // Use OWAFusion's interval fusion for possibilistic intervals
    const possibilisticResult = OWAFusion.fuseIntervalsWithMeta(
      possibilisticIntervals,
      this._createDefaultMetas(possibilisticIntervals),
      null,
      method
    );

    // For qualitative intervals, perform fusion directly on qualitative scale
    const qualitativeResult = this._fuseQualitativeIntervals(qualitativeIntervals, method, scale);

    return {
      possibilistic: possibilisticResult.interval,
      qualitative: qualitativeResult
    };
  }

  /**
   * Fuse qualitative intervals using qualitative scale operations
   * @private
   */
  static _fuseQualitativeIntervals(intervals, method, scale) {
    if (intervals.length === 0) {
      return { lower: scale.bottom, upper: scale.bottom };
    }

    if (intervals.length === 1) {
      return intervals[0];
    }

    switch (method) {
      case 'union':
        // Union: [min of lowers, max of uppers]
        const unionLower = scale.minAll(intervals.map(i => i.lower));
        const unionUpper = scale.maxAll(intervals.map(i => i.upper));
        return { lower: unionLower, upper: unionUpper };

      case 'intersection':
        // Intersection: [max of lowers, min of uppers]
        const intersectionLower = scale.maxAll(intervals.map(i => i.lower));
        const intersectionUpper = scale.minAll(intervals.map(i => i.upper));
        
        // Ensure valid interval (lower <= upper)
        if (scale.compare(intersectionLower, intersectionUpper) > 0) {
          // No intersection, return empty interval
          return { lower: scale.bottom, upper: scale.bottom };
        }
        return { lower: intersectionLower, upper: intersectionUpper };

      case 'max':
        // Max: take the interval with highest upper bound
        const maxInterval = intervals.reduce((max, current) => 
          scale.compare(current.upper, max.upper) > 0 ? current : max
        );
        return maxInterval;

      case 'min':
        // Min: take the interval with lowest lower bound
        const minInterval = intervals.reduce((min, current) => 
          scale.compare(current.lower, min.lower) < 0 ? current : min
        );
        return minInterval;

      case 'average':
      case 'avg':
      case 'mean':
        // Average: average the lower and upper bounds separately
        const avgLower = this._averageQualitativeValues(intervals.map(i => i.lower), scale);
        const avgUpper = this._averageQualitativeValues(intervals.map(i => i.upper), scale);
        return { lower: avgLower, upper: avgUpper };

      case 'sum':
        // Sum: in qualitative bag algebra, sum means "all intervals contribute"
        // This is equivalent to taking the maximum interval (strongest evidence dominates)
        const maxSumInterval = intervals.reduce((max, current) => 
          scale.compare(current.upper, max.upper) > 0 ? current : max
        );
        return maxSumInterval;

      case 'majority':
        // Majority: take the interval that appears most frequently or has the most overlap
        if (intervals.length === 1) return intervals[0];
        // For simplicity, return the interval with the highest upper bound
        return intervals.reduce((max, current) => 
          scale.compare(current.upper, max.upper) > 0 ? current : max
        );

      case 'median':
        // Median: take the middle interval when sorted by lower bound
        const sortedIntervals = [...intervals].sort((a, b) => 
          scale.compare(a.lower, b.lower)
        );
        const midIndex = Math.floor(sortedIntervals.length / 2);
        return sortedIntervals[midIndex];

      case 'optimistic':
        // Optimistic: take the interval with the highest upper bound
        return intervals.reduce((max, current) => 
          scale.compare(current.upper, max.upper) > 0 ? current : max
        );

      case 'pessimistic':
        // Pessimistic: take the interval with the lowest lower bound
        return intervals.reduce((min, current) => 
          scale.compare(current.lower, min.lower) < 0 ? current : min
        );

      case 'top2':
        // Top2: take the two intervals with highest upper bounds and union them
        const top2Intervals = [...intervals]
          .sort((a, b) => scale.compare(b.upper, a.upper))
          .slice(0, 2);
        return this._fuseQualitativeIntervals(top2Intervals, 'union', scale);

      case 'top3':
        // Top3: take the three intervals with highest upper bounds and union them
        const top3Intervals = [...intervals]
          .sort((a, b) => scale.compare(b.upper, a.upper))
          .slice(0, 3);
        return this._fuseQualitativeIntervals(top3Intervals, 'union', scale);

      default:
        // Default to union
        return this._fuseQualitativeIntervals(intervals, 'union', scale);
    }
  }

  /**
   * Average qualitative values using scale operations
   * @private
   */
  static _averageQualitativeValues(values, scale) {
    if (values.length === 0) return scale.bottom;
    if (values.length === 1) return values[0];

    // Find the median value on the scale
    const sortedValues = [...values].sort((a, b) => scale.compare(a, b));
    const midIndex = Math.floor(sortedValues.length / 2);
    
    if (sortedValues.length % 2 === 0) {
      // Even number of values, return the lower of the two middle values
      return sortedValues[midIndex - 1];
    } else {
      // Odd number of values, return the middle value
      return sortedValues[midIndex];
    }
  }

  /**
   * Create hybrid result object
   * @private
   */
  static _createHybridResult(result, originalValues, scale, strategy, preserveType) {
    return {
      possibilistic: result.qualitative.value,
      qualitative: result.qualitative.value,
      scale: scale,
      strategy: strategy,
      method: result.qualitative.meta?.method || 'max',
      meta: {
        qualitative: result.qualitative.meta,
        possibilistic: result.possibilistic.meta
      },
      originalValues: originalValues,
      convertedValues: {
        possibilistic: result.possibilistic.meta?.allValues || [],
        qualitative: result.qualitative.meta?.allValues || []
      },
      preserveType: preserveType
    };
  }

  /**
   * Create empty result
   * @private
   */
  static _createEmptyResult(scale) {
    return {
      possibilistic: 0,
      qualitative: scale.bottom,
      scale: scale,
      meta: { reason: 'no_values' }
    };
  }

  /**
   * Create default metadata for fusion
   * @private
   */
  static _createDefaultMetas(values) {
    return values.map((value, index) => ({
      index: index,
      value: value,
      timestamp: Date.now()
    }));
  }

  /**
   * Create linguistic metadata for fusion
   * @private
   */
  static _createLinguisticMetas(linguistics) {
    return linguistics.map((linguistic, index) => ({
      index: index,
      linguistic: linguistic,
      timestamp: Date.now()
    }));
  }
}
