import React, { useMemo, useRef, useState } from 'react';
import usePagination, { Offset } from 'src/hooks/usePagination';
import { SelectFilter, NumberFilter, resolveFilterByType } from './Filters';
import type { DataItem, Header, Value, Filter } from './types';
import { HeaderType } from './types';
import styles from './Table.module.css';

interface Props {
  data: DataItem[];
  headers: Header[];
}

export default function Table({ data, headers }: Props): JSX.Element {
  const [filters, setFilters] = useState<Record<string, Filter>>({});
  const filtersId = useRef(crypto.randomUUID());

  const filteredItems = useMemo(() => {
    return data.filter((item) => {
      return Object.entries(filters).every(([key, filter]) =>
        resolveFilterByType(filter, item[key]),
      );
    });
  }, [data, filters]);

  const { items, changeOffset, getPaginationRange } = usePagination<DataItem>({
    items: filteredItems,
    limit: 10,
  });

  const handleUpdateFilters = (
    name: string,
    type: HeaderType,
    value: Value,
  ): void => {
    setFilters((prev) => {
      if (value === null) {
        // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
        delete prev[name];
        return { ...prev };
      }

      return { ...prev, [name]: { value, type } };
    });
  };

  function formatCell(data: DataItem, header: Header): JSX.Element {
    // This formatting is only used because the source is trusted (private markdown files manage only by me)
    // and because Astro don't allow me to pass JSX from an Astro file to a TSX file,
    // so I have to pass the formatted row as a string.
    // DON'T use this method on a public API
    if (header.hasCustomCell && header.formatter) {
      return (
        <div dangerouslySetInnerHTML={{ __html: header.formatter(data) }} />
      );
    }

    if (header.type === HeaderType.Multiple) {
      return (
        <ul className="text-start">
          {data[header.key].map((item: JSX.Element, idx: number) => (
            <li key={idx}>{item}</li>
          ))}
        </ul>
      );
    }

    return data[header.key];
  }

  function formatFilter(header: Header): JSX.Element {
    const baseProps = {
      key: header.key + filtersId.current,
      keyData: header.key,
      value: filters[header.key]?.value,
      onChange: (value: Value) => {
        handleUpdateFilters(header.key, header.type, value);
      },
    };

    switch (header.type) {
      case HeaderType.String:
        return (
          <input
            onChange={(e) => {
              baseProps.onChange(e.target.value);
            }}
            key={baseProps.key}
          />
        );
      case HeaderType.Number:
        return (
          <NumberFilter
            {...baseProps}
            onChange={(value: [string, number | null]) => {
              handleUpdateFilters(header.key, header.type, value as Value);
            }}
          />
        );
      case HeaderType.Select:
        return <SelectFilter data={data} {...baseProps} />;
      case HeaderType.Multiple:
        return <SelectFilter {...baseProps} isMultiple data={data} />;

      default:
        break;
    }

    return <></>;
  }

  return (
    <>
      <section className="hstack">
        <button
          className="ml-auto"
          onClick={() => {
            setFilters({});
            filtersId.current = crypto.randomUUID();
          }}
        >
          Clear Filters
        </button>
      </section>

      <section className="mt-1 overflow-scroll">
        <table className={styles.table}>
          <thead>
            <tr>
              {headers.map((item, idx) => (
                <th key={idx}>
                  <div className="vstack">
                    {item.header}
                    {formatFilter(item)}
                  </div>
                </th>
              ))}
            </tr>
          </thead>

          <tbody>
            {items.map((item, idx) => (
              <tr key={idx}>
                {headers.map((header, hidx) => (
                  <td key={hidx}>{formatCell(item, header)}</td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      </section>

      <section className="mt-1">
        <button
          onClick={() => {
            changeOffset(Offset.First);
          }}
        >
          First
        </button>
        <button
          onClick={() => {
            changeOffset(Offset.Prev);
          }}
        >
          Prev
        </button>

        {getPaginationRange().map((item) => (
          <button
            className={item.current ? 'btn-primary' : ''}
            key={item.page}
            onClick={() => {
              changeOffset(Offset.To, item.page);
            }}
          >
            {item.page}
          </button>
        ))}

        <button
          onClick={() => {
            changeOffset(Offset.Next);
          }}
        >
          Next
        </button>
        <button
          onClick={() => {
            changeOffset(Offset.Last);
          }}
        >
          Last
        </button>
      </section>
    </>
  );
}
