import React, { Fragment, useCallback, useMemo, useState } from 'react';
import { faker } from '@faker-js/faker';
import {
  storybookArgTypes,
  storybookExcludedControlParams,
  StoryMetaType,
} from '@lg-tools/storybook-utils';
import { StoryFn } from '@storybook/react';

import { Badge } from '@leafygreen-ui/badge';
import { Button } from '@leafygreen-ui/button';
import { css } from '@leafygreen-ui/emotion';
import { Tooltip } from '@leafygreen-ui/tooltip';

import {
  KitchenSink,
  makeData,
  makeKitchenSinkData,
  Person,
} from '../utils/makeData.testutils';
import {
  Cell,
  type ColumnDef,
  ExpandedContent,
  type ExpandedState,
  flexRender,
  HeaderCell,
  type HeaderGroup,
  HeaderRow,
  type LeafyGreenTableCell,
  type LeafyGreenVirtualItem,
  LGColumnDef,
  Row,
  type SortingState,
  Table,
  TableBody,
  TableHead,
  type TableProps,
  useLeafyGreenVirtualTable,
} from '..';

import { VerticalAlignment } from './Table.types';

type StoryTableProps = TableProps<unknown>;

const meta: StoryMetaType<typeof Table> = {
  title: 'Composition/Data Display/Table/With Virtualized Scrolling',
  component: Table,
  argTypes: {
    children: { control: 'none' },
    darkMode: storybookArgTypes.darkMode,
    ref: { control: 'none' },
    shouldAlternateRowColor: { control: 'boolean', defaultValue: false },
    shouldTruncate: { control: 'boolean', defaultValue: true },
    verticalAlignment: {
      control: { type: 'radio' },
      options: VerticalAlignment,
      defaultValue: VerticalAlignment.Top,
    },
  },
  args: {
    verticalAlignment: VerticalAlignment.Top,
    shouldTruncate: true,
    shouldAlternateRowColor: false,
  },
  parameters: {
    default: 'Basic',
    chromatic: {
      disableSnapshot: true,
    },
    controls: {
      exclude: [...storybookExcludedControlParams, 'children'],
    },
    docs: {
      source: { type: 'code' },
    },
  },
};
export default meta;

const virtualScrollingContainerHeight = css`
  max-height: calc(100vh - 200px);
  width: 100%;
`;

const basicColumnDefs: Array<LGColumnDef<Person>> = [
  {
    accessorKey: 'index',
    header: 'index',
    size: 40,
  },
  {
    accessorKey: 'id',
    header: 'ID',
    size: 60,
  },
  {
    accessorKey: 'firstName',
    header: 'First Name',
    cell: info => info.getValue(),
  },
  {
    accessorFn: row => row.lastName,
    id: 'lastName',
    cell: info => info.getValue(),
    header: () => <span>Last Name</span>,
  },
  {
    accessorKey: 'age',
    header: () => 'Age',
    size: 50,
    align: 'center',
  },
  {
    accessorKey: 'visits',
    header: () => <span>Visits</span>,
    size: 50,
  },
  {
    accessorKey: 'status',
    header: 'Status',
    size: 90,
  },
];

