/**
 * PossibilisticConverter - Converts between possibilistic values and qualitative scales
 * 
 * This module provides bidirectional conversion between:
 * - Numeric possibility values [0,1] ↔ Qualitative scale values
 * - Linguistic expressions ↔ Qualitative scale values
 * - Possibilistic intervals ↔ Qualitative intervals
 * 
 * The converter supports both "upgrading" (qualitative → possibilistic) and "downgrading" 
 * (possibilistic → qualitative) operations, with downgrading as the default for fusion.
 */

import { QualitativeScale } from './QualitativeScale.js';

// Linguistic mapping for converting natural language to possibilistic values
export const LINGUISTIC_MAPPING = {
  'Almost Certain':      { median: 0.98, q1: 0.95, q3: 0.99 },
  'Highly Likely':       { median: 0.90, q1: 0.85, q3: 0.95 },
  'Very Good Chance':    { median: 0.85, q1: 0.78, q3: 0.92 },
  'Believable':          { median: 0.75, q1: 0.65, q3: 0.85 },
  'Likely':              { median: 0.72, q1: 0.65, q3: 0.80 },
  'Probable':            { median: 0.70, q1: 0.60, q3: 0.80 },
  'Probably':            { median: 0.68, q1: 0.55, q3: 0.80 },
  'Even':                { median: 0.60, q1: 0.52, q3: 0.68 },
  'About Even':          { median: 0.50, q1: 0.48, q3: 0.52 },
  'Slightly Against':    { median: 0.40, q1: 0.32, q3: 0.48 },
  'Probably Not':        { median: 0.32, q1: 0.20, q3: 0.45 },
  'Doubtful':            { median: 0.25, q1: 0.15, q3: 0.35 },
  'Unlikely':            { median: 0.18, q1: 0.10, q3: 0.25 },
  'Improbable':          { median: 0.10, q1: 0.05, q3: 0.20 },
  'Slight':              { median: 0.10, q1: 0.05, q3: 0.18 },
  'Little Chance':       { median: 0.05, q1: 0.02, q3: 0.10 },
  'Certainly Not':       { median: 0.02, q1: 0.01, q3: 0.05 }
};

export class PossibilisticConverter {
  /**
   * Convert a possibilistic value to a qualitative scale value
   * @param {number} possibility - Possibility value in [0,1]
   * @param {QualitativeScale} scale - Target qualitative scale
   * @param {string} strategy - Conversion strategy: 'closest', 'floor', 'ceiling', 'interpolate'
   * @returns {number} Qualitative scale value
   */
  static downgradePossibility(possibility, scale, strategy = 'closest') {
    if (!scale.contains(possibility)) {
      // Find the closest value on the scale
      return this._findClosestValue(possibility, scale, strategy);
    }
    return possibility;
  }

  /**
   * Convert a qualitative scale value to a possibilistic value
   * @param {number} qualitativeValue - Value from qualitative scale
   * @param {QualitativeScale} scale - Source qualitative scale
   * @param {string} strategy - Conversion strategy: 'direct', 'interpolate', 'linguistic'
   * @returns {number} Possibility value in [0,1]
   */
  static upgradePossibility(qualitativeValue, scale, strategy = 'direct') {
    if (scale.contains(qualitativeValue)) {
      return qualitativeValue; // Already a valid possibility value
    }
    
    switch (strategy) {
      case 'interpolate':
        return this._interpolateValue(qualitativeValue, scale);
      case 'linguistic':
        return this._linguisticToPossibility(qualitativeValue);
      default:
        return qualitativeValue;
    }
  }

  /**
   * Convert a linguistic expression to a qualitative scale value
   * @param {string} linguistic - Linguistic expression (e.g., "Highly Likely")
   * @param {QualitativeScale} scale - Target qualitative scale
   * @param {string} strategy - Conversion strategy: 'median', 'q1', 'q3', 'closest'
   * @returns {number} Qualitative scale value
   */
  static linguisticToQualitative(linguistic, scale, strategy = 'median') {
    const mapping = LINGUISTIC_MAPPING[linguistic];
    if (!mapping) {
      throw new Error(`Unknown linguistic expression: ${linguistic}`);
    }

    let possibility;
    switch (strategy) {
      case 'q1':
        possibility = mapping.q1;
        break;
      case 'q3':
        possibility = mapping.q3;
        break;
      case 'closest':
        // Find the closest value on the scale to the median
        possibility = this._findClosestValue(mapping.median, scale, 'closest');
        break;
      default:
        possibility = mapping.median;
    }

    return this.downgradePossibility(possibility, scale, 'closest');
  }

