/**
 * Transform Utilities
 *
 * Client-side data transformation utilities for filtering, sorting,
 * searching, and pagination. Useful for client-side data manipulation
 * when server-side operations are not available.
 *
 * @example
 * ```tsx
 * import { transformData, filterData, sortData, searchData } from '@/prism/headless/utils';
 *
 * // Full pipeline
 * const result = transformData({
 *   data: users,
 *   search: { query: 'john', fields: ['name', 'email'] },
 *   filters: [{ field: 'status', operator: 'eq', value: 'active' }],
 *   sort: [{ field: 'name', direction: 'asc' }],
 *   pagination: { page: 1, pageSize: 20 },
 * });
 *
 * // Or use individual functions
 * const filtered = filterData(users, filters);
 * const sorted = sortData(filtered, sorts);
 * const searched = searchData(sorted, 'john', ['name', 'email']);
 * ```
 *
 * ⚠️ AUTO-GENERATED BY PRISM - DO NOT EDIT
 */

import type {
  FilterEntry,
  FilterOperator,
  SortEntry,
  SortDirection,
  TransformOptions,
  TransformResult,
} from '../types';

// ============================================================================
// Filter Utilities
// ============================================================================

/**
 * Get a nested property value from an object using a dot-notation path.
 */
function getNestedValue<T>(obj: T, path: string): unknown {
  const keys = path.split('.');
  let value: unknown = obj;

  for (const key of keys) {
    if (value === null || value === undefined) {
      return undefined;
    }
    value = (value as Record<string, unknown>)[key];
  }

  return value;
}

/**
 * Check if a value matches a filter condition.
 */
function matchesFilter<T>(
  item: T,
  filter: FilterEntry<keyof T | string>
): boolean {
  const value = getNestedValue(item, filter.field as string);
  const filterValue = filter.value;

  switch (filter.operator) {
    case 'eq':
      return value === filterValue;

    case 'neq':
      return value !== filterValue;

    case 'gt':
      return (
        typeof value === 'number' &&
        typeof filterValue === 'number' &&
        value > filterValue
      );

    case 'gte':
      return (
        typeof value === 'number' &&
        typeof filterValue === 'number' &&
        value >= filterValue
      );

    case 'lt':
      return (
        typeof value === 'number' &&
        typeof filterValue === 'number' &&
        value < filterValue
      );

    case 'lte':
      return (
        typeof value === 'number' &&
        typeof filterValue === 'number' &&
        value <= filterValue
      );

    case 'contains':
      return (
        typeof value === 'string' &&
        typeof filterValue === 'string' &&
        value.toLowerCase().includes(filterValue.toLowerCase())
      );

    case 'startsWith':
      return (
        typeof value === 'string' &&
        typeof filterValue === 'string' &&
        value.toLowerCase().startsWith(filterValue.toLowerCase())
      );

    case 'endsWith':
      return (
        typeof value === 'string' &&
        typeof filterValue === 'string' &&
        value.toLowerCase().endsWith(filterValue.toLowerCase())
      );

    case 'in':
      return Array.isArray(filterValue) && filterValue.includes(value);

    case 'notIn':
      return Array.isArray(filterValue) && !filterValue.includes(value);

    case 'between':
      return (
        typeof value === 'number' &&
        Array.isArray(filterValue) &&
        filterValue.length === 2 &&
        typeof filterValue[0] === 'number' &&
        typeof filterValue[1] === 'number' &&
        value >= filterValue[0] &&
        value <= filterValue[1]
      );

    case 'isNull':
      return value === null || value === undefined;

    case 'isNotNull':
      return value !== null && value !== undefined;

    default:
      return true;
  }
}

/**
 * Filter an array of items based on filter entries.
 *
 * @param data - Array of items to filter
 * @param filters - Array of filter entries
 * @returns Filtered array
 */
export function filterData<T>(
  data: T[],
  filters: FilterEntry<keyof T | string>[]
): T[] {
  if (filters.length === 0) {
    return data;
  }

  return data.filter((item) =>
    filters.every((filter) => matchesFilter(item, filter))
  );
}

// ============================================================================
// Sort Utilities
// ============================================================================

/**
 * Compare two values for sorting.
 */
