import { ColumnMetadata } from "@/store/use-grid-store";

export interface ModelRecommendation {
  recommendedType: 'linear' | 'logistic' | 'polynomial' | 'exponential';
  confidence: number;
  reasons: string[];
  polynomialDegree?: number;
  recommendedInteractions: string[][];
  warnings: string[];
  modelComparison?: {
    linear: { r2: number; aic: number; bic: number };
    polynomial: { r2: number; aic: number; bic: number; degree: number };
    exponential?: { r2: number; aic: number; bic: number };
  };
}

interface VariableAnalysis {
  name: string;
  type: 'numeric' | 'categorical';
  uniqueCount: number;
  missingCount: number;
  isBinary: boolean;
  correlation?: number;
  skewness?: number;
}

/**
 * Analyse les données et recommande le meilleur type de modèle de régression
 * Utilise des tests statistiques robustes (AIC, BIC, F-test)
 */
export function analyzeAndRecommendModel(
  data: any[],
  columns: ColumnMetadata[],
  targetVariable: string | null
): ModelRecommendation | null {
  if (!targetVariable || data.length === 0) return null;

  const targetCol = columns.find(c => c.name === targetVariable);
  if (!targetCol) return null;

  // Analyser la variable cible
  const targetAnalysis = analyzeVariable(data, targetVariable, targetCol.type);

  // Si cible binaire → régression logistique
  if (targetAnalysis.isBinary) {
    return {
      recommendedType: 'logistic',
      confidence: 0.95,
      reasons: [
        "✅ La variable cible est **binaire** (2 valeurs uniques)",
        "✅ La régression logistique est conçue pour les cibles 0/1",
        "✅ Prédit la **probabilité** d'appartenance à une classe"
      ],
      recommendedInteractions: [],
      warnings: targetAnalysis.uniqueCount > 2
        ? ["⚠️ La cible a plus de 2 valeurs uniques mais ressemble à une variable binaire. Vérifiez les données."]
        : []
    };
  }

  // Si cible catégorielle avec peu de valeurs
  if (targetCol.type !== 'numeric' && targetAnalysis.uniqueCount <= 10) {
    return {
      recommendedType: 'logistic',
      confidence: 0.85,
      reasons: [
        `✅ La variable cible est **catégorielle** (${targetAnalysis.uniqueCount} valeurs uniques)`,
        "✅ La régression logistique multinomiale peut gérer plusieurs classes"
      ],
      recommendedInteractions: [],
      warnings: [
        "⚠️ Assurez-vous que les catégories sont ordonnées si l'ordre compte",
        `⚠️ ${targetAnalysis.uniqueCount} classes: assurez-vous d'avoir assez de données par classe`
      ]
    };
  }

  // Pour les cibles numériques, analyser les prédicteurs disponibles
  const numericCols = columns.filter(c => c.type === 'numeric' && c.name !== targetVariable);
  if (numericCols.length === 0) {
    return {
      recommendedType: 'linear',
      confidence: 0.5,
      reasons: ["❌ Aucune variable numérique disponible pour l'analyse"],
      recommendedInteractions: [],
      warnings: ["⚠️ Il faut au moins une variable numérique pour faire une régression"]
    };
  }

  // Trouver le meilleur prédicteur pour l'analyse
  const bestPredictor = findBestPredictor(data, targetVariable, numericCols.map(c => c.name));
  if (!bestPredictor) {
    return {
      recommendedType: 'linear',
      confidence: 0.5,
      reasons: ["⚠️ Impossible de déterminer le meilleur modèle automatiquement"],
      recommendedInteractions: [],
      warnings: ["⚠️ Vérifiez vos données et réessayez"]
    };
  }

  // Tester plusieurs modèles et comparer avec AIC/BIC
  const modelComparison = compareModels(data, targetVariable, bestPredictor.name);

  // Détecter les interactions potentielles
  const interactions = detectPotentialInteractions(data, targetVariable, numericCols.map(c => c.name));

  // Décider du meilleur modèle basé sur AIC/BIC
  return selectBestModel(modelComparison, bestPredictor, interactions);
}

