import { useState, useMemo, useEffect, useRef } from "react";

export type SortDir = "asc" | "desc" | null;

export interface TableSortState<F extends string> {
  sorted: never[]; // overridden per call
  sortField: F | null;
  sortDir: SortDir;
  toggle: (field: F, getValue?: (row: never) => string | number | null | undefined) => void;
  reset: () => void;
}

export interface UseTableSortOptions {
  /** Initial sort field loaded from persisted preferences. */
  initialField?: string | null;
  /** Initial sort direction loaded from persisted preferences. */
  initialDir?: "asc" | "desc" | null;
  /**
   * Called whenever the sort state changes (after the first render).
   * Use this to persist the new sort state.
   */
  onSortChange?: (field: string | null, dir: SortDir) => void;
}

function compareValues(a: unknown, b: unknown, dir: "asc" | "desc"): number {
  // Nulls last regardless of direction
  if (a == null && b == null) return 0;
  if (a == null) return 1;
  if (b == null) return -1;

  let result: number;
  if (typeof a === "string" && typeof b === "string") {
    result = a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" });
  } else if (typeof a === "number" && typeof b === "number") {
    result = a - b;
  } else {
    result = String(a).localeCompare(String(b));
  }
  return dir === "asc" ? result : -result;
}

export function useTableSort<T extends object, F extends string = string>(
  rows: T[],
  options?: UseTableSortOptions,
) {
  const [sortField, setSortField] = useState<F | null>(
    (options?.initialField ?? null) as F | null,
  );
  const [sortDir, setSortDir] = useState<SortDir>(options?.initialDir ?? null);
  // Store custom getValue functions keyed by field
  const [getters, setGetters] = useState<Partial<Record<F, (row: T) => unknown>>>({});

  // Keep onSortChange in a ref so the effect doesn't need it as a dependency
  const onSortChangeRef = useRef(options?.onSortChange);
  onSortChangeRef.current = options?.onSortChange;

  // Skip the initial render — only fire onSortChange for user-driven changes
  const isFirstRender = useRef(true);
  useEffect(() => {
    if (isFirstRender.current) {
      isFirstRender.current = false;
      return;
    }
    onSortChangeRef.current?.(sortField, sortDir);
  }, [sortField, sortDir]);

  function toggle(field: F, getValue?: (row: T) => unknown) {
    if (getValue) {
      setGetters((prev) => ({ ...prev, [field]: getValue }));
    }
    setSortField((prev) => {
      if (prev !== field) {
        setSortDir("asc");
        return field;
      }
      // Cycle: asc → desc → null
      setSortDir((d) => {
        if (d === "asc") return "desc";
        if (d === "desc") return null;
        return "asc";
      });
      return prev;
    });
  }

  function reset() {
    setSortField(null);
    setSortDir(null);
  }

  const sorted = useMemo((): T[] => {
    if (!sortField || !sortDir) return rows;
    const getter = getters[sortField] ?? ((row: T) => row[sortField as unknown as keyof T]);
    return [...rows].sort((a, b) => compareValues(getter(a), getter(b), sortDir));
  }, [rows, sortField, sortDir, getters]);

  return { sorted, sortField, sortDir, toggle, reset };
}