  /**
   * Convert a qualitative scale value to a linguistic expression
   * @param {number} qualitativeValue - Value from qualitative scale
   * @param {QualitativeScale} scale - Source qualitative scale
   * @returns {string} Linguistic expression
   */
  static qualitativeToLinguistic(qualitativeValue, scale) {
    const possibility = this.upgradePossibility(qualitativeValue, scale, 'direct');
    
    // Find the closest linguistic mapping
    let closestLinguistic = 'About Even';
    let minDistance = Infinity;
    
    for (const [linguistic, mapping] of Object.entries(LINGUISTIC_MAPPING)) {
      const distance = Math.abs(possibility - mapping.median);
      if (distance < minDistance) {
        minDistance = distance;
        closestLinguistic = linguistic;
      }
    }
    
    return closestLinguistic;
  }

  /**
   * Convert a possibilistic interval to a qualitative interval
   * @param {Object} interval - Possibilistic interval {min: number, max: number}
   * @param {QualitativeScale} scale - Target qualitative scale
   * @param {string} strategy - Conversion strategy: 'closest', 'floor', 'ceiling'
   * @returns {Object} Qualitative interval {lower: number, upper: number}
   */
  static downgradeInterval(interval, scale, strategy = 'closest') {
    const lower = this.downgradePossibility(interval.min, scale, strategy);
    const upper = this.downgradePossibility(interval.max, scale, strategy);
    
    // Ensure lower <= upper on the qualitative scale
    const orderedLower = scale.min(lower, upper);
    const orderedUpper = scale.max(lower, upper);
    
    return { lower: orderedLower, upper: orderedUpper };
  }

  /**
   * Convert a qualitative interval to a possibilistic interval
   * @param {Object} interval - Qualitative interval {lower: number, upper: number}
   * @param {QualitativeScale} scale - Source qualitative scale
   * @param {string} strategy - Conversion strategy: 'direct', 'interpolate'
   * @returns {Object} Possibilistic interval {min: number, max: number}
   */
  static upgradeInterval(interval, scale, strategy = 'direct') {
    const min = this.upgradePossibility(interval.lower, scale, strategy);
    const max = this.upgradePossibility(interval.upper, scale, strategy);
    
    // Ensure min <= max for possibilistic intervals
    const orderedMin = Math.min(min, max);
    const orderedMax = Math.max(min, max);
    
    return { min: orderedMin, max: orderedMax };
  }

  /**
   * Convert an array of possibilistic values to qualitative scale values
   * @param {number[]} possibilities - Array of possibility values
   * @param {QualitativeScale} scale - Target qualitative scale
   * @param {string} strategy - Conversion strategy
   * @returns {number[]} Array of qualitative scale values
   */
  static downgradePossibilities(possibilities, scale, strategy = 'closest') {
    return possibilities.map(p => this.downgradePossibility(p, scale, strategy));
  }

  /**
   * Convert an array of qualitative scale values to possibilistic values
   * @param {number[]} qualitativeValues - Array of qualitative scale values
   * @param {QualitativeScale} scale - Source qualitative scale
   * @param {string} strategy - Conversion strategy
   * @returns {number[]} Array of possibility values
   */
  static upgradePossibilities(qualitativeValues, scale, strategy = 'direct') {
    return qualitativeValues.map(v => this.upgradePossibility(v, scale, strategy));
  }