/**
 * Trouve le meilleur prédicteur simple (corrélation la plus forte)
 */
function findBestPredictor(
  data: any[],
  targetVar: string,
  predictors: string[]
): { name: string; correlation: number } | null {
  let bestPredictor: { name: string; correlation: number } | null = null;
  let bestAbsCorr = 0;

  for (const pred of predictors) {
    const pairs = data
      .map(row => {
        const x = row[pred];
        const y = row[targetVar];
        const xNum = typeof x === 'bigint' ? Number(x) : x;
        const yNum = typeof y === 'bigint' ? Number(y) : y;
        return { x: xNum, y: yNum };
      })
      .filter(p => {
        if (p.x == null || p.y == null) return false;
        if (typeof p.x !== 'number' || typeof p.y !== 'number') return false;
        if (!isFinite(p.x) || !isFinite(p.y)) return false;
        return true;
      });

    if (pairs.length < 3) continue;

    const corr = calculateCorrelation(pairs);
    if (!isNaN(corr) && Math.abs(corr) > bestAbsCorr) {
      bestAbsCorr = Math.abs(corr);
      bestPredictor = { name: pred, correlation: corr };
    }
  }

  return bestPredictor;
}

/**
 * Compare plusieurs modèles (linéaire, polynomial, exponentiel)
 * et retourne leurs métriques (R², AIC, BIC)
 */
function compareModels(
  data: any[],
  targetVar: string,
  predictor: string
): {
  linear: { r2: number; aic: number; bic: number };
  polynomial: { r2: number; aic: number; bic: number; degree: number };
  exponential?: { r2: number; aic: number; bic: number };
} {
  // Préparer les données
  const pairs = data
    .map(row => {
      const x = row[predictor];
      const y = row[targetVar];
      const xNum = typeof x === 'bigint' ? Number(x) : x;
      const yNum = typeof y === 'bigint' ? Number(y) : y;
      return { x: xNum, y: yNum };
    })
    .filter(p => {
      if (p.x == null || p.y == null) return false;
      if (typeof p.x !== 'number' || typeof p.y !== 'number') return false;
      if (!isFinite(p.x) || !isFinite(p.y)) return false;
      return true;
    });

  if (pairs.length < 10) {
    // Pas assez de données, retourner des valeurs par défaut
    return {
      linear: { r2: 0, aic: Infinity, bic: Infinity },
      polynomial: { r2: 0, aic: Infinity, bic: Infinity, degree: 2 }
    };
  }

  const n = pairs.length;
  const yValues = pairs.map(p => p.y);

  // 1. Régression linéaire
  const linearFit = fitLinear(pairs);
  const linearResults = calculateModelMetrics(linearFit.predictions, yValues, n, 2); // 2 paramètres (slope + intercept)

  // 2. Régression polynomial (degré 2 et 3)
  const poly2Fit = fitPolynomial(pairs, 2);
  const poly2Results = calculateModelMetrics(poly2Fit.predictions, yValues, n, 3); // 3 paramètres

  const poly3Fit = fitPolynomial(pairs, 3);
  const poly3Results = calculateModelMetrics(poly3Fit.predictions, yValues, n, 4); // 4 paramètres

  // Choisir le meilleur degré polynomial
  const polynomialFit = poly2Results.aic < poly3Results.aic ? poly2Fit : poly3Fit;
  const polynomialResults = poly2Results.aic < poly3Results.aic ? poly2Results : poly3Results;
  const polynomialDegree = poly2Results.aic < poly3Results.aic ? 2 : 3;

  // 3. Régression exponentielle (seulement si y > 0)
  let exponentialResults: { r2: number; aic: number; bic: number } | undefined;
  const positiveYPairs = pairs.filter(p => p.y > 0);
  if (positiveYPairs.length >= pairs.length * 0.8) {
    try {
      const expFit = fitExponential(positiveYPairs);
      const expPredictions = positiveYPairs.map(p => expFit.a * Math.exp(expFit.b * p.x));
      const expYValues = positiveYPairs.map(p => p.y);
      exponentialResults = calculateModelMetrics(expPredictions, expYValues, positiveYPairs.length, 2);
    } catch (e) {
      // L'ajustement exponentiel a échoué (log de valeurs négatives ou nulles)
      exponentialResults = undefined;
    }
  }

  return {
    linear: linearResults,
    polynomial: {
      r2: polynomialResults.r2,
      aic: polynomialResults.aic,
      bic: polynomialResults.bic,
      degree: polynomialDegree
    },
    exponential: exponentialResults
  };
}

