/**
 * Sorting utilities for data manipulation
 */

import { SortConfig, MultiSortConfig, ComparatorFn, SortDirection, DataType } from './types';

/**
 * Get nested property value from object
 * @param obj - Object to get value from
 * @param path - Property path (supports dot notation)
 */
function getNestedValue(obj: any, path: string | keyof any): any {
  if (typeof path === 'string' && path.includes('.')) {
    return path.split('.').reduce((current, key) => current?.[key], obj);
  }
  return obj?.[path];
}

/**
 * Convert value to appropriate type for comparison
 * @param value - Value to convert
 * @param dataType - Target data type
 */
function convertValue(value: any, dataType: DataType): any {
  if (value == null) return value;

  switch (dataType) {
    case 'string':
      return String(value).toLowerCase();
    case 'number':
      const num = Number(value);
      return isNaN(num) ? 0 : num;
    case 'date':
      if (value instanceof Date) return value;
      const date = new Date(value);
      return isNaN(date.getTime()) ? new Date(0) : date;
    case 'boolean':
      return Boolean(value);
    default:
      return value;
  }
}

/**
 * Create a comparator function for a specific property and direction
 * @param key - Property key to sort by
 * @param direction - Sort direction
 * @param dataType - Data type for proper comparison
 */
export function createComparator<T = any>(
  key: keyof T,
  direction: SortDirection = 'asc',
  dataType: DataType = 'string'
): ComparatorFn<T> {
  return (a: T, b: T): number => {
    const valueA = convertValue(getNestedValue(a, key), dataType);
    const valueB = convertValue(getNestedValue(b, key), dataType);

    // Handle null/undefined values
    if (valueA == null && valueB == null) return 0;
    if (valueA == null) return direction === 'asc' ? -1 : 1;
    if (valueB == null) return direction === 'asc' ? 1 : -1;

    let result = 0;

    if (dataType === 'string') {
      result = valueA.localeCompare(valueB);
    } else if (dataType === 'date') {
      result = valueA.getTime() - valueB.getTime();
    } else {
      // For numbers and booleans
      if (valueA < valueB) result = -1;
      else if (valueA > valueB) result = 1;
      else result = 0;
    }

    return direction === 'desc' ? -result : result;
  };
}

/**
 * Sort array by single property
 * @param data - Array to sort
 * @param config - Sort configuration
 */
export function sortBy<T>(data: T[], config: SortConfig<T>): T[] {
  if (!Array.isArray(data) || data.length === 0) return data;

  const comparator = createComparator(
    config.key,
    config.direction,
    config.dataType || 'string'
  );

  return [...data].sort(comparator);
}

/**
 * Sort array by multiple properties with priority
 * @param data - Array to sort
 * @param config - Multi-sort configuration
 */
export function sortByMultiple<T>(data: T[], config: MultiSortConfig<T>): T[] {
  if (!Array.isArray(data) || data.length === 0) return data;
  if (!config.sorts || config.sorts.length === 0) return data;

  // Create comparators for each sort configuration
  const comparators = config.sorts.map(sortConfig =>
    createComparator(
      sortConfig.key,
      sortConfig.direction,
      sortConfig.dataType || 'string'
    )
  );

  return [...data].sort((a: T, b: T): number => {
    // Apply each comparator in order until we find a non-zero result
    for (const comparator of comparators) {
      const result = comparator(a, b);
      if (result !== 0) return result;
    }
    return 0;
  });
}

/**
 * Create a stable sort function that maintains relative order of equal elements
 * @param data - Array to sort
 * @param comparator - Comparison function
 */
export function stableSort<T>(data: T[], comparator: ComparatorFn<T>): T[] {
  if (!Array.isArray(data) || data.length === 0) return data;

  // Add index to maintain stability
  const indexed = data.map((item, index) => ({ item, index }));

  indexed.sort((a, b) => {
    const result = comparator(a.item, b.item);
    // If items are equal, maintain original order
    return result !== 0 ? result : a.index - b.index;
  });

  return indexed.map(({ item }) => item);
}

/**
 * Check if an array is sorted according to a comparator
 * @param data - Array to check
 * @param comparator - Comparison function
 */
export function isSorted<T>(data: T[], comparator: ComparatorFn<T>): boolean {
  if (!Array.isArray(data) || data.length <= 1) return true;

  for (let i = 1; i < data.length; i++) {
    if (comparator(data[i - 1], data[i]) > 0) return false;
  }
  return true;
}

/**
 * Reverse sort direction
 * @param direction - Current direction
 */
export function reverseDirection(direction: SortDirection): SortDirection {
  return direction === 'asc' ? 'desc' : 'asc';
}

/**
 * Get sort direction for next click (for UI toggling)
 * @param currentDirection - Current sort direction
 * @param allowUnsorted - Whether to allow unsorted state
 */
export function getNextSortDirection(
  currentDirection: SortDirection | null,
  allowUnsorted: boolean = true
): SortDirection | null {
  if (currentDirection === null) return 'asc';
  if (currentDirection === 'asc') return 'desc';
  if (currentDirection === 'desc' && allowUnsorted) return null;
  return 'asc'; // Cycle back to asc if unsorted not allowed
}

/**
 * Sort array by multiple keys with individual directions
 * @param data - Array to sort
 * @param keys - Array of keys to sort by
 * @param directions - Array of directions (defaults to 'asc' for all)
 * @param dataTypes - Array of data types (defaults to 'string' for all)
 */
export function sortByKeys<T>(
  data: T[],
  keys: (keyof T)[],
  directions: SortDirection[] = [],
  dataTypes: DataType[] = []
): T[] {
  if (!Array.isArray(data) || data.length === 0) return data;
  if (!keys || keys.length === 0) return data;

  const sorts: SortConfig<T>[] = keys.map((key, index) => ({
    key,
    direction: directions[index] || 'asc',
    dataType: dataTypes[index] || 'string'
  }));

  return sortByMultiple(data, { sorts });
}