  /**
   * Create a hybrid fusion result that can work with both possibilistic and qualitative values
   * @param {Array} values - Array of values (mixed possibilistic and qualitative)
   * @param {QualitativeScale} targetScale - Target scale for conversion
   * @param {string} strategy - Conversion strategy
   * @returns {Object} Fusion result with both possibilistic and qualitative representations
   */
  static createHybridFusion(values, targetScale, strategy = 'downgrade') {
    const convertedValues = values.map(value => {
      if (typeof value === 'number') {
        if (targetScale.contains(value)) {
          return { possibilistic: value, qualitative: value };
        } else {
          if (strategy === 'downgrade') {
            const qualitative = this.downgradePossibility(value, targetScale, 'closest');
            return { possibilistic: value, qualitative };
          } else {
            const possibilistic = this.upgradePossibility(value, targetScale, 'direct');
            return { possibilistic, qualitative: value };
          }
        }
      }
      return { possibilistic: value, qualitative: value };
    });

    return {
      possibilisticValues: convertedValues.map(v => v.possibilistic),
      qualitativeValues: convertedValues.map(v => v.qualitative),
      scale: targetScale,
      strategy
    };
  }

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

  /**
   * Find the closest value on a scale to a given possibility
   * @private
   */
  static _findClosestValue(possibility, scale, strategy) {
    const values = scale.values;
    let closest = values[0];
    let minDistance = Math.abs(possibility - closest);

    for (const value of values) {
      const distance = Math.abs(possibility - value);
      if (distance < minDistance) {
        minDistance = distance;
        closest = value;
      }
    }

    switch (strategy) {
      case 'floor':
        // Find the largest value <= possibility
        return values.filter(v => v <= possibility).pop() || values[0];
      case 'ceiling':
        // Find the smallest value >= possibility
        return values.find(v => v >= possibility) || values[values.length - 1];
      default:
        return closest;
    }
  }

  /**
   * Interpolate a value based on its position in the scale
   * @private
   */
  static _interpolateValue(value, scale) {
    const values = scale.values;
    const index = scale.indexOf(value);
    
    if (index >= 0) {
      return value; // Already on the scale
    }

    // Find the two closest values for interpolation
    let lower = values[0];
    let upper = values[values.length - 1];
    
    for (let i = 0; i < values.length - 1; i++) {
      if (value >= values[i] && value <= values[i + 1]) {
        lower = values[i];
        upper = values[i + 1];
        break;
      }
    }

    // Linear interpolation
    const ratio = (value - lower) / (upper - lower);
    return lower + ratio * (upper - lower);
  }

  /**
   * Convert a linguistic expression to a possibility value
   * @private
   */
  static _linguisticToPossibility(linguistic) {
    const mapping = LINGUISTIC_MAPPING[linguistic];
    if (!mapping) {
      throw new Error(`Unknown linguistic expression: ${linguistic}`);
    }
    return mapping.median;
  }

  /**
   * Get all available linguistic expressions
   * @returns {string[]} Array of linguistic expressions
   */
  static getLinguisticExpressions() {
    return Object.keys(LINGUISTIC_MAPPING);
  }

  /**
   * Get linguistic mapping for a specific expression
   * @param {string} linguistic - Linguistic expression
   * @returns {Object|null} Mapping object or null if not found
   */
  static getLinguisticMapping(linguistic) {
    return LINGUISTIC_MAPPING[linguistic] || null;
  }

  /**
   * Validate that a possibility value is in the valid range
   * @param {number} possibility - Possibility value to validate
   * @returns {boolean} True if valid
   */
  static isValidPossibility(possibility) {
    return typeof possibility === 'number' && possibility >= 0 && possibility <= 1;
  }

  /**
   * Validate that a qualitative value is on the given scale
   * @param {number} value - Qualitative value to validate
   * @param {QualitativeScale} scale - Scale to validate against
   * @returns {boolean} True if valid
   */
  static isValidQualitative(value, scale) {
    return scale.contains(value);
  }

  /**
   * Create a qualitative interval from possibilistic bounds
   * @param {number} lowerBound - Lower possibility bound
   * @param {number} upperBound - Upper possibility bound
   * @param {QualitativeScale} scale - Target qualitative scale
   * @param {string} strategy - Conversion strategy
   * @returns {Object} Qualitative interval {lower: number, upper: number}
   */
  static createQualitativeInterval(lowerBound, upperBound, scale, strategy = 'closest') {
    const lower = this.downgradePossibility(lowerBound, scale, strategy);
    const upper = this.downgradePossibility(upperBound, scale, strategy);
    
    return { lower, upper };
  }

