/* DataTable — paginated, sortable, optionally expandable list table.
 *
 * Backed by @tanstack/react-table v8 in **manual** mode: the parent owns
 * data fetching, sort + search dispatch, and the page/pageSize state.
 * The table itself just renders what it's given and emits user-input
 * callbacks.
 *
 * External API is intentionally stable from the previous custom
 * implementation:
 *
 *   <DataTable
 *     columns={[{ key, label, sortable?, render? }]}
 *     data={rows}
 *     totalCount={n}
 *     page={1} pageSize={25}
 *     onPageChange={fn}
 *     onSort={(col, dir) => …}      // optional
 *     onSearch={(q) => …}           // optional
 *     onRowClick={(row) => …}       // optional
 *     isLoading={false}             // optional
 *
 *     // NEW since the TanStack rewrite:
 *     getRowCanExpand={(row) => boolean}   // optional, default false
 *     renderSubRow={(row) => <…>}          // optional; required when expandable
 *   />
 *
 * The Column shape is the same as before (key/label/sortable/render).
 * Internally we translate to ColumnDef so existing consumers keep working
 * without code changes. */

'use client';

import { useMemo, useState } from 'react';
import {
  type ColumnDef,
  type ExpandedState,
  type Row,
  type SortingState,
  flexRender,
  getCoreRowModel,
  getExpandedRowModel,
  useReactTable,
} from '@tanstack/react-table';
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Skeleton } from '@/components/ui/skeleton';
import { EmptyState } from '@/components/ui/empty-state';
import {
  ArrowDown,
  ArrowUp,
  ArrowUpDown,
  ChevronDown,
  ChevronLeft,
  ChevronRight,
  ChevronsLeft,
  ChevronsRight,
  Download,
  Loader2,
  Search,
} from 'lucide-react';
import { cn } from '@/lib/utils';

export interface Column<TData = any> {
  key: string;
  label: string;
  sortable?: boolean;
  render?: (value: any, row: TData) => React.ReactNode;
}

export interface DataTableProps<TData = any> {
  columns: Column<TData>[];
  data: TData[];
  totalCount: number;
  page: number;
  pageSize: number;
  onPageChange: (page: number) => void;
  onSort?: (column: string, direction: 'asc' | 'desc') => void;
  onSearch?: (query: string) => void;
  onRowClick?: (row: TData) => void;
  isLoading?: boolean;
  /** Per-row gate for expansion. Return true to enable a chevron toggle. */
  getRowCanExpand?: (row: TData) => boolean;
  /** Renders the expanded sub-row body when a row is open. */
  renderSubRow?: (row: TData) => React.ReactNode;
  /** Empty-state slot. Defaults to a neutral "No results" message. */
  emptyTitle?: string;
  emptyDescription?: string;
  /** Pin the first column when the table scrolls horizontally. Default true. */
  stickyFirstColumn?: boolean;
  /** When true, render an "Export CSV" button. Exports the current page only
   *  unless `onExportAll` is provided (which receives all records server-side). */
  exportable?: boolean;
  /** Filename prefix for the CSV download (e.g. "tickets" → "tickets-2026-05-03.csv"). */
  exportFilename?: string;
  /** Optional async callback for full-result export — called instead of building
   *  the CSV from `data`.  Should resolve with the full row set. */
  onExportAll?: () => Promise<TData[]>;
}

