"use client";

import * as React from "react";
import {
  type ColumnDef,
  type SortingState,
  type VisibilityState,
  type RowSelectionState,
  flexRender,
  getCoreRowModel,
  getSortedRowModel,
  getPaginationRowModel,
  getFilteredRowModel,
  useReactTable,
} from "@tanstack/react-table";
import { ArrowDown, ArrowUp, ArrowUpDown, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, Columns3, Download } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "./button";
import { SearchInput, NativeSelect } from "./input";
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuTrigger } from "./dropdown-menu";
import { EmptyState } from "./states";
import { Checkbox } from "./primitives";

export interface DataTableProps<T> {
  columns: ColumnDef<T, unknown>[];
  data: T[];
  searchKey?: string;
  searchPlaceholder?: string;
  /** Extra toolbar controls (filters) */
  toolbar?: React.ReactNode;
  emptyTitle?: string;
  emptyDescription?: string;
  emptyAction?: React.ReactNode;
  pageSize?: number;
  onRowClick?: (row: T) => void;
  rowHref?: (row: T) => string | undefined;
  /** Render for mobile: return a card for the row (falls back to table) */
  mobileCard?: (row: T) => React.ReactNode;
  selectable?: boolean;
  bulkActions?: (rows: T[], clear: () => void) => React.ReactNode;
  exportName?: string;
  exportRows?: (rows: T[]) => Record<string, unknown>[];
  dense?: boolean;
  className?: string;
  initialSorting?: SortingState;
  globalFilterFn?: (row: T, q: string) => boolean;
  stickyHeader?: boolean;
}

function toCsv(rows: Record<string, unknown>[]) {
  if (!rows.length) return "";
  const keys = Object.keys(rows[0]);
  const esc = (v: unknown) => {
    const s = v == null ? "" : String(v);
    return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
  };
  return [keys.join(","), ...rows.map((r) => keys.map((k) => esc(r[k])).join(","))].join("\n");
}