export const Basic: StoryFn<StoryTableProps> = args => {
  const tableContainerRef = React.useRef<HTMLDivElement>(null);
  const data = React.useMemo(() => makeData(false, 10_000), []);

  const columns = useMemo(() => basicColumnDefs, []);

  const table = useLeafyGreenVirtualTable<Person>({
    containerRef: tableContainerRef,
    data,
    columns,
  });

  return (
    <>
      <div>
        <p>{table.getRowModel().rows.length} total rows</p>
        <p>{table?.virtual.getVirtualItems().length} virtual rows</p>
        <p>{table?.virtual.getTotalSize()} virtual rows</p>
      </div>

      <Table
        {...args}
        table={table}
        ref={tableContainerRef}
        className={virtualScrollingContainerHeight}
      >
        <TableHead isSticky>
          {table.getHeaderGroups().map((headerGroup: HeaderGroup<Person>) => (
            <HeaderRow key={headerGroup.id}>
              {headerGroup.headers.map(header => {
                return (
                  <HeaderCell key={header.id} header={header}>
                    {flexRender(
                      header.column.columnDef.header,
                      header.getContext(),
                    )}
                  </HeaderCell>
                );
              })}
            </HeaderRow>
          ))}
        </TableHead>
        <TableBody>
          {table.virtual.getVirtualItems() &&
            table.virtual
              .getVirtualItems()
              .map((virtualRow: LeafyGreenVirtualItem<Person>) => {
                const row = virtualRow.row;
                const cells = row.getVisibleCells();
                return (
                  <Row key={virtualRow.key} virtualRow={virtualRow} row={row}>
                    {cells.map((cell: LeafyGreenTableCell<Person>) => {
                      return (
                        <Cell key={cell.id} cell={cell}>
                          {flexRender(
                            cell.column.columnDef.cell,
                            cell.getContext(),
                          )}
                        </Cell>
                      );
                    })}
                  </Row>
                );
              })}
        </TableBody>
      </Table>
    </>
  );
};

export const DynamicData: StoryFn<StoryTableProps> = args => {
  const tableContainerRef = React.useRef<HTMLDivElement>(null);
  const data = React.useMemo(() => makeData(false, 100), []);
  const [showEmoji, setShowEmoji] = useState(true);

  const columns = useMemo<Array<LGColumnDef<Person>>>(
    () => [
      {
        accessorKey: 'index',
        header: 'index',
        size: 40,
      },
      {
        accessorKey: 'id',
        header: 'ID',
        size: 60,
      },
      {
        accessorKey: 'firstName',
        header: 'First Name',
        cell: info => info.getValue(),
      },
      {
        accessorFn: row => row.lastName,
        id: 'lastName',
        cell: info => info.getValue(),
        header: () => <span>Last Name</span>,
      },
      {
        accessorKey: 'age',
        header: () => 'Age',
        size: 50,
        align: 'center',
        cell: info => {
          return `${info.getValue()} ${showEmoji ? '🥬' : ''}`;
        },
      },
      {
        accessorKey: 'visits',
        header: () => <span>Visits</span>,
        size: 50,
      },
    ],
    [showEmoji],
  );

  const table = useLeafyGreenVirtualTable<Person>({
    containerRef: tableContainerRef,
    data,
    columns,
  });

  return (
    <>
      <div>
        <p>{table.getRowModel().rows.length} total rows</p>
        <p>{table?.virtual.getVirtualItems().length} virtual rows</p>
        <p>{table?.virtual.getTotalSize()} virtual rows</p>
        <Button onClick={() => setShowEmoji(!showEmoji)}>Toggle 🥬</Button>
      </div>

      <Table
        {...args}
        table={table}
        ref={tableContainerRef}
        className={virtualScrollingContainerHeight}
      >
        <TableHead isSticky>
          {table.getHeaderGroups().map((headerGroup: HeaderGroup<Person>) => (
            <HeaderRow key={headerGroup.id}>
              {headerGroup.headers.map(header => {
                return (
                  <HeaderCell key={header.id} header={header}>
                    {flexRender(
                      header.column.columnDef.header,
                      header.getContext(),
                    )}
                  </HeaderCell>
                );
              })}
            </HeaderRow>
          ))}
        </TableHead>
        <TableBody>
          {table.virtual.getVirtualItems() &&
            table.virtual
              .getVirtualItems()
              .map((virtualRow: LeafyGreenVirtualItem<Person>) => {
                const row = virtualRow.row;
                const cells = row.getVisibleCells();
                return (
                  <Row key={virtualRow.key} virtualRow={virtualRow} row={row}>
                    {cells.map((cell: LeafyGreenTableCell<Person>) => {
                      return (
                        <Cell key={cell.id} cell={cell}>
                          {flexRender(
                            cell.column.columnDef.cell,
                            cell.getContext(),
                          )}
                        </Cell>
                      );
                    })}
                  </Row>
                );
              })}
        </TableBody>
      </Table>
    </>
  );
};
DynamicData.parameters = {
  chromatic: {
    disableSnapshot: true,
  },
};