export default function DataTable<TData = any>({
  columns,
  data,
  totalCount,
  page,
  pageSize,
  onPageChange,
  onSort,
  onSearch,
  onRowClick,
  isLoading = false,
  getRowCanExpand,
  renderSubRow,
  emptyTitle = 'No data found',
  emptyDescription = 'Try adjusting your search or filters.',
  stickyFirstColumn = true,
  exportable = false,
  exportFilename = 'export',
  onExportAll,
}: DataTableProps<TData>) {
  const [searchQuery, setSearchQuery] = useState('');
  const [sorting, setSorting] = useState<SortingState>([]);
  const [expanded, setExpanded] = useState<ExpandedState>({});

  const expandable = !!renderSubRow;
  const totalPages = Math.max(1, Math.ceil(totalCount / pageSize));

  // Translate the legacy Column shape into TanStack ColumnDef.
  const tanstackColumns = useMemo<ColumnDef<TData>[]>(() => {
    const cols: ColumnDef<TData>[] = [];

    // Lead expansion column when expansion is enabled.
    if (expandable) {
      cols.push({
        id: '__expand',
        header: () => null,
        cell: ({ row }) =>
          row.getCanExpand() ? (
            <button
              onClick={(e) => {
                e.stopPropagation();
                row.toggleExpanded();
              }}
              aria-label={row.getIsExpanded() ? 'Collapse row' : 'Expand row'}
              className="inline-flex h-6 w-6 items-center justify-center rounded-sm hover:bg-muted/60"
            >
              {row.getIsExpanded() ? (
                <ChevronDown className="h-3.5 w-3.5 text-muted-foreground" />
              ) : (
                <ChevronRight className="h-3.5 w-3.5 text-muted-foreground" />
              )}
            </button>
          ) : null,
        size: 32,
      });
    }

    for (const c of columns) {
      cols.push({
        id: c.key,
        accessorKey: c.key,
        enableSorting: !!c.sortable,
        header: c.label,
        cell: ({ row, getValue }) =>
          c.render ? c.render(getValue(), row.original) : (getValue() as React.ReactNode),
      });
    }

    return cols;
  }, [columns, expandable]);

  const table = useReactTable<TData>({
    data,
    columns: tanstackColumns,
    state: { sorting, expanded },
    onSortingChange: (updater) => {
      const next = typeof updater === 'function' ? updater(sorting) : updater;
      setSorting(next);
      // Defer to caller for actual data fetch.
      if (onSort && next.length > 0) {
        onSort(next[0].id, next[0].desc ? 'desc' : 'asc');
      }
    },
    onExpandedChange: setExpanded,
    getRowCanExpand: getRowCanExpand
      ? (row) => getRowCanExpand(row.original)
      : () => expandable,
    getCoreRowModel: getCoreRowModel(),
    getExpandedRowModel: getExpandedRowModel(),
    manualPagination: true,
    manualSorting: true,
    pageCount: totalPages,
  });

  const handleSearch = () => {
    onSearch?.(searchQuery);
  };

  const [exporting, setExporting] = useState(false);
  async function handleExport() {
    setExporting(true);
    try {
      const rows = onExportAll ? await onExportAll() : data;
      const csv = toCsv(columns, rows);
      const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
      const url = URL.createObjectURL(blob);
      const today = new Date().toISOString().slice(0, 10);
      const a = document.createElement('a');
      a.href = url;
      a.download = `${exportFilename}-${today}.csv`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    } finally {
      setExporting(false);
    }
  }

  return (
    <div className="space-y-4">
      {(onSearch || exportable) && (
        <div className="flex gap-2">
          {onSearch && (
            <>
              <div className="relative flex-1">
                <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
                <Input
                  placeholder="Search…"
                  value={searchQuery}
                  onChange={(e) => setSearchQuery(e.target.value)}
                  onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
                  className="pl-10"
                />
              </div>
              <Button onClick={handleSearch} disabled={isLoading}>
                {isLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Search className="w-4 h-4" />}
                <span className="ml-2">Search</span>
              </Button>
            </>
          )}
          {exportable && (
            <Button variant="outline" onClick={handleExport} disabled={exporting || data.length === 0} className="ml-auto">
              {exporting ? (
                <Loader2 className="w-4 h-4 animate-spin" />
              ) : (
                <Download className="w-4 h-4" />
              )}
              <span className="ml-2">Export CSV</span>
            </Button>
          )}
        </div>
      )}

      <div className="border rounded-md overflow-hidden bg-card">
        <Table stickyFirstColumn={stickyFirstColumn}>
          <TableHeader>
            {table.getHeaderGroups().map((headerGroup) => (
              <TableRow key={headerGroup.id} className="bg-muted/50 hover:bg-muted/50">
                {headerGroup.headers.map((header) => {
                  const sortable = header.column.getCanSort();
                  const sortDir = header.column.getIsSorted();
                  return (
                    <TableHead
                      key={header.id}
                      style={header.column.columnDef.size ? { width: header.column.columnDef.size } : undefined}
                      className="font-semibold"
                    >
                      {header.isPlaceholder ? null : sortable ? (
                        <Button
                          variant="ghost"
                          size="sm"
                          onClick={header.column.getToggleSortingHandler()}
                          className="h-8 -ml-3"
                        >
                          {flexRender(header.column.columnDef.header, header.getContext())}
                          <SortIcon dir={sortDir === 'asc' ? 'asc' : sortDir === 'desc' ? 'desc' : null} />
                        </Button>
                      ) : (
                        flexRender(header.column.columnDef.header, header.getContext())
                      )}
                    </TableHead>
                  );
                })}
              </TableRow>
            ))}
          </TableHeader>

          <TableBody>
            {isLoading ? (
              renderLoadingRows(table)
            ) : table.getRowModel().rows.length === 0 ? (
              <TableRow>
                <TableCell colSpan={tanstackColumns.length} className="py-8">
                  <EmptyState icon={Search} title={emptyTitle} description={emptyDescription} size="sm" />
                </TableCell>
              </TableRow>
            ) : (
              table.getRowModel().rows.map((row) => (
                <ExpandableRow
                  key={row.id}
                  row={row}
                  onRowClick={onRowClick}
                  renderSubRow={renderSubRow}
                  colSpan={tanstackColumns.length}
                />
              ))
            )}
          </TableBody>
        </Table>
      </div>

      <div className="flex flex-col sm:flex-row items-center justify-between gap-4 px-2">
        <div className="text-sm text-muted-foreground">
          Showing <span className="font-medium text-foreground num">
            {totalCount === 0 ? 0 : Math.min((page - 1) * pageSize + 1, totalCount)}
          </span>{' '}
          to <span className="font-medium text-foreground num">
            {Math.min(page * pageSize, totalCount)}
          </span>{' '}
          of <span className="font-medium text-foreground num">{totalCount}</span> results
        </div>
        <div className="flex items-center gap-1">
          <Button
            variant="outline"
            size="icon"
            onClick={() => onPageChange(1)}
            disabled={page <= 1 || isLoading}
            className="h-8 w-8"
            aria-label="First page"
          >
            <ChevronsLeft className="w-4 h-4" />
          </Button>
          <Button
            variant="outline"
            size="icon"
            onClick={() => onPageChange(page - 1)}
            disabled={page <= 1 || isLoading}
            className="h-8 w-8"
            aria-label="Previous page"
          >
            <ChevronLeft className="w-4 h-4" />
          </Button>
          <span className="px-3 text-sm font-medium num">
            Page {page} of {totalPages}
          </span>
          <Button
            variant="outline"
            size="icon"
            onClick={() => onPageChange(page + 1)}
            disabled={page >= totalPages || isLoading}
            className="h-8 w-8"
            aria-label="Next page"
          >
            <ChevronRight className="w-4 h-4" />
          </Button>
          <Button
            variant="outline"
            size="icon"
            onClick={() => onPageChange(totalPages)}
            disabled={page >= totalPages || isLoading}
            className="h-8 w-8"
            aria-label="Last page"
          >
            <ChevronsRight className="w-4 h-4" />
          </Button>
        </div>
      </div>
    </div>
  );
}