/**
 * Fit linéaire (y = a*x + b)
 */
function fitLinear(pairs: { x: number; y: number }[]): { predictions: number[] } {
  const n = pairs.length;
  const sumX = pairs.reduce((sum, p) => sum + p.x, 0);
  const sumY = pairs.reduce((sum, p) => sum + p.y, 0);
  const sumXX = pairs.reduce((sum, p) => sum + p.x * p.x, 0);
  const sumXY = pairs.reduce((sum, p) => sum + p.x * p.y, 0);

  const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
  const intercept = (sumY - slope * sumX) / n;

  const predictions = pairs.map(p => slope * p.x + intercept);
  return { predictions };
}

/**
 * Fit polynomial degré k (y = a0 + a1*x + a2*x² + ... + ak*x^k)
 * Utilise la matrice de Vandermonde
 */
function fitPolynomial(pairs: { x: number; y: number }[], degree: number): { predictions: number[] } {
  const n = pairs.length;

  // Normaliser x pour éviter les problèmes numériques
  const xValues = pairs.map(p => p.x);
  const minX = Math.min(...xValues);
  const maxX = Math.max(...xValues);
  const range = maxX - minX || 1;

  // Créer la matrice de Vandermonde
  const X: number[][] = [];
  const Y: number[] = pairs.map(p => p.y);

  for (let i = 0; i < n; i++) {
    const normalizedX = (pairs[i].x - minX) / range;
    const row: number[] = [];
    for (let j = 0; j <= degree; j++) {
      row.push(Math.pow(normalizedX, j));
    }
    X.push(row);
  }

  // Résoudre X'X * beta = X'Y
  const XT = transpose(X);
  const XTX = matrixMultiply(XT, X);
  const XTY = matrixVectorMultiply(XT, Y);

  // Ajouter une petite régularisation pour la stabilité numérique
  const lambda = 1e-10;
  for (let i = 0; i <= degree; i++) {
    XTX[i][i] += lambda;
  }

  const coeffs = solveLinearSystem(XTX, XTY);

  // Générer les prédictions
  const predictions = pairs.map(p => {
    const normalizedX = (p.x - minX) / range;
    let y = 0;
    for (let j = 0; j <= degree; j++) {
      y += coeffs[j] * Math.pow(normalizedX, j);
    }
    return y;
  });

  return { predictions };
}

/**
 * Fit exponentielle (y = a * e^(b*x))
 */
function fitExponential(pairs: { x: number; y: number }[]): { a: number; b: number } {
  // Transformer: ln(y) = ln(a) + b*x
  const logPairs = pairs.map(p => ({ x: p.x, y: Math.log(p.y) }));

  const n = logPairs.length;
  const sumX = logPairs.reduce((sum, p) => sum + p.x, 0);
  const sumY = logPairs.reduce((sum, p) => sum + p.y, 0);
  const sumXX = logPairs.reduce((sum, p) => sum + p.x * p.x, 0);
  const sumXY = logPairs.reduce((sum, p) => sum + p.x * p.y, 0);

  const b = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
  const lnA = (sumY - b * sumX) / n;
  const a = Math.exp(lnA);

  return { a, b };
}