export const NestedRows: StoryFn<StoryTableProps> = args => {
  const tableContainerRef = React.useRef<HTMLDivElement>(null);
  const data = React.useState(() => makeData(false, 5000, 5, 3))[0];

  const columns = useMemo(() => basicColumnDefs, []);

  const table = useLeafyGreenVirtualTable<Person>({
    containerRef: tableContainerRef,
    data,
    columns,
  });

  return (
    <>
      <div>
        <p>{table.getRowModel().rows.length} total rows</p>
      </div>

      <Table
        {...args}
        table={table}
        ref={tableContainerRef}
        className={virtualScrollingContainerHeight}
      >
        <TableHead isSticky>
          {table.getHeaderGroups().map((headerGroup: HeaderGroup<Person>) => (
            <HeaderRow key={headerGroup.id}>
              {headerGroup.headers.map(header => {
                return (
                  <HeaderCell key={header.id} header={header}>
                    {flexRender(
                      header.column.columnDef.header,
                      header.getContext(),
                    )}
                  </HeaderCell>
                );
              })}
            </HeaderRow>
          ))}
        </TableHead>
        <TableBody>
          {table.virtual.getVirtualItems() &&
            table.virtual
              .getVirtualItems()
              .map((virtualRow: LeafyGreenVirtualItem<Person>) => {
                const row = virtualRow.row;
                const cells = row.getVisibleCells();

                return (
                  <Row key={virtualRow.key} row={row} virtualRow={virtualRow}>
                    {cells.map((cell: LeafyGreenTableCell<Person>) => {
                      return (
                        <Cell key={cell.id} cell={cell}>
                          {flexRender(
                            cell.column.columnDef.cell,
                            cell.getContext(),
                          )}
                        </Cell>
                      );
                    })}
                  </Row>
                );
              })}
        </TableBody>
      </Table>
    </>
  );
};

export const SortableRows: StoryFn<StoryTableProps> = args => {
  const tableContainerRef = React.useRef<HTMLDivElement>(null);
  const data = React.useState(() => makeData(false, 5000))[0];
  const [sorting, setSorting] = React.useState<SortingState>([]);

  const columns = React.useMemo<Array<ColumnDef<Person>>>(
    () => [
      {
        accessorKey: 'id',
        header: 'ID',
        size: 60,
        enableSorting: true,
      },
      {
        accessorKey: 'firstName',
        header: 'First Name',
        cell: info => info.getValue(),
      },
      {
        accessorFn: row => row.lastName,
        id: 'lastName',
        cell: info => info.getValue(),

        header: () => <span>Last Name</span>,
        enableSorting: true,
      },
      {
        accessorKey: 'age',

        header: () => 'Age',
        size: 50,
        enableSorting: true,
      },
      {
        accessorKey: 'visits',

        header: () => <span>Visits</span>,
        size: 50,
      },
      {
        accessorKey: 'status',
        header: 'Status',
        size: 90,
      },
    ],
    [],
  );

  const table = useLeafyGreenVirtualTable<Person>({
    containerRef: tableContainerRef,
    data,
    columns,
    state: {
      sorting,
    },
    onSortingChange: setSorting,
  });

  return (
    <>
      <div>
        <p>{table.getRowModel().rows.length} total rows</p>
      </div>

      <Table
        {...args}
        table={table}
        ref={tableContainerRef}
        className={virtualScrollingContainerHeight}
      >
        <TableHead isSticky>
          {table.getHeaderGroups().map((headerGroup: HeaderGroup<Person>) => (
            <HeaderRow key={headerGroup.id}>
              {headerGroup.headers.map(header => {
                return (
                  <HeaderCell key={header.id} header={header}>
                    {flexRender(
                      header.column.columnDef.header,
                      header.getContext(),
                    )}
                  </HeaderCell>
                );
              })}
            </HeaderRow>
          ))}
        </TableHead>
        <TableBody>
          {table.virtual.getVirtualItems() &&
            table.virtual
              .getVirtualItems()
              .map((virtualRow: LeafyGreenVirtualItem<Person>) => {
                const row = virtualRow.row;
                const cells = row.getVisibleCells();

                return (
                  <Row key={virtualRow.key} row={row} virtualRow={virtualRow}>
                    {cells.map((cell: LeafyGreenTableCell<Person>) => {
                      return (
                        <Cell key={cell.id} cell={cell}>
                          {flexRender(
                            cell.column.columnDef.cell,
                            cell.getContext(),
                          )}
                        </Cell>
                      );
                    })}
                  </Row>
                );
              })}
        </TableBody>
      </Table>
    </>
  );
};

