/**
 * Metrics Calculations for Analytics
 * Provides metric calculations and statistical functions
 */

import { TrendDirection, TimeSeriesDataPoint, KPICardData } from './types';

/**
 * Calculate mean of an array of numbers
 */
export function mean(values: number[]): number {
  if (values.length === 0) return 0;
  return values.reduce((sum, v) => sum + v, 0) / values.length;
}

/**
 * Calculate median of an array of numbers
 */
export function median(values: number[]): number {
  if (values.length === 0) return 0;
  const sorted = [...values].sort((a, b) => a - b);
  const mid = Math.floor(sorted.length / 2);
  return sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
}

/**
 * Calculate standard deviation
 */
export function standardDeviation(values: number[]): number {
  if (values.length === 0) return 0;
  const avg = mean(values);
  const squareDiffs = values.map(v => Math.pow(v - avg, 2));
  return Math.sqrt(mean(squareDiffs));
}

/**
 * Calculate percentile
 */
export function percentile(values: number[], p: number): number {
  if (values.length === 0) return 0;
  const sorted = [...values].sort((a, b) => a - b);
  const index = (p / 100) * (sorted.length - 1);
  const lower = Math.floor(index);
  const upper = Math.ceil(index);
  const weight = index - lower;
  return sorted[lower] * (1 - weight) + sorted[upper] * weight;
}

/**
 * Calculate min and max
 */
export function minMax(values: number[]): { min: number; max: number } {
  if (values.length === 0) return { min: 0, max: 0 };
  return {
    min: Math.min(...values),
    max: Math.max(...values),
  };
}

/**
 * Determine trend direction from two values
 */
export function getTrendDirection(current: number, previous: number, threshold: number = 0.5): TrendDirection {
  const change = ((current - previous) / Math.abs(previous || 1)) * 100;
  if (Math.abs(change) < threshold) return 'stable';
  return change > 0 ? 'up' : 'down';
}

/**
 * Calculate percentage change
 */
export function percentageChange(current: number, previous: number): number {
  if (previous === 0) return current > 0 ? 100 : 0;
  return ((current - previous) / Math.abs(previous)) * 100;
}

/**
 * Calculate moving average
 */
export function movingAverage(data: TimeSeriesDataPoint[], windowSize: number): TimeSeriesDataPoint[] {
  return data.map((point, index) => {
    const start = Math.max(0, index - windowSize + 1);
    const window = data.slice(start, index + 1);
    const avg = mean(window.map(p => p.value));
    return {
      ...point,
      value: Math.round(avg * 100) / 100,
    };
  });
}

/**
 * Calculate rate of change (derivative)
 */
export function rateOfChange(data: TimeSeriesDataPoint[]): TimeSeriesDataPoint[] {
  return data.slice(1).map((point, index) => ({
    ...point,
    value: point.value - data[index].value,
  }));
}

/**
 * Normalize values to 0-100 range
 */
export function normalize(values: number[]): number[] {
  const { min, max } = minMax(values);
  const range = max - min;
  if (range === 0) return values.map(() => 50);
  return values.map(v => ((v - min) / range) * 100);
}

/**
 * Calculate compound annual growth rate (CAGR)
 */
export function cagr(startValue: number, endValue: number, years: number): number {
  if (startValue <= 0 || years <= 0) return 0;
  return (Math.pow(endValue / startValue, 1 / years) - 1) * 100;
}

/**
 * Calculate efficiency score
 */
export function efficiencyScore(actual: number, optimal: number): number {
  if (optimal === 0) return actual === 0 ? 100 : 0;
  return Math.min(100, (optimal / actual) * 100);
}

/**
 * Calculate carbon intensity (kg CO2 per km)
 */
export function carbonIntensity(carbonKg: number, distanceKm: number): number {
  if (distanceKm === 0) return 0;
  return carbonKg / distanceKm;
}

/**
 * Calculate food miles score (0-100, lower is better)
 */
export function foodMilesScore(miles: number, maxMiles: number = 5000): number {
  if (miles >= maxMiles) return 0;
  return Math.round((1 - miles / maxMiles) * 100);
}

/**
 * Calculate sustainability composite score
 */
export function sustainabilityScore(
  carbonReduction: number,
  localPercentage: number,
  waterEfficiency: number,
  wasteReduction: number
): number {
  const weights = {
    carbon: 0.35,
    local: 0.25,
    water: 0.25,
    waste: 0.15,
  };

  return Math.round(
    carbonReduction * weights.carbon +
    localPercentage * weights.local +
    waterEfficiency * weights.water +
    wasteReduction * weights.waste
  );
}

/**
 * Generate KPI card data from metrics
 */
