/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import { PureComponent, ReactNode } from 'react';

import { nanoid } from 'nanoid';

import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
import { t, styled, css } from '@superset-ui/core';

import { Icons } from 'src/components/Icons';
import { FilterValue } from 'react-table';
import { Button } from '../Button';
import Fieldset from './Fieldset';
import { recurseReactClone } from './utils';
import {
  type CRUDCollectionProps,
  type CRUDCollectionState,
  type Sort,
} from './types';
import Table, {
  type ColumnsType,
  type SortOrder,
  type SorterResult,
  type TablePaginationConfig,
  TableSize,
} from '../Table';

const CrudButtonWrapper = styled.div`
  text-align: right;
  ${({ theme }) => `margin-bottom: ${theme.sizeUnit * 2}px`}
`;

const StyledButtonWrapper = styled.span`
  ${({ theme }) => `
    margin-top: ${theme.sizeUnit * 3}px;
    margin-left: ${theme.sizeUnit * 3}px;
    button>span>:first-of-type {
      margin-right: 0;
    }
  `}
`;

type CollectionItem = { id: string | number; [key: string]: any };

function createCollectionArray(collection: Record<PropertyKey, any>) {
  return Object.keys(collection).map(k => collection[k] as CollectionItem);
}

function createKeyedCollection(arr: Array<object>) {
  const collectionArray = arr.map(
    (o: any) =>
      ({
        ...o,
        id: o.id || nanoid(),
      }) as CollectionItem,
  );

  const collection: Record<PropertyKey, any> = {};
  collectionArray.forEach((o: CollectionItem) => {
    collection[o.id] = o;
  });

  return {
    collection,
    collectionArray,
  };
}

export default class CRUDCollection extends PureComponent<
  CRUDCollectionProps,
  CRUDCollectionState