export const SelectableRows: StoryFn<StoryTableProps> = args => {
  const tableContainerRef = React.useRef<HTMLDivElement>(null);
  const data = React.useState(() => makeData(false, 5000))[0];
  const [rowSelection, setRowSelection] = React.useState({});

  const columns = useMemo(() => basicColumnDefs, []);

  const table = useLeafyGreenVirtualTable<Person>({
    containerRef: tableContainerRef,
    data,
    columns,
    state: {
      rowSelection,
    },
    onRowSelectionChange: setRowSelection,
    hasSelectableRows: true,
  });

  return (
    <>
      <div>
        <p>{table.getRowModel().rows.length} total rows</p>
      </div>
      <Table
        {...args}
        table={table}
        ref={tableContainerRef}
        className={virtualScrollingContainerHeight}
      >
        <TableHead isSticky>
          {table.getHeaderGroups().map((headerGroup: HeaderGroup<Person>) => (
            <HeaderRow key={headerGroup.id}>
              {headerGroup.headers.map(header => {
                return (
                  <HeaderCell key={header.id} header={header}>
                    {flexRender(
                      header.column.columnDef.header,
                      header.getContext(),
                    )}
                  </HeaderCell>
                );
              })}
            </HeaderRow>
          ))}
        </TableHead>
        <TableBody>
          {table.virtual.getVirtualItems() &&
            table.virtual
              .getVirtualItems()
              .map((virtualRow: LeafyGreenVirtualItem<Person>) => {
                const row = virtualRow.row;
                const cells = row.getVisibleCells();

                return (
                  <Row key={virtualRow.key} row={row} virtualRow={virtualRow}>
                    {cells.map((cell: LeafyGreenTableCell<Person>) => {
                      return (
                        <Cell key={cell.id} cell={cell}>
                          {flexRender(
                            cell.column.columnDef.cell,
                            cell.getContext(),
                          )}
                        </Cell>
                      );
                    })}
                  </Row>
                );
              })}
        </TableBody>
      </Table>
    </>
  );
};

export const ExpandableContent: StoryFn<StoryTableProps> = args => {
  const tableContainerRef = React.useRef<HTMLDivElement>(null);
  const data = React.useState(() => makeData(true, 5000))[0];
  const [expanded, setExpanded] = React.useState<ExpandedState>({});

  const columns = useMemo(() => basicColumnDefs, []);

  const table = useLeafyGreenVirtualTable<Person>({
    containerRef: tableContainerRef,
    data,
    columns,
    state: {
      expanded,
    },
    onExpandedChange: setExpanded,
  });

  return (
    <>
      <div>
        <p>{table.getRowModel().rows.length} total rows</p>
      </div>

      <Table
        {...args}
        table={table}
        ref={tableContainerRef}
        className={virtualScrollingContainerHeight}
      >
        <TableHead isSticky>
          {table.getHeaderGroups().map((headerGroup: HeaderGroup<Person>) => (
            <HeaderRow key={headerGroup.id}>
              {headerGroup.headers.map(header => {
                return (
                  <HeaderCell key={header.id} header={header}>
                    {flexRender(
                      header.column.columnDef.header,
                      header.getContext(),
                    )}
                  </HeaderCell>
                );
              })}
            </HeaderRow>
          ))}
        </TableHead>
        <TableBody>
          {table.virtual.getVirtualItems() &&
            table.virtual
              .getVirtualItems()
              .map((virtualRow: LeafyGreenVirtualItem<Person>) => {
                const row = virtualRow.row;
                const isExpandedContent = row.isExpandedContent ?? false;

                return (
                  <Fragment key={virtualRow.key}>
                    {!isExpandedContent && (
                      <Row row={row} virtualRow={virtualRow}>
                        {row
                          .getVisibleCells()
                          .map((cell: LeafyGreenTableCell<Person>) => {
                            return (
                              <Cell key={cell.id} cell={cell}>
                                {flexRender(
                                  cell.column.columnDef.cell,
                                  cell.getContext(),
                                )}
                              </Cell>
                            );
                          })}
                      </Row>
                    )}
                    {isExpandedContent && (
                      <ExpandedContent row={row} virtualRow={virtualRow} />
                    )}
                  </Fragment>
                );
              })}
        </TableBody>
      </Table>
    </>
  );
};