export function DataTable<T>({
  columns,
  data,
  searchKey,
  searchPlaceholder,
  toolbar,
  emptyTitle = "Nothing here yet",
  emptyDescription,
  emptyAction,
  pageSize = 20,
  onRowClick,
  rowHref,
  mobileCard,
  selectable,
  bulkActions,
  exportName,
  exportRows,
  dense,
  className,
  initialSorting = [],
  globalFilterFn,
  stickyHeader = true,
}: DataTableProps<T>) {
  const [sorting, setSorting] = React.useState<SortingState>(initialSorting);
  const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
  const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
  const [globalFilter, setGlobalFilter] = React.useState("");

  const cols = React.useMemo<ColumnDef<T, unknown>[]>(() => {
    if (!selectable) return columns;
    return [
      {
        id: "__select",
        header: ({ table }) => <Checkbox checked={table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && "indeterminate")} onCheckedChange={(v) => table.toggleAllPageRowsSelected(!!v)} aria-label="Select all" />,
        cell: ({ row }) => (
          <div onClick={(e) => e.stopPropagation()}>
            <Checkbox checked={row.getIsSelected()} onCheckedChange={(v) => row.toggleSelected(!!v)} aria-label="Select row" />
          </div>
        ),
        enableSorting: false,
        enableHiding: false,
        size: 32,
      },
      ...columns,
    ];
  }, [columns, selectable]);

  const table = useReactTable({
    data,
    columns: cols,
    state: { sorting, columnVisibility, rowSelection, globalFilter },
    onSortingChange: setSorting,
    onColumnVisibilityChange: setColumnVisibility,
    onRowSelectionChange: setRowSelection,
    onGlobalFilterChange: setGlobalFilter,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    globalFilterFn: globalFilterFn
      ? (row, _id, value) => globalFilterFn(row.original, String(value).toLowerCase())
      : (row, _id, value) => {
          const q = String(value).toLowerCase();
          if (searchKey) return String((row.original as Record<string, unknown>)[searchKey] ?? "").toLowerCase().includes(q);
          return JSON.stringify(row.original).toLowerCase().includes(q);
        },
    initialState: { pagination: { pageSize } },
    enableRowSelection: !!selectable,
  });

  const selectedRows = table.getFilteredSelectedRowModel().rows.map((r) => r.original);
  const total = table.getFilteredRowModel().rows.length;
  const { pageIndex, pageSize: ps } = table.getState().pagination;
  const from = total === 0 ? 0 : pageIndex * ps + 1;
  const to = Math.min(total, (pageIndex + 1) * ps);

  function exportCsv() {
    const rows = exportRows ? exportRows(table.getFilteredRowModel().rows.map((r) => r.original)) : (table.getFilteredRowModel().rows.map((r) => r.original) as Record<string, unknown>[]);
    const blob = new Blob([toCsv(rows)], { type: "text/csv;charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `${exportName ?? "export"}.csv`;
    a.click();
    URL.revokeObjectURL(url);
  }

  const hasToolbar = searchKey !== undefined || globalFilterFn || toolbar || exportName;

  return (
    <div className={cn("flex flex-col gap-3", className)}>
      {hasToolbar ? (
        <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
          <div className="flex min-w-0 flex-1 flex-col gap-2 md:flex-row md:flex-wrap md:items-center">
            {searchKey !== undefined || globalFilterFn ? <SearchInput value={globalFilter} onChange={setGlobalFilter} placeholder={searchPlaceholder} className="w-full md:w-64" /> : null}
            {toolbar ? <div className="-mx-3 flex items-center gap-2 overflow-x-auto px-3 pb-0.5 scrollbar-none md:mx-0 md:contents md:px-0 [&>*]:shrink-0">{toolbar}</div> : null}
          </div>
          <div className="flex items-center gap-2">
            {selectedRows.length > 0 && bulkActions ? (
              <div className="flex items-center gap-2 rounded-md border border-primary/30 bg-primary/5 px-2 py-1 text-xs">
                <span className="font-medium text-primary">{selectedRows.length} selected</span>
                {bulkActions(selectedRows, () => setRowSelection({}))}
              </div>
            ) : null}
            {exportName ? (
              <Button variant="ghost" size="sm" onClick={exportCsv} className="hidden sm:inline-flex">
                <Download /> Export
              </Button>
            ) : null}
            <DropdownMenu>
              <DropdownMenuTrigger asChild>
                <Button variant="ghost" size="iconSm" className="hidden sm:inline-flex" aria-label="Columns">
                  <Columns3 />
                </Button>
              </DropdownMenuTrigger>
              <DropdownMenuContent align="end" className="w-48">
                <DropdownMenuLabel>Columns</DropdownMenuLabel>
                {table
                  .getAllColumns()
                  .filter((c) => c.getCanHide())
                  .map((c) => (
                    <DropdownMenuCheckboxItem key={c.id} checked={c.getIsVisible()} onCheckedChange={(v) => c.toggleVisibility(!!v)} className="capitalize">
                      {typeof c.columnDef.header === "string" ? c.columnDef.header : c.id.replace(/_/g, " ")}
                    </DropdownMenuCheckboxItem>
                  ))}
              </DropdownMenuContent>
            </DropdownMenu>
          </div>
        </div>
      ) : null}

      {total === 0 ? (
        <div className="surface">
          <EmptyState title={emptyTitle} description={emptyDescription ?? (globalFilter ? "Try a different search or clear the filters." : undefined)} action={emptyAction} />
        </div>
      ) : (
        <>
          {/* Phone layout: a dedicated card when the table provides one, otherwise an automatic key/value card built from the visible columns */}
          <div className="flex flex-col gap-2 md:hidden stagger">
            {table.getRowModel().rows.map((row) => {
              const href = rowHref?.(row.original);
              if (mobileCard)
                return (
                  <div key={row.id} onClick={() => onRowClick?.(row.original)}>
                    {mobileCard(row.original)}
                  </div>
                );
              const cells = row.getVisibleCells().filter((c) => c.column.id !== "__select");
              const [first, ...rest] = cells;
              const inner = (
                <>
                  <div className="text-sm font-medium text-fg">{flexRender(first.column.columnDef.cell, first.getContext())}</div>
                  {rest.length ? (
                    <dl className="mt-2.5 grid grid-cols-2 gap-x-3 gap-y-2 text-xs">
                      {rest.map((cell) => (
                        <div key={cell.id} className="min-w-0">
                          <dt className="eyebrow truncate">{typeof cell.column.columnDef.header === "string" ? cell.column.columnDef.header : cell.column.id.replace(/_/g, " ")}</dt>
                          <dd className="mt-0.5 truncate text-fg">{flexRender(cell.column.columnDef.cell, cell.getContext())}</dd>
                        </div>
                      ))}
                    </dl>
                  ) : null}
                </>
              );
              const cls = "block w-full rounded-xl border border-border bg-surface p-3.5 text-left shadow-xs hairline-top pressable";
              return href ? (
                <a key={row.id} href={href} className={cls}>
                  {inner}
                </a>
              ) : (
                <div key={row.id} onClick={() => onRowClick?.(row.original)} className={cn(cls, onRowClick && "cursor-pointer")}>
                  {inner}
                </div>
              );
            })}
          </div>

          <div className="surface hidden overflow-hidden md:block">
            <div className="overflow-x-auto scrollbar-thin">
              <table className="w-full text-sm">
                <thead className={cn("table-head-glass text-left", stickyHeader && "sticky top-0 z-[1]")}>
                  {table.getHeaderGroups().map((hg) => (
                    <tr key={hg.id} className="border-b border-border">
                      {hg.headers.map((h) => {
                        const canSort = h.column.getCanSort();
                        const sorted = h.column.getIsSorted();
                        const align = (h.column.columnDef.meta as { align?: string } | undefined)?.align;
                        return (
                          <th key={h.id} style={{ width: h.getSize() !== 150 ? h.getSize() : undefined }} className={cn("px-3 py-2.5 text-2xs font-semibold uppercase tracking-wider text-fg-muted whitespace-nowrap first:pl-4 last:pr-4", align === "right" && "text-right", align === "center" && "text-center")}>
                            {h.isPlaceholder ? null : canSort ? (
                              <button type="button" onClick={h.column.getToggleSortingHandler()} className={cn("inline-flex items-center gap-1 hover:text-fg transition-colors", sorted && "text-fg")}>
                                {flexRender(h.column.columnDef.header, h.getContext())}
                                {sorted === "asc" ? <ArrowUp className="size-3" /> : sorted === "desc" ? <ArrowDown className="size-3" /> : <ArrowUpDown className="size-3 opacity-40" />}
                              </button>
                            ) : (
                              flexRender(h.column.columnDef.header, h.getContext())
                            )}
                          </th>
                        );
                      })}
                    </tr>
                  ))}
                </thead>
                <tbody>
                  {table.getRowModel().rows.map((row) => {
                    const href = rowHref?.(row.original);
                    const clickable = !!onRowClick || !!href;
                    return (
                      <tr
                        key={row.id}
                        data-state={row.getIsSelected() ? "selected" : undefined}
                        onClick={() => {
                          if (onRowClick) onRowClick(row.original);
                          else if (href) window.location.assign(href);
                        }}
                        className={cn("border-b border-border last:border-0 transition-colors data-[state=selected]:bg-primary/5", clickable && "cursor-pointer hover:bg-primary/[0.035]")}
                      >
                        {row.getVisibleCells().map((cell) => {
                          const align = (cell.column.columnDef.meta as { align?: string } | undefined)?.align;
                          return (
                            <td key={cell.id} className={cn("px-3 align-middle first:pl-4 last:pr-4", dense ? "py-2" : "py-3", align === "right" && "text-right", align === "center" && "text-center")}>
                              {flexRender(cell.column.columnDef.cell, cell.getContext())}
                            </td>
                          );
                        })}
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            </div>
          </div>

          {/* Pagination */}
          {total > ps || table.getPageCount() > 1 ? (
            <div className="flex flex-col-reverse items-center justify-between gap-2 sm:flex-row">
              <p className="text-xs text-fg-muted tabular">
                {from}–{to} of {total}
              </p>
              <div className="flex items-center gap-1.5">
                <NativeSelect value={ps} onChange={(e) => table.setPageSize(Number(e.target.value))} className="h-8 w-[76px] text-xs" aria-label="Rows per page">
                  {[10, 20, 50, 100].map((n) => (
                    <option key={n} value={n}>
                      {n} / p
                    </option>
                  ))}
                </NativeSelect>
                <Button variant="ghost" size="iconSm" onClick={() => table.setPageIndex(0)} disabled={!table.getCanPreviousPage()} aria-label="First page">
                  <ChevronsLeft />
                </Button>
                <Button variant="ghost" size="iconSm" onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()} aria-label="Previous page">
                  <ChevronLeft />
                </Button>
                <span className="px-1 text-xs tabular text-fg-muted">
                  {pageIndex + 1} / {table.getPageCount()}
                </span>
                <Button variant="ghost" size="iconSm" onClick={() => table.nextPage()} disabled={!table.getCanNextPage()} aria-label="Next page">
                  <ChevronRight />
                </Button>
                <Button variant="ghost" size="iconSm" onClick={() => table.setPageIndex(table.getPageCount() - 1)} disabled={!table.getCanNextPage()} aria-label="Last page">
                  <ChevronsRight />
                </Button>
              </div>
            </div>
          ) : null}
        </>
      )}
    </div>
  );
}

/** Simple filter select used in table toolbars. */
export function FilterSelect({ value, onChange, options, placeholder, className }: { value: string; onChange: (v: string) => void; options: { value: string; label: string }[]; placeholder: string; className?: string }) {
  return (
    <NativeSelect value={value} onChange={(e) => onChange(e.target.value)} className={cn("h-9 w-auto min-w-[130px] text-xs", value && "border-primary/40 text-fg", className)}>
      <option value="">{placeholder}</option>
      {options.map((o) => (
        <option key={o.value} value={o.value}>
          {o.label}
        </option>
      ))}
    </NativeSelect>
  );
}