export function generateKPICards(metrics: {
  plants: { current: number; previous: number };
  carbon: { current: number; previous: number };
  foodMiles: { current: number; previous: number };
  users: { current: number; previous: number };
  sustainability: { current: number; previous: number };
}): KPICardData[] {
  return [
    {
      id: 'total-plants',
      title: 'Total Plants',
      value: metrics.plants.current,
      change: metrics.plants.current - metrics.plants.previous,
      changePercent: percentageChange(metrics.plants.current, metrics.plants.previous),
      trend: getTrendDirection(metrics.plants.current, metrics.plants.previous),
      color: 'green',
    },
    {
      id: 'carbon-saved',
      title: 'Carbon Saved',
      value: metrics.carbon.current.toFixed(1),
      unit: 'kg CO2',
      change: metrics.carbon.current - metrics.carbon.previous,
      changePercent: percentageChange(metrics.carbon.current, metrics.carbon.previous),
      trend: getTrendDirection(metrics.carbon.current, metrics.carbon.previous),
      color: 'teal',
    },
    {
      id: 'food-miles',
      title: 'Food Miles',
      value: metrics.foodMiles.current.toFixed(0),
      unit: 'km',
      change: metrics.foodMiles.current - metrics.foodMiles.previous,
      changePercent: percentageChange(metrics.foodMiles.current, metrics.foodMiles.previous),
      trend: getTrendDirection(metrics.foodMiles.previous, metrics.foodMiles.current), // Inverted: lower is better
      color: 'blue',
    },
    {
      id: 'active-users',
      title: 'Active Users',
      value: metrics.users.current,
      change: metrics.users.current - metrics.users.previous,
      changePercent: percentageChange(metrics.users.current, metrics.users.previous),
      trend: getTrendDirection(metrics.users.current, metrics.users.previous),
      color: 'purple',
    },
    {
      id: 'sustainability',
      title: 'Sustainability Score',
      value: metrics.sustainability.current.toFixed(0),
      unit: '%',
      change: metrics.sustainability.current - metrics.sustainability.previous,
      changePercent: percentageChange(metrics.sustainability.current, metrics.sustainability.previous),
      trend: getTrendDirection(metrics.sustainability.current, metrics.sustainability.previous),
      color: 'green',
    },
  ];
}

/**
 * Calculate growth metrics
 */
export function calculateGrowthMetrics(data: TimeSeriesDataPoint[]): {
  totalGrowth: number;
  averageDaily: number;
  peakValue: number;
  peakDate: string;
  trend: TrendDirection;
} {
  if (data.length === 0) {
    return { totalGrowth: 0, averageDaily: 0, peakValue: 0, peakDate: '', trend: 'stable' };
  }

  const values = data.map(d => d.value);
  const total = values.reduce((sum, v) => sum + v, 0);
  const avgDaily = total / data.length;
  const maxIndex = values.indexOf(Math.max(...values));

  const firstHalf = mean(values.slice(0, Math.floor(values.length / 2)));
  const secondHalf = mean(values.slice(Math.floor(values.length / 2)));

  return {
    totalGrowth: total,
    averageDaily: Math.round(avgDaily * 100) / 100,
    peakValue: values[maxIndex],
    peakDate: data[maxIndex].timestamp,
    trend: getTrendDirection(secondHalf, firstHalf),
  };
}

/**
 * Detect anomalies using z-score
 */
export function detectAnomalies(
  data: TimeSeriesDataPoint[],
  threshold: number = 2
): TimeSeriesDataPoint[] {
  const values = data.map(d => d.value);
  const avg = mean(values);
  const std = standardDeviation(values);

  if (std === 0) return [];

  return data.filter(point => {
    const zScore = Math.abs((point.value - avg) / std);
    return zScore > threshold;
  });
}

/**
 * Calculate correlation coefficient between two datasets
 */
export function correlationCoefficient(x: number[], y: number[]): number {
  if (x.length !== y.length || x.length === 0) return 0;

  const n = x.length;
  const meanX = mean(x);
  const meanY = mean(y);

  let numerator = 0;
  let denomX = 0;
  let denomY = 0;

  for (let i = 0; i < n; i++) {
    const dx = x[i] - meanX;
    const dy = y[i] - meanY;
    numerator += dx * dy;
    denomX += dx * dx;
    denomY += dy * dy;
  }

  const denominator = Math.sqrt(denomX * denomY);
  return denominator === 0 ? 0 : numerator / denominator;
}

/**
 * Format large numbers for display
 */
export function formatNumber(value: number, decimals: number = 1): string {
  if (Math.abs(value) >= 1000000) {
    return (value / 1000000).toFixed(decimals) + 'M';
  }
  if (Math.abs(value) >= 1000) {
    return (value / 1000).toFixed(decimals) + 'K';
  }
  return value.toFixed(decimals);
}

/**
 * Format percentage for display
 */
export function formatPercentage(value: number, showSign: boolean = false): string {
  const formatted = value.toFixed(1);
  if (showSign && value > 0) return '+' + formatted + '%';
  return formatted + '%';
}