export const TallRows: StoryFn<StoryTableProps> = args => {
  const tableContainerRef = React.useRef<HTMLDivElement>(null);
  const data = React.useMemo(() => {
    return makeData(false, 10_000).map(d => ({
      ...d,
      id:
        faker.string.uuid() +
        '--' +
        faker.string.uuid() +
        '--' +
        faker.string.uuid() +
        '--' +
        faker.string.uuid(),
    }));
  }, []);

  const columnDefs: Array<LGColumnDef<Person>> = [
    {
      accessorKey: 'index',
      header: 'index',
      size: 90,
    },
    {
      accessorKey: 'id',
      header: 'ID',
      // This makes the width auto
      size: NaN,
    },
    {
      accessorKey: 'firstName',
      header: 'First Name',
      cell: info => info.getValue(),
      size: 120,
    },
    {
      accessorFn: row => row.lastName,
      id: 'lastName',
      cell: info => info.getValue(),
      header: () => <span>Last Name</span>,
      size: 120,
    },
    {
      accessorKey: 'age',
      header: () => 'Age',
      size: 50,
      align: 'center',
    },
    {
      accessorKey: 'visits',
      header: () => <span>Visits</span>,
      size: 50,
    },
    {
      accessorKey: 'status',
      header: 'Status',
      size: 110,
    },
  ];

  // FIXME:
  // eslint-disable-next-line react-hooks/exhaustive-deps
  const columns = useMemo(() => columnDefs, []);
  const estimateSize = useCallback(() => 68, []);

  const table = useLeafyGreenVirtualTable<Person>({
    containerRef: tableContainerRef,
    data,
    columns,
    virtualizerOptions: {
      estimateSize,
    },
  });

  return (
    <div
      className={css`
        width: 100%;
      `}
    >
      <div>
        <p>{table.getRowModel().rows.length} total rows</p>
        <p>{table?.virtual.getVirtualItems().length} virtual rows</p>
      </div>

      <Table
        {...args}
        table={table}
        ref={tableContainerRef}
        className={virtualScrollingContainerHeight}
      >
        <TableHead isSticky>
          {table.getHeaderGroups().map((headerGroup: HeaderGroup<Person>) => (
            <HeaderRow key={headerGroup.id}>
              {headerGroup.headers.map(header => {
                return (
                  <HeaderCell key={header.id} header={header}>
                    {flexRender(
                      header.column.columnDef.header,
                      header.getContext(),
                    )}
                  </HeaderCell>
                );
              })}
            </HeaderRow>
          ))}
        </TableHead>
        <TableBody>
          {table.virtual.getVirtualItems() &&
            table.virtual
              .getVirtualItems()
              .map((virtualRow: LeafyGreenVirtualItem<Person>) => {
                const row = virtualRow.row;
                const cells = row.getVisibleCells();
                return (
                  <Row key={virtualRow.key} row={row} virtualRow={virtualRow}>
                    {cells.map((cell: LeafyGreenTableCell<Person>) => {
                      return (
                        <Cell
                          key={cell.id}
                          className={css`
                            padding-block: 4px;

                            & > div {
                              max-height: unset;
                            }
                          `}
                          cell={cell}
                        >
                          {flexRender(
                            cell.column.columnDef.cell,
                            cell.getContext(),
                          )}
                        </Cell>
                      );
                    })}
                  </Row>
                );
              })}
        </TableBody>
      </Table>
    </div>
  );
};

