export type SortDirection = "asc" | "desc";

export type SortConfig<Key extends string = string> = {
  key: Key;
  direction: SortDirection;
};

export type SortableValue = string | number | boolean | Date | null | undefined;

export type SortResolver<T> = (item: T) => SortableValue;

export type SortResolverMap<T, Key extends string = string> = Partial<
  Record<Key, SortResolver<T>>
>;

function normalizeSortableValue(value: SortableValue) {
  if (value instanceof Date) {
    return value.getTime();
  }
  if (typeof value === "string") {
    return value.toLocaleLowerCase();
  }
  if (typeof value === "boolean") {
    return value ? 1 : 0;
  }
  return value;
}

export function compareNullableValues(
  left: SortableValue,
  right: SortableValue,
  direction: SortDirection,
) {
  if (left === right) {
    return 0;
  }

  if (left === null || left === undefined) {
    return 1;
  }

  if (right === null || right === undefined) {
    return -1;
  }

  const normalizedLeft = normalizeSortableValue(left);
  const normalizedRight = normalizeSortableValue(right);

  if (normalizedLeft === normalizedRight) {
    return 0;
  }

  if (normalizedLeft === null || normalizedLeft === undefined) {
    return 1;
  }

  if (normalizedRight === null || normalizedRight === undefined) {
    return -1;
  }

  const comparison = normalizedLeft < normalizedRight ? -1 : 1;
  return direction === "asc" ? comparison : -comparison;
}

export function toggleSort<Key extends string>(
  current: SortConfig<Key> | null,
  key: Key,
): SortConfig<Key> {
  if (current?.key === key && current.direction === "asc") {
    return { key, direction: "desc" };
  }

  return { key, direction: "asc" };
}

export function sortItems<T, Key extends string = string>(
  items: T[],
  sortConfig: SortConfig<Key> | null,
  resolverMap: SortResolverMap<T, Key> = {},
) {
  if (!sortConfig) {
    return [...items];
  }

  const resolveValue =
    resolverMap[sortConfig.key] ??
    ((item: T) =>
      (item as Record<string, SortableValue>)[sortConfig.key] ?? null);

  return [...items].sort((left, right) =>
    compareNullableValues(
      resolveValue(left),
      resolveValue(right),
      sortConfig.direction,
    ),
  );
}