> {
  constructor(props: CRUDCollectionProps) {
    super(props);

    const { collection, collectionArray } = createKeyedCollection(
      props.collection,
    );
    this.state = {
      expandedColumns: {},
      collection,
      collectionArray,
      sortColumn: '',
      sort: 0,
    };
    this.onAddItem = this.onAddItem.bind(this);
    this.renderExpandableSection = this.renderExpandableSection.bind(this);
    this.getLabel = this.getLabel.bind(this);
    this.onFieldsetChange = this.onFieldsetChange.bind(this);
    this.changeCollection = this.changeCollection.bind(this);
    this.handleTableChange = this.handleTableChange.bind(this);
    this.buildTableColumns = this.buildTableColumns.bind(this);
    this.toggleExpand = this.toggleExpand.bind(this);
  }

  UNSAFE_componentWillReceiveProps(nextProps: CRUDCollectionProps) {
    if (nextProps.collection !== this.props.collection) {
      const { collection, collectionArray } = createKeyedCollection(
        nextProps.collection,
      );
      this.setState(prevState => ({
        collection,
        collectionArray,
        expandedColumns: prevState.expandedColumns,
      }));
    }
  }

  onCellChange(id: number, col: string, val: boolean) {
    this.setState(prevState => {
      const updatedCollection = {
        ...prevState.collection,
        [id]: {
          ...prevState.collection[id],
          [col]: val,
        },
      };
      const updatedCollectionArray = prevState.collectionArray.map(item =>
        item.id === id ? updatedCollection[id] : item,
      );

      if (this.props.onChange) {
        this.props.onChange(updatedCollectionArray);
      }
      return {
        collection: updatedCollection,
        collectionArray: updatedCollectionArray,
      };
    });
  }

  onAddItem() {
    if (this.props.itemGenerator) {
      let newItem = this.props.itemGenerator();
      const shouldStartExpanded = newItem.expanded === true;
      if (!newItem.id) {
        newItem = { ...newItem, id: nanoid() };
      }
      delete newItem.expanded;

      this.setState(
        prevState => {
          const newCollection = {
            ...prevState.collection,
            [newItem.id]: newItem,
          };
          const newExpandedColumns = shouldStartExpanded
            ? { ...prevState.expandedColumns, [newItem.id]: true }
            : prevState.expandedColumns;
          const newCollectionArray = [newItem, ...prevState.collectionArray];

          return {
            collection: newCollection,
            collectionArray: newCollectionArray,
            expandedColumns: newExpandedColumns,
          };
        },
        () => {
          if (this.props.onChange) {
            this.props.onChange(this.state.collectionArray);
          }
        },
      );
    }
  }

  onFieldsetChange(item: any) {
    this.changeCollection({
      ...this.state.collection,
      [item.id]: item,
    });
  }

  getLabel(col: any): string {
    const { columnLabels } = this.props;
    let label = columnLabels?.[col] ? columnLabels[col] : col;
    if (label.startsWith('__')) {
      label = '';
    }
    return label;
  }

  getTooltip(col: string): string | undefined {
    const { columnLabelTooltips } = this.props;
    return columnLabelTooltips?.[col];
  }

  changeCollection(collection: any) {
    console.log({ collection });
    const newCollectionArray = createCollectionArray(collection);
    console.log({ newCollectionArray });
    this.setState({ collection, collectionArray: newCollectionArray });

    if (this.props.onChange) {
      this.props.onChange(newCollectionArray);
    }
  }

  deleteItem(id: string | number) {
    const newColl = { ...this.state.collection };
    delete newColl[id];
    this.changeCollection(newColl);
  }

  toggleExpand(id: any) {
    this.setState(prevState => ({
      expandedColumns: {
        ...prevState.expandedColumns,
        [id]: !prevState.expandedColumns[id],
      },
    }));
  }

  handleTableChange(
    _pagination: TablePaginationConfig,
    _filters: Record<string, FilterValue | null>,
    sorter: SorterResult<CollectionItem> | SorterResult<CollectionItem>[],
  ) {
    const columnSorter = Array.isArray(sorter) ? sorter[0] : sorter;
    let newSortColumn = '';
    let newSortOrder = 0;

    if (columnSorter?.columnKey && columnSorter?.order) {
      newSortColumn = columnSorter.columnKey as string;
      newSortOrder = columnSorter.order === 'ascend' ? 1 : 2;
    }

    const { sortColumns } = this.props;
    const col = newSortColumn;

    if (sortColumns?.includes(col) || newSortOrder === 0) {
      let sortedArray = [...this.props.collection];

      if (newSortOrder !== 0) {
        const compareSort = (m: Sort, n: Sort) => {
          if (typeof m === 'string' && typeof n === 'string') {
            return (m || '').localeCompare(n || '');
          }
          if (typeof m === 'number' && typeof n === 'number') {
            return m - n;
          }
          if (typeof m === 'boolean' && typeof n === 'boolean') {
            return m === n ? 0 : m ? 1 : -1;
          }
          const mStr = String(m ?? '');
          const nStr = String(n ?? '');
          return mStr.localeCompare(nStr);
        };

        sortedArray.sort((a: any, b: any) => compareSort(a[col], b[col]));
        if (newSortOrder === 2) {
          sortedArray.reverse();
        }
      } else {
        const { collectionArray } = createKeyedCollection(
          this.props.collection,
        );
        sortedArray = collectionArray;
      }

      this.setState({
        collectionArray: sortedArray,
        sortColumn: newSortColumn,
        sort: newSortOrder,
      });
    }
  }

  renderExpandableSection(item: any): ReactNode {
    const propsGenerator = () => ({ item, onChange: this.onFieldsetChange });
    return recurseReactClone(
      this.props.expandFieldset,
      Fieldset,
      propsGenerator,
    );
  }

  renderCell(record: any, col: any): ReactNode {
    const renderer = this.props.itemRenderers?.[col];
    const val = record[col];
    const onChange = this.onCellChange.bind(this, record.id, col);
    return renderer ? renderer(val, onChange, this.getLabel(col), record) : val;
  }

  buildTableColumns() {
    const { tableColumns, allowDeletes, sortColumns = [] } = this.props;

    const antdColumns: ColumnsType = tableColumns.map(col => {
      const label = this.getLabel(col);
      const tooltip = this.getTooltip(col);
      const isSortable = sortColumns.includes(col);
      const currentSortOrder: SortOrder | null | undefined =
        this.state.sortColumn === col
          ? this.state.sort === 1
            ? 'ascend'
            : this.state.sort === 2
              ? 'descend'
              : null
          : null;

      return {
        key: col,
        dataIndex: col,
        minWidth: 100,
        title: (
          <>
            {label}
            {tooltip && (
              <>
                {' '}
                <InfoTooltipWithTrigger
                  label={t('description')}
                  tooltip={tooltip}
                  placement="top"
                />
              </>
            )}
          </>
        ),
        render: (text: any, record: CollectionItem) =>
          this.renderCell(record, col),
        onCell: (record: CollectionItem) => {
          const cellPropsFn = this.props.itemCellProps?.[col];
          const val = record[col];
          return cellPropsFn ? cellPropsFn(val, label, record) : {};
        },
        sorter: isSortable,
        sortOrder: currentSortOrder,
      };
    });

    if (allowDeletes) {
      antdColumns.push({
        key: '__actions',
        dataIndex: '__actions',
        sorter: false,
        title: <></>,
        onCell: () => ({}),
        sortOrder: null,
        minWidth: 50,
        render: (_, record: CollectionItem) => (
          <span
            data-test="crud-delete-option"
            className="text-primary"
            css={{ display: 'flex', justifyContent: 'center' }}
          >
            <Icons.DeleteOutlined
              aria-label="Delete item"
              className="pointer"
              data-test="crud-delete-icon"
              role="button"
              tabIndex={0}
              onClick={() => this.deleteItem(record.id)}
              iconSize="l"
            />
          </span>
        ),
      });
    }

    return antdColumns as ColumnsType<CollectionItem>;
  }

  render() {
    const {
      stickyHeader,
      emptyMessage = t('No items'),
      expandFieldset,
    } = this.props;

    const tableColumns = this.buildTableColumns();
    const expandedRowKeys = Object.keys(this.state.expandedColumns).filter(
      id => this.state.expandedColumns[id],
    );

    const expandableConfig = expandFieldset
      ? {
          expandedRowRender: (record: CollectionItem) =>
            this.renderExpandableSection(record),
          rowExpandable: () => true,
          expandedRowKeys,
          onExpand: (expanded: boolean, record: CollectionItem) => {
            this.toggleExpand(record.id);
          },
        }
      : undefined;

    return (
      <>
        <CrudButtonWrapper>
          {this.props.allowAddItem && (
            <StyledButtonWrapper>
              <Button
                buttonSize="small"
                buttonStyle="secondary"
                onClick={this.onAddItem}
                data-test="add-item-button"
              >
                <Icons.PlusOutlined
                  iconSize="m"
                  data-test="crud-add-table-item"
                />
                {t('Add item')}
              </Button>
            </StyledButtonWrapper>
          )}
        </CrudButtonWrapper>
        <Table<CollectionItem>
          data-test="crud-table"
          columns={tableColumns}
          data={this.state.collectionArray as CollectionItem[]}
          rowKey={(record: CollectionItem) => String(record.id)}
          sticky={stickyHeader}
          pagination={false}
          onChange={this.handleTableChange}
          locale={{ emptyText: emptyMessage }}
          css={
            stickyHeader &&
            css`
              height: 350px;
              overflow: auto;
            `
          }
          expandable={expandableConfig}
          size={TableSize.Middle}
          tableLayout="auto"
        />
      </>
    );
  }
}
