import type { Column, TDisplayFunction } from './types.js';

export function computeColumnsFromDisplayFunction<T>(
  displayFunction: TDisplayFunction<T>,
  data: T[]
): Column<T>[] {
  if (!data || data.length === 0) return [];
  const firstTransformedItem = displayFunction(data[0]);
  const keys: string[] = Object.keys(firstTransformedItem);
  return keys.map((key) => ({
    key,
    header: key,
    value: (row: T) => displayFunction(row)[key],
  }));
}

export function computeEffectiveColumns<T>(
  columns: Column<T>[] | undefined,
  augmentFromDisplayFunction: boolean,
  displayFunction: TDisplayFunction<T>,
  data: T[]
): Column<T>[] {
  const base = (columns || []).slice();
  if (!augmentFromDisplayFunction) return base;
  const fromDisplay = computeColumnsFromDisplayFunction(displayFunction, data);
  const existingKeys = new Set(base.map((c) => String(c.key)));
  for (const col of fromDisplay) {
    if (!existingKeys.has(String(col.key))) {
      base.push(col);
    }
  }
  return base;
}

export function getCellValue<T>(row: T, col: Column<T>, displayFunction?: TDisplayFunction<T>): any {
  return col.value ? col.value(row) : (row as any)[col.key as any];
}

export function getViewData<T>(
  data: T[],
  effectiveColumns: Column<T>[],
  sortKey?: string,
  sortDir?: 'asc' | 'desc' | null,
  filterText?: string,
  columnFilters?: Record<string, string>,
  filterMode: 'table' | 'data' = 'table',
  lucenePredicate?: (row: T) => boolean
): T[] {
  let arr = data.slice();
  const ft = (filterText || '').trim().toLowerCase();
  const cf = columnFilters || {};
  const cfKeys = Object.keys(cf).filter((k) => (cf[k] ?? '').trim().length > 0);
  if (ft || cfKeys.length > 0) {
    arr = arr.filter((row) => {
      // column filters (AND across columns)
      for (const k of cfKeys) {
        if (filterMode === 'data') {
          // raw object check for that key
          const val = (row as any)[k];
          const s = String(val ?? '').toLowerCase();
          const needle = String(cf[k]).toLowerCase();
          if (!s.includes(needle)) return false;
        } else {
          const col = effectiveColumns.find((c) => String(c.key) === k);
          if (!col || col.hidden || col.filterable === false) continue;
          const val = getCellValue(row, col);
          const s = String(val ?? '').toLowerCase();
          const needle = String(cf[k]).toLowerCase();
          if (!s.includes(needle)) return false;
        }
      }
      // global filter (OR across visible columns) or lucene predicate
      if (ft) {
        if (lucenePredicate) {
          if (!lucenePredicate(row)) return false;
          return true;
        }
        let any = false;
        if (filterMode === 'data') {
          for (const val of Object.values(row as any)) {
            const s = String(val ?? '').toLowerCase();
            if (s.includes(ft)) { any = true; break; }
          }
        } else {
          for (const col of effectiveColumns) {
            if (col.hidden) continue;
            const val = getCellValue(row, col);
            const s = String(val ?? '').toLowerCase();
            if (s.includes(ft)) { any = true; break; }
          }
        }
        if (!any) return false;
      }
      return true;
    });
  }
  if (!sortKey || !sortDir) return arr;
  const col = effectiveColumns.find((c) => String(c.key) === sortKey);
  if (!col) return arr;
  const dir = sortDir === 'asc' ? 1 : -1;
  arr.sort((a, b) => {
    const va = getCellValue(a, col);
    const vb = getCellValue(b, col);
    if (va == null && vb == null) return 0;
    if (va == null) return -1 * dir;
    if (vb == null) return 1 * dir;
    if (typeof va === 'number' && typeof vb === 'number') return (va - vb) * dir;
    const sa = String(va).toLowerCase();
    const sb = String(vb).toLowerCase();
    if (sa < sb) return -1 * dir;
    if (sa > sb) return 1 * dir;
    return 0;
  });
  return arr;
}