export const WithLeafyGreenComponents: StoryFn<StoryTableProps> = args => {
  const tableContainerRef = React.useRef<HTMLDivElement>(null);
  const [data] = useState(() => makeKitchenSinkData(10_000));

  const columns = React.useMemo<Array<LGColumnDef<KitchenSink>>>(
    () => [
      {
        accessorKey: 'dateCreated',
        header: 'Date Created',
        enableSorting: true,
        cell: info =>
          (info.getValue() as Date).toLocaleDateString('en-us', {
            year: 'numeric',
            month: 'short',
            day: 'numeric',
          }),
        size: 180,
      },
      {
        accessorKey: 'clusterType',
        header: 'Cluster Type',
        // This makes the size auto
        size: NaN,
      },
      {
        accessorKey: 'frequency',
        header: 'Frequency',
        align: 'center',
        size: 140,
      },
      {
        accessorKey: 'encryptorEnabled',
        header: 'Encryptor',

        cell: info => (
          <Badge variant={info.getValue() ? 'green' : 'red'}>
            {info.getValue() ? 'Enabled' : 'Not enabled'}
          </Badge>
        ),
      },
      {
        accessorKey: 'mdbVersion',
        header: 'MongoDB Version',
        enableSorting: true,
        size: 120,
      },
      {
        id: 'actions',
        header: '',
        size: 150,

        cell: _ => {
          return (
            <div>
              <Tooltip trigger={<Button size="small">Button</Button>}>
                {"I'm leafy, you're leafy"}
              </Tooltip>
            </div>
          );
        },
      },
    ],
    [],
  );

  const table = useLeafyGreenVirtualTable<KitchenSink>({
    containerRef: tableContainerRef,
    data,
    columns,
  });

  return (
    <div
      className={css`
        width: 100%;
      `}
    >
      <div>
        <p>{table.getRowModel().rows.length} total rows</p>
      </div>

      <Table
        {...args}
        table={table}
        ref={tableContainerRef}
        className={virtualScrollingContainerHeight}
      >
        <TableHead isSticky>
          {table
            .getHeaderGroups()
            .map((headerGroup: HeaderGroup<KitchenSink>) => (
              <HeaderRow key={headerGroup.id}>
                {headerGroup.headers.map(header => {
                  return (
                    <HeaderCell key={header.id} header={header}>
                      {flexRender(
                        header.column.columnDef.header,
                        header.getContext(),
                      )}
                    </HeaderCell>
                  );
                })}
              </HeaderRow>
            ))}
        </TableHead>
        <TableBody>
          {table.virtual.getVirtualItems() &&
            table.virtual
              .getVirtualItems()
              .map((virtualRow: LeafyGreenVirtualItem<KitchenSink>) => {
                const row = virtualRow.row;
                const isExpandedContent = row.isExpandedContent ?? false;

                return (
                  <Fragment key={virtualRow.key}>
                    {!isExpandedContent && (
                      <Row row={row} virtualRow={virtualRow}>
                        {row
                          .getVisibleCells()
                          .map((cell: LeafyGreenTableCell<KitchenSink>) => {
                            return (
                              <Cell key={cell.id} cell={cell}>
                                {flexRender(
                                  cell.column.columnDef.cell,
                                  cell.getContext(),
                                )}
                              </Cell>
                            );
                          })}
                      </Row>
                    )}
                    {isExpandedContent && (
                      <ExpandedContent row={row} virtualRow={virtualRow} />
                    )}
                  </Fragment>
                );
              })}
        </TableBody>
      </Table>
    </div>
  );
};

export const NoTruncation = WithLeafyGreenComponents.bind({});
NoTruncation.args = {
  shouldTruncate: false,
};
