/**
 * Failure DNA (§4, §41).
 *
 * Every historical failure is reduced to a 10-dimension normalised signature. The live
 * grid state is reduced the same way, and a weighted distance gives a similarity score.
 * Because both sides use the identical extractor, a match means "the grid currently
 * looks like it did before that failure" in a way an operator can inspect dimension by
 * dimension — not an opaque score.
 */

import { clamp, round } from './rng'

/** Ordered feature keys. The order is part of the DNA contract — do not reorder. */
export const DNA_FEATURES = [
  'voltageDeviation',
  'frequencyDeviation',
  'loadPercentage',
  'temperature',
  'loadGrowthRate',
  'renewableVariability',
  'assetAge',
  'maintenanceScore',
  'neighboringFailures',
  'weatherTemperature',
] as const

export type DnaFeature = (typeof DNA_FEATURES)[number]
export type DnaVector = Record<DnaFeature, number>

/** Human-readable labels for the Explainable-AI panel. */
export const DNA_FEATURE_LABELS: Record<DnaFeature, { en: string; ar: string; unit: string }> = {
  voltageDeviation: { en: 'Voltage deviation', ar: 'انحراف الجهد', unit: '%' },
  frequencyDeviation: { en: 'Frequency deviation', ar: 'انحراف التردد', unit: 'Hz' },
  loadPercentage: { en: 'Loading', ar: 'نسبة التحميل', unit: '%' },
  temperature: { en: 'Asset temperature', ar: 'حرارة الأصل', unit: '°C' },
  loadGrowthRate: { en: 'Load growth rate', ar: 'معدل نمو الحمل', unit: 'pp/h' },
  renewableVariability: { en: 'Renewable variability', ar: 'تذبذب المتجددة', unit: '%' },
  assetAge: { en: 'Asset age', ar: 'عمر الأصل', unit: 'yr' },
  maintenanceScore: { en: 'Maintenance debt', ar: 'تأخر الصيانة', unit: 'idx' },
  neighboringFailures: { en: 'Neighbouring stress', ar: 'إجهاد الأصول المجاورة', unit: 'idx' },
  weatherTemperature: { en: 'Ambient temperature', ar: 'حرارة الجو', unit: '°C' },
}

/**
 * Normalisation ranges. Each raw measurement is mapped into [0, 1] so that distances are
 * comparable across dimensions with wildly different units.
 */
const RANGES: Record<DnaFeature, [number, number]> = {
  voltageDeviation: [0, 8],
  frequencyDeviation: [0, 0.5],
  loadPercentage: [0, 130],
  temperature: [20, 130],
  loadGrowthRate: [-10, 25],
  renewableVariability: [0, 40],
  assetAge: [0, 40],
  maintenanceScore: [0, 1],
  neighboringFailures: [0, 1],
  weatherTemperature: [0, 55],
}

/**
 * Similarity weights. Loading, temperature and growth carry the most signal for the
 * thermal/overload failure families that dominate a hot-climate grid.
 */
const WEIGHTS: Record<DnaFeature, number> = {
  voltageDeviation: 0.9,
  frequencyDeviation: 0.6,
  loadPercentage: 1.6,
  temperature: 1.6,
  loadGrowthRate: 1.3,
  renewableVariability: 0.8,
  assetAge: 0.9,
  maintenanceScore: 1.0,
  neighboringFailures: 0.9,
  weatherTemperature: 1.1,
}

const WEIGHT_SUM = Object.values(WEIGHTS).reduce((a, b) => a + b, 0)

export function normaliseFeature(feature: DnaFeature, raw: number): number {
  const [min, max] = RANGES[feature]
  return clamp((raw - min) / (max - min), 0, 1)
}

export function denormaliseFeature(feature: DnaFeature, value: number): number {
  const [min, max] = RANGES[feature]
  return min + clamp(value, 0, 1) * (max - min)
}

export interface DnaRawInput {
  voltageDeviationPct: number
  frequencyHz: number
  loadPct: number
  tempC: number
  loadGrowthPpPerHour: number
  renewableVariabilityPct: number
  assetAgeYears: number
  /** 0 = maintenance up to date, 1 = badly overdue. */
  maintenanceDebt: number
  /** 0 = neighbours healthy, 1 = neighbours in critical state. */
  neighbourStress: number
  ambientTempC: number
}

/** Reduce a live (or historical) grid state to its normalised DNA signature. */
export function extractDna(input: DnaRawInput): DnaVector {
  return {
    voltageDeviation: normaliseFeature('voltageDeviation', Math.abs(input.voltageDeviationPct)),
    frequencyDeviation: normaliseFeature('frequencyDeviation', Math.abs(input.frequencyHz - 60)),
    loadPercentage: normaliseFeature('loadPercentage', input.loadPct),
    temperature: normaliseFeature('temperature', input.tempC),
    loadGrowthRate: normaliseFeature('loadGrowthRate', input.loadGrowthPpPerHour),
    renewableVariability: normaliseFeature('renewableVariability', input.renewableVariabilityPct),
    assetAge: normaliseFeature('assetAge', input.assetAgeYears),
    maintenanceScore: normaliseFeature('maintenanceScore', input.maintenanceDebt),
    neighboringFailures: normaliseFeature('neighboringFailures', input.neighbourStress),
    weatherTemperature: normaliseFeature('weatherTemperature', input.ambientTempC),
  }
}

/**
 * Weighted-Euclidean similarity in [0, 100].
 *
 * Distance is normalised by the weight sum so a perfect match is 100 and two vectors at
 * opposite corners of the unit hypercube score 0. Euclidean rather than cosine because
 * magnitude matters here: a lightly loaded transformer is genuinely unlike a heavily
 * loaded one even when the "shape" of the two signatures agrees.
 */
export function dnaSimilarity(a: DnaVector, b: DnaVector): number {
  let sum = 0
  for (const feature of DNA_FEATURES) {
    const diff = a[feature] - b[feature]
    sum += WEIGHTS[feature] * diff * diff
  }
  const distance = Math.sqrt(sum / WEIGHT_SUM)
  return round(clamp(1 - distance, 0, 1) * 100, 1)
}

export interface DnaMatch<T = unknown> {
  similarity: number
  pattern: T
  /** Per-dimension agreement, so the UI can show *why* the signatures match. */
  contributions: Array<{
    feature: DnaFeature
    current: number
    historical: number
    agreement: number
  }>
}

/** Top-N historical patterns most similar to the current state (§41 asks for top 3). */
export function matchDna<T extends { dna: DnaVector }>(
  current: DnaVector,
  patterns: T[],
  limit = 3,
): DnaMatch<T>[] {
  return patterns
    .map((pattern) => ({
      similarity: dnaSimilarity(current, pattern.dna),
      pattern,
      contributions: DNA_FEATURES.map((feature) => ({
        feature,
        current: round(current[feature], 4),
        historical: round(pattern.dna[feature], 4),
        agreement: round(clamp(1 - Math.abs(current[feature] - pattern.dna[feature]), 0, 1) * 100, 1),
      })),
    }))
    .sort((a, b) => b.similarity - a.similarity)
    .slice(0, limit)
}

/** Convenience: build a DnaVector from a flat database row. */
export function dnaFromRow(row: Record<DnaFeature, number>): DnaVector {
  return DNA_FEATURES.reduce((acc, feature) => {
    acc[feature] = row[feature]
    return acc
  }, {} as DnaVector)
}
