import { useEffect, useMemo, useState } from "react";
import { usePagination } from "@edr/ui-common";

/**
 * Search + date-range + pagination over an already-fetched array.
 *
 * Client-side on purpose: the freight lists are hundreds of rows (largest table
 * is ~1.1k), so filtering in the browser avoids paginating ~20 API endpoints —
 * several of which sit on billing paths. If a list ever outgrows this (roughly
 * 5k rows, where the per-keystroke filter starts to feel slow), move that ONE
 * page to a server-side query; the component API here stays the same.
 *
 * Dates are `YYYY-MM-DD` strings, matching Mantine 9's date inputs. Comparing
 * them lexically keeps the range on calendar days and sidesteps timezone drift
 * entirely — a UTC timestamp is truncated to its date before the comparison.
 *
 * ponytail: linear scan per keystroke, no debounce — fine at this size; add
 * a debounce (or server-side filtering) if a list gets big enough to stutter.
 */
export interface ListControlsOptions<T> {
  /**
   * Fields matched against the search box. Constrained to real keys of the row
   * so a typo is a compile error rather than a filter that silently matches
   * nothing. For nested or derived values, pass `searchValue` instead.
   */
  searchKeys?: (keyof T)[];
  /**
   * Row's meaningful business date (arrival, invoice, dispatch…), which is what
   * staff actually filter by. Falls back to `createdAt` when the row has no
   * value for it, so a record is never silently invisible to a date range.
   */
  dateKey?: keyof T;
  /** Rows per page. */
  pageSize?: number;
  /** Custom search extractor when the value isn't a top-level field. */
  searchValue?: (row: T) => string;
}

const readField = (row: unknown, key: string): unknown =>
  row && typeof row === "object" ? (row as Record<string, unknown>)[key] : undefined;

/**
 * Reduce any stored date to its `YYYY-MM-DD` calendar day. ISO strings are cut
 * directly rather than parsed, so a timestamp is never shifted into the
 * previous/next day by the viewer's timezone.
 */
export const toDayString = (raw: unknown): string | null => {
  if (!raw) return null;
  if (raw instanceof Date) {
    return Number.isNaN(raw.getTime()) ? null : raw.toISOString().slice(0, 10);
  }
  const text = String(raw);
  if (/^\d{4}-\d{2}-\d{2}/.test(text)) return text.slice(0, 10);
  const parsed = new Date(text);
  return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString().slice(0, 10);
};

/**
 * Does a stored date fall inside an inclusive `YYYY-MM-DD` range? Exported for
 * lists that already own their filtering (e.g. FleetResourcePage, which folds
 * server-side filters and search together) so the range semantics — inclusive
 * ends, undated rows excluded — stay defined in exactly one place.
 */
export const matchesDayRange = (
  raw: unknown,
  dateFrom: string | null,
  dateTo: string | null,
): boolean => {
  if (!dateFrom && !dateTo) return true;
  const day = toDayString(raw);
  if (!day) return false;
  if (dateFrom && day < dateFrom) return false;
  if (dateTo && day > dateTo) return false;
  return true;
};

export const useListControls = <T,>(rows: T[], options: ListControlsOptions<T> = {}) => {
  const { searchKeys = [], dateKey, pageSize = 10, searchValue } = options;

  const [search, setSearch] = useState("");
  const [dateFrom, setDateFrom] = useState<string | null>(null);
  const [dateTo, setDateTo] = useState<string | null>(null);
  const { pagination, setPagination } = usePagination({ pageSize });

  const keys = searchKeys.map(String);
  const keySignature = keys.join("|");
  const dateKeyStr = dateKey ? String(dateKey) : undefined;

  const filteredRows = useMemo(() => {
    const term = search.trim().toLowerCase();
    if (!term && !dateFrom && !dateTo) return rows;

    return rows.filter((row) => {
      if (term) {
        const haystack = searchValue
          ? searchValue(row)
          : keys.map((key) => String(readField(row, key) ?? "")).join(" ");
        if (!haystack.toLowerCase().includes(term)) return false;
      }
      if (dateFrom || dateTo) {
        const raw = dateKeyStr
          ? (readField(row, dateKeyStr) ?? readField(row, "createdAt"))
          : null;
        if (!matchesDayRange(raw, dateFrom, dateTo)) return false;
      }
      return true;
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [rows, search, dateFrom, dateTo, keySignature, dateKeyStr, searchValue]);

  // Narrowing the result set can strand the user on a page that no longer
  // exists (filter to 3 rows while on page 5 → empty table). Snap back to the
  // first page whenever the filters change.
  useEffect(() => {
    setPagination((prev) => (prev.pageIndex === 0 ? prev : { ...prev, pageIndex: 0 }));
  }, [search, dateFrom, dateTo, setPagination]);

  const pageCount = Math.max(1, Math.ceil(filteredRows.length / pagination.pageSize));

  const pagedRows = useMemo(() => {
    const start = pagination.pageIndex * pagination.pageSize;
    return filteredRows.slice(start, start + pagination.pageSize);
  }, [filteredRows, pagination.pageIndex, pagination.pageSize]);

  const hasFilters = Boolean(search || dateFrom || dateTo);

  const reset = () => {
    setSearch("");
    setDateFrom(null);
    setDateTo(null);
  };

  return {
    search,
    setSearch,
    dateFrom,
    setDateFrom,
    dateTo,
    setDateTo,
    hasFilters,
    reset,
    filteredRows,
    pagedRows,
    pageCount,
    pagination,
    setPagination,
    totalCount: filteredRows.length,
    /** Spread straight onto <DataTable /> so every list paginates identically. */
    tableProps: {
      pagination: {
        pageIndex: pagination.pageIndex,
        pageSize: pagination.pageSize,
        pageCount,
        totalCount: filteredRows.length,
      },
      tableOptions: {
        manualPagination: true as const,
        pageCount,
        state: { pagination },
        onPaginationChange: setPagination,
      },
    },
  };
};
