"use client";

import { useEffect, useMemo, useState, type ReactNode } from "react";
import { ArrowUpDown, ArrowUp, ArrowDown, Search, Download, ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "./cn";
import { EmptyState, Skeleton } from "./ui";

export type Column<T> = {
  key: string;
  header: string;
  render?: (row: T) => ReactNode;
  accessor?: (row: T) => string | number;
  sortable?: boolean;
  align?: "left" | "right" | "center";
  className?: string;
};

type Props<T> = {
  columns: Column<T>[];
  rows: T[];
  rowKey: (row: T) => string;
  loading?: boolean;
  searchable?: boolean;
  searchPlaceholder?: string;
  filters?: ReactNode;
  exportName?: string;
  pageSize?: number;
  onRowClick?: (row: T) => void;
  empty?: { title: string; description?: string; icon?: ReactNode; action?: ReactNode };
  selectable?: boolean;
  onSelectionChange?: (rows: T[]) => void;
  /** Change this value to clear the current selection (e.g. after a bulk action). */
  selectionResetKey?: unknown;
};

function val<T>(col: Column<T>, row: T): string | number {
  if (col.accessor) return col.accessor(row);
  const v = (row as Record<string, unknown>)[col.key];
  return typeof v === "number" ? v : String(v ?? "");
}

export function DataTable<T>({
  columns,
  rows,
  rowKey,
  loading,
  searchable = true,
  searchPlaceholder = "Search…",
  filters,
  exportName,
  pageSize = 10,
  onRowClick,
  empty,
  selectable,
  onSelectionChange,
  selectionResetKey,
}: Props<T>) {
  const [q, setQ] = useState("");
  const [sort, setSort] = useState<{ key: string; dir: "asc" | "desc" } | null>(null);
  const [page, setPage] = useState(1);
  const [selected, setSelected] = useState<Set<string>>(new Set());

  useEffect(() => {
    setSelected(new Set());
  }, [selectionResetKey]);

  const emit = (next: Set<string>) => {
    setSelected(next);
    onSelectionChange?.(rows.filter((r) => next.has(rowKey(r))));
  };

  const filtered = useMemo(() => {
    let data = rows;
    if (q.trim()) {
      const needle = q.toLowerCase();
      data = data.filter((r) => columns.some((c) => String(val(c, r)).toLowerCase().includes(needle)));
    }
    if (sort) {
      const col = columns.find((c) => c.key === sort.key);
      if (col) {
        data = [...data].sort((a, b) => {
          const av = val(col, a);
          const bv = val(col, b);
          const cmp = typeof av === "number" && typeof bv === "number" ? av - bv : String(av).localeCompare(String(bv));
          return sort.dir === "asc" ? cmp : -cmp;
        });
      }
    }
    return data;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [rows, q, sort, columns]);

  const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));
  const current = Math.min(page, totalPages);
  const pageRows = filtered.slice((current - 1) * pageSize, current * pageSize);
  const totalCols = columns.length + (selectable ? 1 : 0);
  const allSelected = filtered.length > 0 && filtered.every((r) => selected.has(rowKey(r)));

  const toggleSort = (key: string) => {
    setSort((s) => (s?.key === key ? (s.dir === "asc" ? { key, dir: "desc" } : null) : { key, dir: "asc" }));
  };

  const exportCsv = () => {
    const head = columns.map((c) => `"${c.header}"`).join(",");
    const body = filtered.map((r) => columns.map((c) => `"${String(val(c, r)).replace(/"/g, '""')}"`).join(",")).join("\n");
    const blob = new Blob([head + "\n" + body], { type: "text/csv" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `${exportName}.csv`;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div className="rounded-xl border border-gray-200 bg-white shadow-sm">
      {(searchable || filters || exportName) && (
        <div className="flex flex-col gap-3 border-b border-gray-100 px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
          <div className="flex flex-1 flex-wrap items-center gap-2">
            {searchable && (
              <div className="relative">
                <Search className="pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400" />
                <input
                  value={q}
                  onChange={(e) => {
                    setQ(e.target.value);
                    setPage(1);
                  }}
                  placeholder={searchPlaceholder}
                  className="h-9 w-full rounded-lg border border-gray-300 bg-white pl-8 pr-3 text-sm outline-none focus:ring-2 focus:ring-[var(--brand)]/25 sm:w-64"
                />
              </div>
            )}
            {filters}
          </div>
          {exportName && filtered.length > 0 && (
            <button onClick={exportCsv} className="inline-flex h-9 items-center gap-1.5 rounded-lg border border-gray-300 px-3 text-xs font-medium text-gray-600 hover:bg-gray-50">
              <Download className="h-4 w-4" /> Export CSV
            </button>
          )}
        </div>
      )}

      <div className="overflow-x-auto">
        <table className="w-full min-w-[640px] text-left text-sm">
          <thead className="bg-gray-50 text-xs uppercase tracking-wide text-gray-500">
            <tr>
              {selectable && (
                <th className="w-10 px-4 py-2.5">
                  <input
                    type="checkbox"
                    aria-label="Select all"
                    checked={allSelected}
                    onChange={(e) => {
                      const next = new Set(selected);
                      filtered.forEach((r) => (e.target.checked ? next.add(rowKey(r)) : next.delete(rowKey(r))));
                      emit(next);
                    }}
                    className="h-4 w-4 rounded border-gray-300 text-[var(--brand)]"
                  />
                </th>
              )}
              {columns.map((c) => (
                <th key={c.key} className={cn("whitespace-nowrap px-4 py-2.5 font-medium", c.align === "right" && "text-right", c.align === "center" && "text-center")}>
                  {c.sortable ? (
                    <button onClick={() => toggleSort(c.key)} className="inline-flex items-center gap-1 hover:text-gray-700">
                      {c.header}
                      {sort?.key === c.key ? (sort.dir === "asc" ? <ArrowUp className="h-3 w-3" /> : <ArrowDown className="h-3 w-3" />) : <ArrowUpDown className="h-3 w-3 opacity-40" />}
                    </button>
                  ) : (
                    c.header
                  )}
                </th>
              ))}
            </tr>
          </thead>
          <tbody className="divide-y divide-gray-50">
            {loading ? (
              Array.from({ length: 5 }).map((_, i) => (
                <tr key={i}>
                  {selectable && <td className="px-4 py-3" />}
                  {columns.map((c) => (
                    <td key={c.key} className="px-4 py-3">
                      <Skeleton className="h-4 w-full max-w-[120px]" />
                    </td>
                  ))}
                </tr>
              ))
            ) : pageRows.length === 0 ? (
              <tr>
                <td colSpan={totalCols}>
                  <EmptyState icon={empty?.icon} title={empty?.title ?? "No results"} description={q ? "Try a different search." : empty?.description} action={!q ? empty?.action : undefined} />
                </td>
              </tr>
            ) : (
              pageRows.map((r) => {
                const key = rowKey(r);
                return (
                  <tr key={key} onClick={onRowClick ? () => onRowClick(r) : undefined} className={cn("transition-colors hover:bg-gray-50/70", onRowClick && "cursor-pointer", selected.has(key) && "bg-[var(--brand-50)]/50")}>
                    {selectable && (
                      <td className="px-4" onClick={(e) => e.stopPropagation()}>
                        <input
                          type="checkbox"
                          aria-label="Select row"
                          checked={selected.has(key)}
                          onChange={() => {
                            const next = new Set(selected);
                            next.has(key) ? next.delete(key) : next.add(key);
                            emit(next);
                          }}
                          className="h-4 w-4 rounded border-gray-300 text-[var(--brand)]"
                        />
                      </td>
                    )}
                    {columns.map((c) => (
                      <td key={c.key} className={cn("whitespace-nowrap px-4 py-3 text-gray-700", c.align === "right" && "text-right", c.align === "center" && "text-center", c.className)}>
                        {c.render ? c.render(r) : String(val(c, r))}
                      </td>
                    ))}
                  </tr>
                );
              })
            )}
          </tbody>
        </table>
      </div>

      {!loading && filtered.length > pageSize && (
        <div className="flex items-center justify-between border-t border-gray-100 px-4 py-3 text-xs text-gray-500">
          <span>
            {(current - 1) * pageSize + 1}–{Math.min(current * pageSize, filtered.length)} of {filtered.length}
          </span>
          <div className="flex items-center gap-1">
            <button disabled={current === 1} onClick={() => setPage(current - 1)} className="inline-flex h-8 items-center gap-1 rounded-lg border border-gray-300 px-2 disabled:opacity-40 hover:bg-gray-50">
              <ChevronLeft className="h-4 w-4" /> Prev
            </button>
            <span className="px-2">
              Page {current} / {totalPages}
            </span>
            <button disabled={current === totalPages} onClick={() => setPage(current + 1)} className="inline-flex h-8 items-center gap-1 rounded-lg border border-gray-300 px-2 disabled:opacity-40 hover:bg-gray-50">
              Next <ChevronRight className="h-4 w-4" />
            </button>
          </div>
        </div>
      )}
    </div>
  );
}
