import { Fragment, useContext, useEffect, useMemo, useState, type ReactElement } from 'react';
import { Building2, ChevronDown } from 'lucide-react';
import { useQuery } from 'urql';
import {
  flexRender,
  useTable,
  type ColumnVisibilityState,
  type RowSelectionState,
  type SortingState,
} from '@tanstack/react-table';
import { tableFeaturesConfig } from '@/lib/table-features.js';
import { AllBusinessesForScreenDocument, BusinessesUsageDocument } from '../../gql/graphql.js';
import { FiltersContext } from '../../providers/filters-context.js';
import {
  DataTablePagination,
  InsertBusiness,
  MergeBusinessesButton,
  TableSkeleton,
} from '../common/index.js';
import { PageLayout } from '../layout/page-layout.js';
import { Alert, AlertDescription, AlertTitle } from '../ui/alert.js';
import { Button } from '../ui/button.js';
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from '../ui/dropdown-menu.js';
import { Empty, EmptyContent, EmptyDescription, EmptyMedia, EmptyTitle } from '../ui/empty.js';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table.js';
import { BatchUpdateBusinessesDialog } from './batch-update-dialog.js';
import {
  businessNodesToRows,
  filterBusinessRows,
  mergeBusinessUsage,
  type BusinessRowFilters,
  type BusinessTableMeta,
} from './business-rows.js';
import { BusinessesFilters } from './businesses-filters.js';
import { COLUMN_GROUPS, columns, DEFAULT_COLUMN_VISIBILITY, USAGE_COLUMN_IDS } from './columns.js';
import { TableScrollContainer } from './table-scroll-container.js';

// Fetch all businesses (no server pagination) so filtering/sorting/pagination are all client-side
// and apply across the whole set, not just one page. The resolver already loads them all in memory.
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
/* GraphQL */ `
  query AllBusinessesForScreen {
    allBusinesses {
      nodes {
        __typename
        id
        name
        ... on LtdFinancialEntity {
          hebrewName
          governmentId
          country {
            id
            code
          }
          city
          zipCode
          createdAt
          updatedAt
          sortCode {
            id
            key
            name
          }
          taxCategory {
            id
            name
          }
          irsCode
          pcn874RecordType
          isClient
          isAdmin
          isActive
          suggestions {
            description
            tags {
              id
              name
            }
          }
        }
      }
    }
  }
`;

// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
/* GraphQL */ `
  query BusinessesUsage($ids: [UUID!]!) {
    businessesUsage(ids: $ids) {
      id
      businessId
      totalTransactions
      totalDocuments
      totalMiscExpenses
      totalLedgerRecords
    }
  }
`;