function ExpandableRow<TData>({
  row,
  onRowClick,
  renderSubRow,
  colSpan,
}: {
  row: Row<TData>;
  onRowClick?: (row: TData) => void;
  renderSubRow?: (row: TData) => React.ReactNode;
  colSpan: number;
}) {
  const clickable = !!onRowClick;
  return (
    <>
      <TableRow
        className={cn(clickable && 'cursor-pointer')}
        onClick={() => onRowClick?.(row.original)}
      >
        {row.getVisibleCells().map((cell) => (
          <TableCell key={cell.id}>
            {flexRender(cell.column.columnDef.cell, cell.getContext())}
          </TableCell>
        ))}
      </TableRow>
      {row.getIsExpanded() && renderSubRow && (
        <TableRow className="bg-muted/20 hover:bg-muted/20">
          <TableCell colSpan={colSpan} className="px-6 py-3">
            {renderSubRow(row.original)}
          </TableCell>
        </TableRow>
      )}
    </>
  );
}

function SortIcon({ dir }: { dir: 'asc' | 'desc' | null }) {
  if (dir === 'asc') return <ArrowUp className="ml-2 h-4 w-4" />;
  if (dir === 'desc') return <ArrowDown className="ml-2 h-4 w-4" />;
  return <ArrowUpDown className="ml-2 h-4 w-4 opacity-50" />;
}

function renderLoadingRows<TData>(table: ReturnType<typeof useReactTable<TData>>) {
  const cols = table.getAllLeafColumns().length;
  return Array.from({ length: 5 }).map((_, i) => (
    <TableRow key={`loading-${i}`}>
      {Array.from({ length: cols }).map((_, j) => (
        <TableCell key={j}>
          <Skeleton className="h-5 w-full" />
        </TableCell>
      ))}
    </TableRow>
  ));
}

/**
 * Build a CSV string from columns + rows.  Cell values come from
 * `row[col.key]` (raw, not column.render — render returns ReactNode
 * which doesn't serialize).  Quotes are doubled, fields containing
 * comma / quote / newline are wrapped in quotes; everything else is
 * left bare.  Booleans render true/false, null/undefined render empty.
 */
function toCsv<T = any>(columns: Column<T>[], rows: T[]): string {
  const escape = (v: unknown): string => {
    if (v === null || v === undefined) return '';
    const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
    return /[",\n\r]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
  };
  const header = columns.map((c) => escape(c.label)).join(',');
  const body = rows
    .map((row) =>
      columns.map((c) => escape((row as Record<string, unknown>)[c.key])).join(','),
    )
    .join('\n');
  // BOM so Excel reads UTF-8 correctly.
  return '﻿' + header + '\n' + body + '\n';
}
