import { Input, Pagination, Select, Space, Table, Typography } from 'antd';
import type { ColumnsType, TableProps } from 'antd/es/table';
import type { ReactNode } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { EmptyState, ErrorState, LoadingState } from '../stateKit';
import './DataListTable.css';

const { Text } = Typography;

export type DataListFilterOption<T> = {
  value: string;
  label: ReactNode;
  predicate: (item: T) => boolean;
};

export type DataListTableProps<T extends object> = {
  data: T[];
  columns: ColumnsType<T>;
  getRowKey: (item: T) => React.Key;
  searchPlaceholder?: string;
  searchFields?: ReadonlyArray<keyof T | ((item: T) => string)>;
  searchPredicate?: (item: T, keyword: string) => boolean;
  filters?: ReadonlyArray<DataListFilterOption<T>>;
  pageSize?: number;
  loading?: boolean;
  error?: ReactNode;
  emptyTitle?: ReactNode;
  emptyDescription?: ReactNode;
  retryLabel?: ReactNode;
  onRetry?: () => void;
  mobileCardRender?: (item: T) => ReactNode;
  tableProps?: Omit<
    TableProps<T>,
    'columns' | 'dataSource' | 'rowKey' | 'pagination' | 'loading'
  >;
};

function normalizeSearchText(text: string) {
  return text.trim().toLowerCase();
}

function resolveFieldText<T extends object>(
  item: T,
  field: keyof T | ((item: T) => string),
) {
  if (typeof field === 'function') {
    return field(item);
  }

  const value = item[field];

  if (value === null || value === undefined) {
    return '';
  }

  return String(value);
}

function filterBySearch<T extends object>(
  items: T[],
  keyword: string,
  searchFields?: DataListTableProps<T>['searchFields'],
  searchPredicate?: DataListTableProps<T>['searchPredicate'],
) {
  const normalizedKeyword = normalizeSearchText(keyword);

  if (!normalizedKeyword) {
    return items;
  }

  if (searchPredicate) {
    return items.filter((item) => searchPredicate(item, normalizedKeyword));
  }

  if (!searchFields?.length) {
    return items;
  }

  return items.filter((item) =>
    searchFields.some((field) =>
      normalizeSearchText(resolveFieldText(item, field)).includes(normalizedKeyword),
    ),
  );
}

export function DataListTable<T extends object>({
  data,
  columns,
  getRowKey,
  searchPlaceholder = '검색어를 입력하세요',
  searchFields,
  searchPredicate,
  filters = [],
  pageSize = 10,
  loading,
  error,
  emptyTitle = '표시할 항목이 없습니다.',
  emptyDescription,
  retryLabel,
  onRetry,
  mobileCardRender,
  tableProps,
}: DataListTableProps<T>) {
  const [keyword, setKeyword] = useState('');
  const [activeFilter, setActiveFilter] = useState<string | undefined>();
  const [currentPage, setCurrentPage] = useState(1);

  const filteredData = useMemo(() => {
    const searchedItems = filterBySearch(data, keyword, searchFields, searchPredicate);
    const filter = filters.find((item) => item.value === activeFilter);

    return filter ? searchedItems.filter(filter.predicate) : searchedItems;
  }, [activeFilter, data, filters, keyword, searchFields, searchPredicate]);

  const pagedData = useMemo(() => {
    const startIndex = (currentPage - 1) * pageSize;
    return filteredData.slice(startIndex, startIndex + pageSize);
  }, [currentPage, filteredData, pageSize]);
  const pageCount = Math.max(1, Math.ceil(filteredData.length / pageSize));

  useEffect(() => {
    setCurrentPage((previousPage) => Math.min(previousPage, pageCount));
  }, [pageCount]);

  if (error) {
    return <ErrorState message={error} retryLabel={retryLabel} onRetry={onRetry} />;
  }

  if (loading) {
    return <LoadingState variant="list" rows={Math.min(pageSize, 5)} />;
  }

  return (
    <Space direction="vertical" size={16} className="data-list-table">
      <Space wrap className="data-list-table__toolbar">
        <Input.Search
          allowClear
          value={keyword}
          placeholder={searchPlaceholder}
          className="data-list-table__search"
          onChange={(event) => {
            setKeyword(event.target.value);
            setCurrentPage(1);
          }}
        />
        {filters.length ? (
          <Select
            allowClear
            value={activeFilter}
            placeholder="필터"
            className="data-list-table__filter"
            options={filters.map((filter) => ({
              value: filter.value,
              label: filter.label,
            }))}
            onChange={(nextFilter) => {
              setActiveFilter(nextFilter);
              setCurrentPage(1);
            }}
          />
        ) : null}
        <Text type="secondary">{filteredData.length}건</Text>
      </Space>

      {filteredData.length ? (
        <>
          <Table<T>
            {...tableProps}
            className={[
              'data-list-table__desktop',
              mobileCardRender ? 'data-list-table__desktop--with-mobile' : '',
              tableProps?.className ?? '',
            ]
              .filter(Boolean)
              .join(' ')}
            columns={columns}
            dataSource={pagedData}
            rowKey={getRowKey}
            pagination={false}
          />
          {mobileCardRender ? (
            <div className="data-list-table__mobile">
              {pagedData.map((item) => (
                <article key={getRowKey(item)} className="data-list-table__card">
                  {mobileCardRender(item)}
                </article>
              ))}
            </div>
          ) : null}
          {filteredData.length > pageSize ? (
            <Pagination
              current={currentPage}
              pageSize={pageSize}
              total={filteredData.length}
              onChange={setCurrentPage}
            />
          ) : null}
        </>
      ) : (
        <EmptyState title={emptyTitle} description={emptyDescription} />
      )}
    </Space>
  );
}
