import { useMemo } from 'react';
import cc from 'classcat';
import { useTableSorting } from './useTableSorting';
import { formatValue } from './formatValue';

import './Table.css';

type TableDataValue = string | number | boolean | null;

export type TableColumn<TData extends TableDataValue> = {
  key: string;
  label?: string;
  className?: string;
  style?: React.CSSProperties;
  computedStyle?: (
    value: TData,
    data: Record<string, TData>
  ) => React.CSSProperties | undefined;
  render?: (value: TData, data: Record<string, TData>) => React.ReactNode;
  width?: string | number;
  sort?: boolean;
  truncate?: boolean;
  valueFormat?: string;
  type?: string;
};

interface TableProps<TData extends TableDataValue> {
  rows: Record<string, TData>[];
  columns: TableColumn<TData>[];
  className?: string;
  style?: React.CSSProperties;
  rowClassName?: string;
}

interface TableRowProps<TData extends TableDataValue> {
  data: Record<string, TData>;
  columns: TableColumn<TData>[];
  className?: string;
}

const TableHeader = <TData extends TableDataValue>({
  columns,
  className,
  onSortClick,
  sortingKey,
  sortingOrder,
}: {
  columns: TableColumn<TData>[];
  onSortClick: (key: string, type: string) => void;
  sortingKey: string;
  sortingOrder: number;
  className?: string;
}) => {
  return (
    <div
      className={className}
      style={{
        display: 'grid',
        gridTemplateColumns: 'var(--gridTemplateColumns)',
        borderBottom: '1px solid var(--borderColor)',
      }}
    >
      {columns.map(
        (
          {
            key,
            label,
            className,
            style,
            truncate = true,
            sort,
            type = 'string',
          },
          i
        ) => {
          const isSortable = sort !== false;
          const name = label === undefined ? key : label;

          const cn = cc([{ truncate, cup: isSortable }, className]);
          return (
            <div
              className={cn}
              style={{
                display: 'flex',
                alignItems: 'center',
                ...(type === 'number' && { justifyContent: 'right' }),
                ...style,
              }}
              onClick={isSortable ? () => onSortClick(key, type) : undefined}
              key={i}
            >
              <span className={cc({ truncate })}>{name}</span>
              <span>
                {sortingKey === key ? (sortingOrder ? ' ↑' : ' ↓') : ''}
              </span>
            </div>
          );
        }
      )}
    </div>
  );
};

const TableRow = <TData extends TableDataValue>({
  data,
  columns,
  className,
}: TableRowProps<TData>) => {
  return (
    <div
      className={className}
      style={{
        display: 'grid',
        gridTemplateColumns: 'var(--gridTemplateColumns)',
      }}
    >
      {columns.map(
        (
          {
            key,
            label,
            className,
            style,
            computedStyle,
            render,
            truncate = true,
            valueFormat,
            type = 'string',
          },
          i
        ) => {
          const value = data[key];
          const computedStyles = computedStyle?.(value, data);
          const formattedValue = valueFormat
            ? formatValue(valueFormat, data)
            : value;

          const cn = cc([{ truncate }, className]);
          return (
            <div
              className={cn}
              style={{
                display: 'flex',
                alignItems: 'center',
                ...(type === 'number' && { justifyContent: 'right' }),
                ...style,
                ...computedStyles,
              }}
              title={label}
              key={key + i}
            >
              <span className={cc({ truncate })}>
                {typeof render === 'function'
                  ? render(value, data)
                  : formattedValue}
              </span>
            </div>
          );
        }
      )}
    </div>
  );
};

export const Table = <TData extends TableDataValue>({
  rows,
  columns,
  className = 'table',
  rowClassName = 'table-row',
  style,
}: TableProps<TData>) => {
  const gridTemplateColumns = columns
    .map((props) => props.width || '1fr')
    .join(' ');

  const [sorting, handleSorting, sort] = useTableSorting();

  const sorted_rows = useMemo(() => sort(rows, sorting), [sort, rows, sorting]);

  return (
    <div
      className={className}
      style={{
        display: 'grid',
        ['--gridTemplateColumns' as string]: gridTemplateColumns,
        ...style,
      }}
    >
      <TableHeader
        className={rowClassName}
        columns={columns}
        onSortClick={handleSorting}
        sortingKey={sorting.key}
        sortingOrder={sorting.order}
      />
      {sorted_rows.map((row, i) => {
        return (
          <TableRow
            className={rowClassName}
            data={row}
            columns={columns}
            key={i}
          />
        );
      })}
    </div>
  );
};
