import { getSearchReg } from '@vev/utils';
import classnames from 'classnames/bind';
import { isArray, isEqual, isObject } from 'lodash';
import React, { HTMLAttributes, useMemo, useState } from 'react';
import { SilkeBox } from '../silke-box';
import { ButtonContext, SilkeButton } from '../silke-button';
import { SilkeCheckbox } from '../silke-checkbox';
import TextFieldContext from '../silke-text-field/text-field-context';
import styles from './silke-table.scss';
import { TableRow } from './table-row';
import { TableColumn, TableRowMenu } from './table-types';
import { SilkeText } from '../silke-text';

const cl = classnames.bind(styles);

type BaseProps<T extends Record<string, any>> = {
  children?: React.ReactNode;
  menu?: TableRowMenu<T>[];
  fixedColumns?: boolean;
  hideHeaders?: boolean;
  columns: Array<TableColumn<T> | keyof T>;
  /** Will filter the list based on the value of the cells (can only search when cell value is string) */
  search?: string;
  items: T[];
  loadMoreLabel?: string;

  onLoadMore?: () => Promise<void>;
  onRowHovered?: (index: number) => void;
} & Omit<HTMLAttributes<HTMLTableElement>, 'cellPadding' | 'cellSpacing' | 'onSelect'>;

type SingleSelect<T extends Record<string, any>> = BaseProps<T> & {
  multiselect?: false | undefined;
  multiselectLabel?: string;
  selected?: T;
  onSelect?: (item: T) => void;
};
type MultiSelect<T extends Record<string, any>> = BaseProps<T> & {
  multiselect: true;
  multiselectLabel?: string;
  selected?: T[];
  onSelect: (items: T[]) => void;
};

type SilkeTableProps<T extends Record<string, any>> = SingleSelect<T> | MultiSelect<T>;

export function SilkeTable<T extends Record<string, any>>({
  items,
  columns: _columns,
  children,
  fixedColumns,
  className,
  search,
  hideHeaders,
  menu,
  multiselect,
  multiselectLabel,
  selected,
  onSelect,
  loadMoreLabel,
  onLoadMore,
  onRowHovered,
  ...rest
}: SilkeTableProps<T>) {
  const [loading, setLoading] = useState<boolean>(false);
  const columns = useMemo(
    () => _columns.map((c) => (isObject(c) ? c : ({ attr: c } as TableColumn<T>))),
    [_columns],
  );

  const selectedRows = useMemo(() => {
    if (!selected || items?.length === 0) return [false];

    return items.map((value) => {
      if (isArray(selected)) return selected.includes(value);
      return isEqual(value, selected);
    });
  }, [items, selected]);

  // If selectedRows does not contain any false values then all is selected
  const allSelected = selectedRows.indexOf(false) === -1;
  const searchReg = search ? getSearchReg(search) : undefined;

  const handleSelect = (value: T, select: boolean) => {
    if (onSelect) {
      if (multiselect === true) {
        const newValue = (selected as T[])?.slice() || [];
        if (select) newValue.push(value);
        else {
          const index = newValue.findIndex((v) => isEqual(value, v));
          if (index !== -1) newValue.splice(index, 1);
        }

        onSelect(newValue as any);
      } else {
        onSelect(select ? (value as any) : undefined);
      }
    }
  };

  const handleLoadMore = async () => {
    if (onLoadMore) {
      try {
        setLoading(true);
        await onLoadMore();
      } finally {
        setLoading(false);
      }
    }
  };

  const handleRowHovered = (index: number) => {
    if (onRowHovered) {
      onRowHovered(index);
    }
  };

  let columnCount = columns.length;
  if (multiselect) columnCount++;
  if (menu) columnCount++;

  return (
    <ButtonContext>
      <SilkeBox column className={styles.root}>
        {children && (
          <TextFieldContext size="base" kind="ghost" inline>
            <SilkeBox
              className={styles.toolbar}
              gap="m"
              vAlign="center"
              hPad="s"
              vPad="s"
              bg="neutral-10"
            >
              {children}
            </SilkeBox>
          </TextFieldContext>
        )}
        <table
          {...rest}
          cellSpacing="0"
          cellPadding="0"
          className={cl('table', className, {
            fixedColumns,
            hasSelected: multiselect ? (selected as T[])?.length > 0 : Boolean(selected),
          })}
        >
          <colgroup>
            {columns.map((col, index) => {
              return (
                <col style={{ width: col.width ? col.width + 'px' : undefined }} key={index} />
              );
            })}
            {menu && <col />}
            {multiselect && <col />}
          </colgroup>
          <thead>
            {!hideHeaders && (
              <tr className={styles.head}>
                {multiselect && (
                  <th className={styles.check}>
                    <SilkeCheckbox
                      value={allSelected}
                      onChange={(v) => onSelect && onSelect(v ? (items as any) : [])}
                    />
                  </th>
                )}

                {multiselectLabel ? (
                  <th>
                    <SilkeText weight="strong">{multiselectLabel}</SilkeText>
                  </th>
                ) : (
                  columns.map((c, i) => <th key={i}>{c.component || c.label}</th>)
                )}
                {menu && <th className={styles.menu} />}
              </tr>
            )}
          </thead>

          <tbody onMouseLeave={() => handleRowHovered(-1)}>
            {items.map((item, i) => (
              <TableRow
                key={i}
                index={i}
                item={item}
                columns={columns}
                menu={menu}
                multiselect={multiselect}
                selected={selectedRows[i]}
                search={searchReg}
                onSelect={onSelect && handleSelect}
                onHover={() => handleRowHovered(i)}
              />
            ))}
          </tbody>
          {onLoadMore && (
            <tfoot>
              <tr>
                <td className={styles.loadBtn} colSpan={columnCount}>
                  <SilkeButton
                    kind="secondary"
                    loading={loading}
                    label={loadMoreLabel || 'Load more'}
                    icon="arrow.down"
                    onClick={handleLoadMore}
                  />
                </td>
              </tr>
            </tfoot>
          )}
        </table>
      </SilkeBox>
    </ButtonContext>
  );
}