/**
 * Calcule les métriques de modèle (R², AIC, BIC)
 */
function calculateModelMetrics(
  predictions: number[],
  actual: number[],
  n: number,
  k: number
): { r2: number; aic: number; bic: number } {
  // R²
  const meanY = actual.reduce((a, b) => a + b, 0) / n;
  const ssTot = actual.reduce((sum, y) => sum + Math.pow(y - meanY, 2), 0);
  const ssRes = actual.reduce((sum, y, i) => sum + Math.pow(y - predictions[i], 2), 0);
  const r2 = ssTot === 0 ? 1 : 1 - ssRes / ssTot;

  // AIC = 2k - 2ln(L)
  // Pour régression: AIC = n * ln(SSR/n) + 2k
  const ssr = ssRes;
  const aic = n * Math.log(ssr / n) + 2 * k;

  // BIC = n * ln(SSR/n) + k * ln(n)
  const bic = n * Math.log(ssr / n) + k * Math.log(n);

  return { r2: Math.max(0, r2), aic, bic };
}

/**
 * Sélectionne le meilleur modèle basé sur AIC/BIC et d'autres critères
 */
function selectBestModel(
  comparison: {
    linear: { r2: number; aic: number; bic: number };
    polynomial: { r2: number; aic: number; bic: number; degree: number };
    exponential?: { r2: number; aic: number; bic: number };
  },
  bestPredictor: { name: string; correlation: number },
  interactions: string[][]
): ModelRecommendation {
  // Critère principal: AIC (plus petit = meilleur)
  type ModelWithType = { type: 'linear' | 'polynomial' | 'exponential'; r2: number; aic: number; bic: number; degree?: number };
  const models: ModelWithType[] = [
    { type: 'linear' as const, ...comparison.linear },
    { type: 'polynomial' as const, ...comparison.polynomial },
  ];

  if (comparison.exponential) {
    models.push({ type: 'exponential' as const, ...comparison.exponential });
  }

  // Trier par AIC
  models.sort((a, b) => a.aic - b.aic);
  const bestModel = models[0];

  // Vérifier si le modèle polynomial est SIGNIFICATIVEMENT meilleur
  const deltaAIC = models[1].aic - models[0].aic; // Différence avec le 2ème meilleur
  const isSignificantlyBetter = deltaAIC > 10; // Seuil de 10 pour forte évidence

  if (bestModel.type === 'polynomial' && isSignificantlyBetter) {
    return {
      recommendedType: 'polynomial',
      confidence: Math.min(0.95, 0.7 + (deltaAIC / 100)),
      polynomialDegree: comparison.polynomial.degree,
      reasons: [
        `✅ Le modèle polynomial (degré ${comparison.polynomial.degree}) est **significativement meilleur** (ΔAIC = ${deltaAIC.toFixed(1)})`,
        `✅ R² ajusté: ${(comparison.polynomial.r2 * 100).toFixed(1)}% vs ${(comparison.linear.r2 * 100).toFixed(1)}% pour linéaire`,
        `✅ AIC polynomial: ${comparison.polynomial.aic.toFixed(0)} vs linéaire: ${comparison.linear.aic.toFixed(0)}`,
        `✅ La relation entre ${bestPredictor.name} et la cible n'est pas linéaire`
      ],
      recommendedInteractions: interactions,
      warnings: comparison.polynomial.degree >= 4
        ? ["⚠️ Degré élevé - risque de sur-apprentissage", "⚠️ Le modèle pourrait être instable"]
        : []
    };
  }

  if (bestModel.type === 'exponential' && isSignificantlyBetter) {
    return {
      recommendedType: 'exponential',
      confidence: 0.85,
      reasons: [
        `✅ Le modèle exponentiel est **significativement meilleur** (ΔAIC = ${deltaAIC.toFixed(1)})`,
        `✅ R²: ${(comparison.exponential!.r2 * 100).toFixed(1)}% vs ${(comparison.linear.r2 * 100).toFixed(1)}% pour linéaire`,
        `✅ Croissance/décroissance exponentielle détectée`,
        `✅ AIC exponentiel: ${comparison.exponential!.aic.toFixed(0)} vs linéaire: ${comparison.linear.aic.toFixed(0)}`
      ],
      recommendedInteractions: interactions,
      warnings: []
    };
  }

  // Par défaut linéaire
  const isStrongCorrelation = Math.abs(bestPredictor.correlation) >= 0.7;
  return {
    recommendedType: 'linear',
    confidence: isStrongCorrelation ? 0.9 : 0.7,
    reasons: [
      `✅ La régression **linéaire** est le modèle le plus approprié`,
      `✅ Corrélation avec ${bestPredictor.name}: r=${bestPredictor.correlation.toFixed(3)}`,
      `✅ R² théorique: ${(bestModel.r2 * 100).toFixed(1)}%`,
      `✅ AIC linéaire: ${comparison.linear.aic.toFixed(0)} (le plus bas = meilleur)`
    ],
    recommendedInteractions: interactions,
    warnings: !isStrongCorrelation
      ? [
        "⚠️ Corrélation modérée. Le R² pourrait être faible.",
        "💡 Considérez d'ajouter des variables ou transformations pour améliorer le fit."
      ]
      : interactions.length > 0
      ? [`💡 Des interactions ont été détectées. Activez "Inclure interactions" pour les tester.`]
      : []
  };
}

