/**
 * Generic utility functions that work with standardized types
 * These functions demonstrate how to use generic types with OHLCV data
 */

import type { HasClose, HasOHLC, HasVolume, OHLCV } from '@stock-bot/types';

/**
 * Extract close prices from any data structure that has a close field
 * Works with OHLCV, MarketData, or any custom type with close price
 */
export function extractCloses<T extends HasClose>(data: T[]): number[] {
  return data.map(item => item.close);
}

/**
 * Extract OHLC prices from any data structure that has OHLC fields
 */
export function extractOHLC<T extends HasOHLC>(
  data: T[]
): {
  opens: number[];
  highs: number[];
  lows: number[];
  closes: number[];
} {
  return {
    opens: data.map(item => item.open),
    highs: data.map(item => item.high),
    lows: data.map(item => item.low),
    closes: data.map(item => item.close),
  };
}

/**
 * Extract volumes from any data structure that has a volume field
 */
export function extractVolumes<T extends HasVolume>(data: T[]): number[] {
  return data.map(item => item.volume);
}

/**
 * Calculate simple moving average using close prices from any compatible data type
 */
export function calculateSMA<T extends HasClose>(data: T[], period: number): number[] {
  const closes = extractCloses(data);
  const result: number[] = [];

  for (let i = period - 1; i < closes.length; i++) {
    const sum = closes.slice(i - period + 1, i + 1).reduce((a, b) => a + b, 0);
    result.push(sum / period);
  }

  return result;
}

/**
 * Calculate typical price (HLC/3) from any OHLC compatible data
 */
export function calculateTypicalPrice<T extends HasOHLC>(data: T[]): number[] {
  return data.map(item => (item.high + item.low + item.close) / 3);
}

/**
 * Calculate true range from OHLC data
 */
export function calculateTrueRange<T extends HasOHLC>(data: T[]): number[] {
  const result: number[] = [];

  for (let i = 0; i < data.length; i++) {
    if (i === 0) {
      result.push(data[i]!.high - data[i]!.low);
    } else {
      const current = data[i]!;
      const previous = data[i - 1]!;
      const tr = Math.max(
        current.high - current.low,
        Math.abs(current.high - previous.close),
        Math.abs(current.low - previous.close)
      );
      result.push(tr);
    }
  }

  return result;
}

/**
 * Calculate returns from close prices
 */
export function calculateReturns<T extends HasClose>(data: T[]): number[] {
  const closes = extractCloses(data);
  const returns: number[] = [];

  for (let i = 1; i < closes.length; i++) {
    const current = closes[i]!;
    const previous = closes[i - 1]!;
    if (previous > 0) {
      returns.push((current - previous) / previous);
    } else {
      returns.push(0);
    }
  }

  return returns;
}

/**
 * Calculate log returns from close prices
 */
export function calculateLogReturns<T extends HasClose>(data: T[]): number[] {
  const closes = extractCloses(data);
  const logReturns: number[] = [];

  for (let i = 1; i < closes.length; i++) {
    const current = closes[i]!;
    const previous = closes[i - 1]!;
    if (previous > 0 && current > 0) {
      logReturns.push(Math.log(current / previous));
    } else {
      logReturns.push(0);
    }
  }

  return logReturns;
}

/**
 * Calculate volume-weighted average price (VWAP) from OHLC + Volume data
 */
export function calculateVWAP<T extends HasOHLC & HasVolume>(data: T[]): number[] {
  const result: number[] = [];
  let cumulativeVolumePrice = 0;
  let cumulativeVolume = 0;

  for (const item of data) {
    const typicalPrice = (item.high + item.low + item.close) / 3;
    cumulativeVolumePrice += typicalPrice * item.volume;
    cumulativeVolume += item.volume;

    if (cumulativeVolume > 0) {
      result.push(cumulativeVolumePrice / cumulativeVolume);
    } else {
      result.push(typicalPrice);
    }
  }

  return result;
}

/**
 * Filter OHLCV data by symbol
 */
export function filterBySymbol(data: OHLCV[], symbol: string): OHLCV[] {
  return data.filter(item => item.symbol === symbol);
}

/**
 * Filter OHLCV data by time range
 */
export function filterByTimeRange(data: OHLCV[], startTime: number, endTime: number): OHLCV[] {
  return data.filter(item => item.timestamp >= startTime && item.timestamp <= endTime);
}

/**
 * Group OHLCV data by symbol
 */
export function groupBySymbol(data: OHLCV[]): Record<string, OHLCV[]> {
  const grouped: Record<string, OHLCV[]> = {};

  for (const item of data) {
    if (!grouped[item.symbol]) {
      grouped[item.symbol] = [];
    }
    grouped[item.symbol]!.push(item);
  }

  return grouped;
}

/**
 * Convert timestamp to Date for OHLCV data
 */
export function convertTimestamps(data: OHLCV[]): Array<OHLCV & { date: Date }> {
  return data.map(item => ({
    ...item,
    date: new Date(item.timestamp),
  }));
}