function compareValues(a: unknown, b: unknown, direction: SortDirection): number {
  // Handle null/undefined
  if (a === null || a === undefined) {
    return direction === 'asc' ? 1 : -1;
  }
  if (b === null || b === undefined) {
    return direction === 'asc' ? -1 : 1;
  }

  // Compare by type
  if (typeof a === 'string' && typeof b === 'string') {
    const result = a.localeCompare(b, undefined, { sensitivity: 'base' });
    return direction === 'asc' ? result : -result;
  }

  if (typeof a === 'number' && typeof b === 'number') {
    const result = a - b;
    return direction === 'asc' ? result : -result;
  }

  if (a instanceof Date && b instanceof Date) {
    const result = a.getTime() - b.getTime();
    return direction === 'asc' ? result : -result;
  }

  // Fallback to string comparison
  const result = String(a).localeCompare(String(b));
  return direction === 'asc' ? result : -result;
}

/**
 * Sort an array of items based on sort entries.
 *
 * @param data - Array of items to sort
 * @param sorts - Array of sort entries (applied in order for multi-sort)
 * @returns Sorted array (new array, original is not modified)
 */
export function sortData<T>(
  data: T[],
  sorts: SortEntry<keyof T | string>[]
): T[] {
  if (sorts.length === 0) {
    return data;
  }

  return [...data].sort((a, b) => {
    for (const sort of sorts) {
      const aValue = getNestedValue(a, sort.field as string);
      const bValue = getNestedValue(b, sort.field as string);
      const result = compareValues(aValue, bValue, sort.direction);

      if (result !== 0) {
        return result;
      }
    }
    return 0;
  });
}

// ============================================================================
// Search Utilities
// ============================================================================

/**
 * Search an array of items based on a query string.
 *
 * @param data - Array of items to search
 * @param query - Search query string
 * @param fields - Fields to search in
 * @returns Filtered array matching the search query
 */
export function searchData<T>(
  data: T[],
  query: string,
  fields: (keyof T | string)[]
): T[] {
  if (!query || query.trim() === '' || fields.length === 0) {
    return data;
  }

  const normalizedQuery = query.toLowerCase().trim();

  return data.filter((item) =>
    fields.some((field) => {
      const value = getNestedValue(item, field as string);
      if (value === null || value === undefined) {
        return false;
      }
      return String(value).toLowerCase().includes(normalizedQuery);
    })
  );
}

// ============================================================================
// Pagination Utilities
// ============================================================================

/**
 * Paginate an array of items.
 *
 * @param data - Array of items to paginate
 * @param page - Page number (1-indexed)
 * @param pageSize - Number of items per page
 * @returns Paginated slice of the array
 */
export function paginateData<T>(
  data: T[],
  page: number,
  pageSize: number
): T[] {
  const startIndex = (page - 1) * pageSize;
  return data.slice(startIndex, startIndex + pageSize);
}

// ============================================================================
// Combined Transform
// ============================================================================

/**
 * Apply a full transformation pipeline to data.
 *
 * Order of operations:
 * 1. Search (if query provided)
 * 2. Filter (if filters provided)
 * 3. Sort (if sorts provided)
 * 4. Paginate (if pagination provided)
 *
 * @param options - Transform options
 * @returns Transformed result with data, totalCount, and totalPages
 */
export function transformData<T>(
  options: TransformOptions<T, keyof T | string>
): TransformResult<T> {
  let result = options.data;

  // 1. Search
  if (options.search && options.search.query) {
    result = searchData(result, options.search.query, options.search.fields);
  }

  // 2. Filter
  if (options.filters && options.filters.length > 0) {
    result = filterData(result, options.filters);
  }

  // 3. Sort
  if (options.sort && options.sort.length > 0) {
    result = sortData(result, options.sort);
  }

  // Calculate totals before pagination
  const totalCount = result.length;
  const pageSize = options.pagination?.pageSize ?? result.length;
  const totalPages = Math.max(1, Math.ceil(totalCount / pageSize));

  // 4. Paginate
  if (options.pagination) {
    result = paginateData(result, options.pagination.page, options.pagination.pageSize);
  }

  return {
    data: result,
    totalCount,
    totalPages,
  };
}

export default transformData;