export const Businesses = (): ReactElement => {
  const { setFiltersContext } = useContext(FiltersContext);

  const [{ data, fetching, error }, refetch] = useQuery({
    query: AllBusinessesForScreenDocument,
  });

  const rows = useMemo(
    () => businessNodesToRows(data?.allBusinesses?.nodes ?? []),
    [data?.allBusinesses?.nodes],
  );

  const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
  const [columnVisibility, setColumnVisibility] =
    useState<ColumnVisibilityState>(DEFAULT_COLUMN_VISIBILITY);
  const [sorting, setSorting] = useState<SortingState>([]);
  const [filters, setFilters] = useState<BusinessRowFilters>({
    name: '',
    client: false,
    admin: false,
    inactive: false,
    unusedOnly: false,
    sortCode: '',
    taxCategory: '',
  });

  // Usage counts are lazy: fetched once a usage column is enabled or "unused only" is on.
  const businessIds = useMemo(() => rows.map(row => row.id), [rows]);
  const usageEnabled = USAGE_COLUMN_IDS.some(id => columnVisibility[id]) || filters.unusedOnly;
  const [{ data: usageData, fetching: usageFetching }] = useQuery({
    query: BusinessesUsageDocument,
    variables: { ids: businessIds },
    pause: !usageEnabled || businessIds.length === 0,
  });

  const tableRows = useMemo(
    () => mergeBusinessUsage(rows, usageData?.businessesUsage ?? []),
    [rows, usageData],
  );
  const filteredRows = useMemo(() => filterBusinessRows(tableRows, filters), [tableRows, filters]);

  // eslint-disable-next-line react-hooks/incompatible-library -- useTable returns non-memoizable handles by design
  const table = useTable({
    features: tableFeaturesConfig,
    data: filteredRows,
    columns,
    getRowId: row => row.id,
    initialState: {
      pagination: { pageIndex: 0, pageSize: 100 },
    },
    enableRowSelection: true,
    onRowSelectionChange: setRowSelection,
    onColumnVisibilityChange: setColumnVisibility,
    onSortingChange: setSorting,
    state: {
      rowSelection,
      columnVisibility,
      sorting,
    },
    // cast: @tanstack's TableMeta interface is empty by default (not augmented here); cells read
    // these handles back off table.options.meta with a matching cast.
    meta: {
      usageFetching: usageEnabled && usageFetching,
      refetchBusinesses: () => refetch(),
    } as BusinessTableMeta,
  });

  // Derive selected ids from the stable `filteredRows` (the table's data) and `rowSelection`
  // (keyed by row id via getRowId), avoiding the unstable `table` object. This stays in sync
  // when filters or data change, unlike memoizing on `rowSelection` alone.
  const selectedIds = useMemo(
    () => filteredRows.filter(row => rowSelection[row.id]).map(row => row.id),
    [filteredRows, rowSelection],
  );

  // Footer
  useEffect(() => {
    // MergeBusinessesButton calls onChange once per selected row, so guard to refetch only once
    let refetched = false;
    const onMergeChange = (): void => {
      if (!refetched) {
        refetched = true;
        refetch();
      }
    };
    const selectedForMerge = selectedIds.map(id => ({
      id,
      onChange: onMergeChange,
    }));
    setFiltersContext(
      <div className="flex flex-row gap-x-5">
        <BusinessesFilters
          filters={filters}
          setFilters={setFilters}
          usageLoading={usageEnabled && usageFetching}
        />
        <MergeBusinessesButton selected={selectedForMerge} resetMerge={() => setRowSelection({})} />
        <BatchUpdateBusinessesDialog
          businessIds={selectedIds}
          onDone={() => {
            refetch();
            setRowSelection({});
          }}
        />
      </div>,
    );
  }, [setFiltersContext, selectedIds, refetch, filters, setFilters, usageEnabled, usageFetching]);

  return (
    <PageLayout
      title={`Businesses (${rows.length})`}
      description="All businesses"
      headerActions={
        <div className="flex items-center py-4 gap-4">
          <InsertBusiness description="" onAdd={() => refetch()} />
          <DropdownMenu>
            <DropdownMenuTrigger asChild>
              <Button variant="outline">
                Columns <ChevronDown />
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="end">
              {COLUMN_GROUPS.map(group => ({
                ...group,
                columns: group.columns.filter(column => table.getColumn(column.id)?.getCanHide()),
              }))
                .filter(group => group.columns.length > 0)
                .map((group, groupIndex) => {
                  const allVisible = group.columns.every(
                    column => table.getColumn(column.id)?.getIsVisible() ?? false,
                  );
                  return (
                    <Fragment key={group.label}>
                      {groupIndex > 0 ? <DropdownMenuSeparator /> : null}
                      <DropdownMenuItem
                        className="flex w-full items-center justify-between gap-4 font-semibold"
                        onSelect={event => {
                          event.preventDefault();
                          for (const column of group.columns) {
                            table.getColumn(column.id)?.toggleVisibility(!allVisible);
                          }
                        }}
                      >
                        {group.label}
                        <span className="text-xs font-normal text-muted-foreground">
                          {allVisible ? 'Hide all' : 'Show all'}
                        </span>
                      </DropdownMenuItem>
                      {group.columns.map(column => {
                        const tableColumn = table.getColumn(column.id);
                        return tableColumn ? (
                          <DropdownMenuCheckboxItem
                            key={column.id}
                            checked={tableColumn.getIsVisible()}
                            onCheckedChange={value => tableColumn.toggleVisibility(!!value)}
                          >
                            {column.label}
                          </DropdownMenuCheckboxItem>
                        ) : null;
                      })}
                    </Fragment>
                  );
                })}
            </DropdownMenuContent>
          </DropdownMenu>
        </div>
      }
    >
      {error ? (
        <Alert variant="destructive">
          <AlertTitle>Failed to load businesses</AlertTitle>
          <AlertDescription>{error.message}</AlertDescription>
        </Alert>
      ) : fetching ? (
        <div className="rounded-md border p-4">
          <TableSkeleton columns={8} rows={10} />
        </div>
      ) : rows.length === 0 ? (
        <Empty className="py-8 sm:py-12">
          <EmptyMedia>
            <Building2 className="size-8 sm:size-10 text-muted-foreground" />
          </EmptyMedia>
          <EmptyTitle>No businesses yet</EmptyTitle>
          <EmptyDescription>
            Add your first business, or generate businesses from your transactions.
          </EmptyDescription>
          <EmptyContent>
            <InsertBusiness description="" onAdd={() => refetch()} />
          </EmptyContent>
        </Empty>
      ) : (
        <div className="space-y-4">
          <TableScrollContainer className="overflow-hidden rounded-md border">
            <Table>
              <TableHeader>
                {table.getHeaderGroups().map(headerGroup => (
                  <TableRow key={headerGroup.id}>
                    {headerGroup.headers.map(header => (
                      <TableHead key={header.id} colSpan={header.colSpan}>
                        {header.isPlaceholder
                          ? null
                          : flexRender(header.column.columnDef.header, header.getContext())}
                      </TableHead>
                    ))}
                  </TableRow>
                ))}
              </TableHeader>
              <TableBody>
                {table.getRowModel().rows.length ? (
                  table.getRowModel().rows.map(row => (
                    <TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
                      {row.getVisibleCells().map(cell => (
                        <TableCell key={cell.id}>
                          {flexRender(cell.column.columnDef.cell, cell.getContext())}
                        </TableCell>
                      ))}
                    </TableRow>
                  ))
                ) : (
                  <TableRow>
                    <TableCell colSpan={columns.length} className="h-24 text-center">
                      No results.
                    </TableCell>
                  </TableRow>
                )}
              </TableBody>
            </Table>
          </TableScrollContainer>
          <DataTablePagination table={table} />
        </div>
      )}
    </PageLayout>
  );
};