/**
 * Analyse une variable
 */
function analyzeVariable(data: any[], varName: string, varType: string): VariableAnalysis {
  const values = data.map(row => row[varName]).filter(v => v != null && v !== '');
  const uniqueValues = new Set(values);

  return {
    name: varName,
    type: varType === 'numeric' ? 'numeric' : 'categorical',
    uniqueCount: uniqueValues.size,
    missingCount: data.length - values.length,
    isBinary: uniqueValues.size === 2 && varType === 'numeric' &&
      [...uniqueValues].every(v => v === 0 || v === 1 || v === '0' || v === '1' || v === false || v === true)
  };
}

/**
 * Calcule la corrélation de Pearson
 */
function calculateCorrelation(pairs: { x: number; y: number }[]): number {
  const n = pairs.length;
  if (n === 0) return NaN;

  const meanX = pairs.reduce((sum, p) => sum + p.x, 0) / n;
  const meanY = pairs.reduce((sum, p) => sum + p.y, 0) / n;

  let num = 0;
  let denX = 0;
  let denY = 0;

  for (const p of pairs) {
    const dx = p.x - meanX;
    const dy = p.y - meanY;
    num += dx * dy;
    denX += dx * dx;
    denY += dy * dy;
  }

  const denominator = Math.sqrt(denX * denY);
  return denominator === 0 ? NaN : num / denominator;
}

/**
 * Détecte les interactions potentielles entre variables
 */