  /**
   * Create a possibilistic interval from qualitative bounds
   * @param {number} lowerBound - Lower qualitative bound
   * @param {number} upperBound - Upper qualitative bound
   * @param {QualitativeScale} scale - Source qualitative scale
   * @param {string} strategy - Conversion strategy
   * @returns {Object} Possibilistic interval {min: number, max: number}
   */
  static createPossibilisticInterval(lowerBound, upperBound, scale, strategy = 'direct') {
    const min = this.upgradePossibility(lowerBound, scale, strategy);
    const max = this.upgradePossibility(upperBound, scale, strategy);
    
    return { min, max };
  }

  /**
   * Expand a qualitative interval by adding uncertainty
   * @param {Object} interval - Qualitative interval {lower: number, upper: number}
   * @param {QualitativeScale} scale - Qualitative scale
   * @param {number} expansionSteps - Number of steps to expand on each side
   * @returns {Object} Expanded qualitative interval
   */
  static expandQualitativeInterval(interval, scale, expansionSteps = 1) {
    const { lower, upper } = interval;
    
    const lowerIndex = scale.indexOf(lower);
    const upperIndex = scale.indexOf(upper);
    
    const expandedLowerIndex = Math.max(0, lowerIndex - expansionSteps);
    const expandedUpperIndex = Math.min(scale.size - 1, upperIndex + expansionSteps);
    
    return {
      lower: scale.at(expandedLowerIndex),
      upper: scale.at(expandedUpperIndex)
    };
  }

  /**
   * Contract a qualitative interval by reducing uncertainty
   * @param {Object} interval - Qualitative interval {lower: number, upper: number}
   * @param {QualitativeScale} scale - Qualitative scale
   * @param {number} contractionSteps - Number of steps to contract on each side
   * @returns {Object} Contracted qualitative interval
   */
  static contractQualitativeInterval(interval, scale, contractionSteps = 1) {
    const { lower, upper } = interval;
    
    const lowerIndex = scale.indexOf(lower);
    const upperIndex = scale.indexOf(upper);
    
    const contractedLowerIndex = Math.min(scale.size - 1, lowerIndex + contractionSteps);
    const contractedUpperIndex = Math.max(0, upperIndex - contractionSteps);
    
    // Ensure contracted interval is valid (lower <= upper)
    const finalLowerIndex = Math.min(contractedLowerIndex, contractedUpperIndex);
    const finalUpperIndex = Math.max(contractedLowerIndex, contractedUpperIndex);
    
    return {
      lower: scale.at(finalLowerIndex),
      upper: scale.at(finalUpperIndex)
    };
  }

  /**
   * Get the width of a qualitative interval in scale steps
   * @param {Object} interval - Qualitative interval {lower: number, upper: number}
   * @param {QualitativeScale} scale - Qualitative scale
   * @returns {number} Width in scale steps
   */
  static getQualitativeIntervalWidth(interval, scale) {
    const { lower, upper } = interval;
    const lowerIndex = scale.indexOf(lower);
    const upperIndex = scale.indexOf(upper);
    
    return upperIndex - lowerIndex;
  }

  /**
   * Check if a qualitative interval contains a value
   * @param {Object} interval - Qualitative interval {lower: number, upper: number}
   * @param {number} value - Value to check
   * @param {QualitativeScale} scale - Qualitative scale
   * @returns {boolean} True if interval contains the value
   */
  static qualitativeIntervalContains(interval, value, scale) {
    const { lower, upper } = interval;
    return scale.compare(value, lower) >= 0 && scale.compare(value, upper) <= 0;
  }

  /**
   * Get the center of a qualitative interval
   * @param {Object} interval - Qualitative interval {lower: number, upper: number}
   * @param {QualitativeScale} scale - Qualitative scale
   * @returns {number} Center value of the interval
   */
  static getQualitativeIntervalCenter(interval, scale) {
    const { lower, upper } = interval;
    const lowerIndex = scale.indexOf(lower);
    const upperIndex = scale.indexOf(upper);
    const centerIndex = Math.floor((lowerIndex + upperIndex) / 2);
    
    return scale.at(centerIndex);
  }
}