function detectPotentialInteractions(
  data: any[],
  targetVar: string,
  predictors: string[]
): string[][] {
  const interactions: string[][] = [];

  // Limiter à 5 prédicteurs pour éviter O(n³)
  const maxPredictors = Math.min(predictors.length, 5);

  for (let i = 0; i < maxPredictors; i++) {
    for (let j = i + 1; j < maxPredictors; j++) {
      const var1 = predictors[i];
      const var2 = predictors[j];

      // Calculer corrélation Var1 → Target
      const pairs1 = data
        .map(row => {
          const x = row[var1];
          const y = row[targetVar];
          const xNum = typeof x === 'bigint' ? Number(x) : x;
          const yNum = typeof y === 'bigint' ? Number(y) : y;
          return { x: xNum, y: yNum };
        })
        .filter(p => {
          if (p.x == null || p.y == null) return false;
          if (typeof p.x !== 'number' || typeof p.y !== 'number') return false;
          if (!isFinite(p.x) || !isFinite(p.y)) return false;
          return true;
        });
      const corr1 = pairs1.length >= 3 ? calculateCorrelation(pairs1) : NaN;

      // Calculer corrélation Var2 → Target
      const pairs2 = data
        .map(row => {
          const x = row[var2];
          const y = row[targetVar];
          const xNum = typeof x === 'bigint' ? Number(x) : x;
          const yNum = typeof y === 'bigint' ? Number(y) : y;
          return { x: xNum, y: yNum };
        })
        .filter(p => {
          if (p.x == null || p.y == null) return false;
          if (typeof p.x !== 'number' || typeof p.y !== 'number') return false;
          if (!isFinite(p.x) || !isFinite(p.y)) return false;
          return true;
        });
      const corr2 = pairs2.length >= 3 ? calculateCorrelation(pairs2) : NaN;

      // Créer variable d'interaction
      const interactionPairs = data
        .map(row => {
          const x1 = row[var1];
          const x2 = row[var2];
          const y = row[targetVar];
          const x1Num = typeof x1 === 'bigint' ? Number(x1) : x1;
          const x2Num = typeof x2 === 'bigint' ? Number(x2) : x2;
          const yNum = typeof y === 'bigint' ? Number(y) : y;
          return { x: (x1Num || 0) * (x2Num || 0), y: yNum };
        })
        .filter(p => {
          if (typeof p.x !== 'number' || p.y == null) return false;
          if (!isFinite(p.x) || !isFinite(p.y)) return false;
          return true;
        });

      if (interactionPairs.length < 10) continue;

      const corrInteraction = interactionPairs.length >= 3 ? calculateCorrelation(interactionPairs) : NaN;

      // Si l'interaction a une meilleure corrélation que les variables seules
      if (
        !isNaN(corrInteraction) &&
        !isNaN(corr1) &&
        !isNaN(corr2) &&
        Math.abs(corrInteraction) > Math.max(Math.abs(corr1), Math.abs(corr2)) + 0.15
      ) {
        interactions.push([var1, var2]);
      }
    }
  }

  return interactions;
}

// Fonctions utilitaires pour l'algèbre linéaire
function transpose(matrix: number[][]): number[][] {
  return matrix[0].map((_, i) => matrix.map(row => row[i]));
}

function matrixMultiply(A: number[][], B: number[][]): number[][] {
  const result: number[][] = [];
  for (let i = 0; i < A.length; i++) {
    result[i] = [];
    for (let j = 0; j < B[0].length; j++) {
      let sum = 0;
      for (let k = 0; k < B.length; k++) {
        sum += A[i][k] * B[k][j];
      }
      result[i][j] = sum;
    }
  }
  return result;
}

function matrixVectorMultiply(matrix: number[][], vector: number[]): number[] {
  return matrix.map(row => row.reduce((sum, val, i) => sum + val * vector[i], 0));
}

function solveLinearSystem(A: number[][], b: number[]): number[] {
  const n = A.length;

  // Élimination de Gauss
  for (let i = 0; i < n; i++) {
    // Pivot partiel
    let maxRow = i;
    for (let k = i + 1; k < n; k++) {
      if (Math.abs(A[k][i]) > Math.abs(A[maxRow][i])) {
        maxRow = k;
      }
    }

    // Échanger les lignes
    [A[i], A[maxRow]] = [A[maxRow], A[i]];
    [b[i], b[maxRow]] = [b[maxRow], b[i]];

    // Élimination
    for (let k = i + 1; k < n; k++) {
      const factor = A[k][i] / A[i][i];
      for (let j = i; j < n; j++) {
        A[k][j] -= factor * A[i][j];
      }
      b[k] -= factor * b[i];
    }
  }

  // Substitution arrière
  const x = new Array(n).fill(0);
  for (let i = n - 1; i >= 0; i--) {
    x[i] = b[i];
    for (let j = i + 1; j < n; j++) {
      x[i] -= A[i][j] * x[j];
    }
    x[i] /= A[i][i];
  }

  return x;
}
