import clsx from 'clsx';
import React, { PropsWithChildren, useEffect, useState } from 'react';
import { DraggableProvided } from 'react-beautiful-dnd';
import { noop } from '../../../helpers/utils';
import { Data } from '../../../types/data';
import { getTooltipText } from '../../../utils/ToolTipHelpers';
import { ActionData } from '../../Actions';
import { Button, ButtonContext } from '../../Buttons';
import { IconName, Icons } from '../../Icons';
import { InlineMenu } from '../../InlineMenu';
import { DynamicListColumn } from '../DynamicDataList.model';
import classes from './DynamicListRow.scss';

export interface DynamicListRowProps<T extends Data> {
  /** The column definition */
  columns: DynamicListColumn<T>[];
  /** List data */
  data: T;
  /** Spacing between columns */
  columnSizes: string;
  /** Header row height */
  columnGap?: string;
  /** List row height */
  rowHeight?: string;
  /** Size of action button and checkbox */
  actionSize?: string;
  /** Horizontal alignment of text */
  horizontalTextAlign?: 'left' | 'right' | 'center';
  /** Vertical alignment of text */
  verticalTextAlign?: 'start' | 'center' | 'end';
  /** If set to true, column text overflow will be wrapped to a new line. Otherwise, it will be truncated with an ellipsis (default: false) */
  textWrap?: boolean;
  /** If set to true, the remove action button will be rendered (default: undefined) */
  allowRemove?: boolean;
  /** If set to true, editable fields will be highlighted and row click events will be fired (default: false) */
  allowEditing?: boolean;
  /** Property name that is used to determine data position (default: undefined) */
  positionKey?: keyof T;
  /** If set to true, this component can be dragged (default: undefined) */
  allowDragging?: boolean;
  /** Determines if there is currently an on-going drag event (default: undefined) */
  dragging?: boolean;
  /** Whether or not the DDL is disabled (default: false) */
  disabled?: boolean;
  /** If set to true, the position column will be shown (default: false) */
  showPositionColumn?: boolean;
  /** If set to true, the action column will be shown (default: false) */
  showActionColumn?: boolean;
  /** Emits when the position input has changed. Row data and the new position is supplied */
  onPositionInputChanged?: (
    currenPosition: number,
    newPosition: number,
  ) => void;
  onRowClicked?: (data: T) => void;
  /** Emits when the action button is clicked. Data is supplied as a parameter */
  onActionClicked?: (data: T) => void;
  /** CSS Class name for additional styles */
  className?: string;
  /** CSS Class name provider for each row. Allows row style to be determined by data */
  rowClassNameProvider?: (data: T) => string;
  /** Provide inline actions which are available through '...' context menu */
  inlineMenuActions?: (data: T) => ActionData[];
  provided?: DraggableProvided;
}

export const DynamicListRow = <T extends Data>({
  columns,
  data,
  columnSizes,
  columnGap,
  rowHeight,
  actionSize,
  horizontalTextAlign,
  verticalTextAlign,
  allowRemove,
  positionKey,
  allowDragging,
  disabled,
  provided,
  onPositionInputChanged = noop,
  onRowClicked = noop,
  onActionClicked = noop,
  className = '',
  rowClassNameProvider,
  inlineMenuActions,
  dragging = false,
  showPositionColumn = false,
  showActionColumn = false,
  allowEditing = false,
  textWrap = false,
}: PropsWithChildren<DynamicListRowProps<T>>): JSX.Element => {
  const customStyles = {
    gridAutoRows: `minmax(50px, ${rowHeight})`,
    gridTemplateColumns: columnSizes,
    gridColumnGap: columnGap,
    justifyItems: horizontalTextAlign,
    alignItems: verticalTextAlign,
    ...provided?.draggableProps.style,
  } as React.CSSProperties;

  const inlineMenuData = inlineMenuActions?.(data);

  const [position, setPosition] = useState<number | string | undefined>(
    positionKey ? Number(data[positionKey]) : undefined,
  );

  useEffect(() => {
    setPosition(positionKey ? Number(data[positionKey]) : undefined);
  }, [data, positionKey]);

  /** Emits current position and new position when user changes position via input */
  const onPositionInputChangedHandler = (value: number): void => {
    if (positionKey !== undefined && typeof value === 'number') {
      const currentPosition = Number(data[positionKey]);
      const newPosition = value;

      // position has not changed, exit
      if (currentPosition === newPosition) {
        return;
      }

      onPositionInputChanged(currentPosition, newPosition);
    }
  };

  return (
    <div
      className={clsx(
        classes.container,
        { [classes.dragging]: dragging },
        'dynamic-list-row-container',
        className,
        rowClassNameProvider ? rowClassNameProvider(data) : '',
      )}
      data-test-id="dynamic-list-row"
      ref={provided?.innerRef}
      {...provided?.draggableProps}
      style={customStyles}
      onClick={() => allowEditing && onRowClicked(data)}
    >
      {showPositionColumn && (
        <div className={classes.position} onClick={(e) => e.stopPropagation()}>
          {allowDragging && (
            <div className={classes.draggable} {...provided?.dragHandleProps}>
              <Icons icon={IconName.Drag} className={classes.dragIcon} />
            </div>
          )}

          <div className={classes.input}>
            <input
              type="text"
              onChange={(event) => {
                // check for valid number
                if (Number.isNaN(Number(event.target.value))) {
                  return;
                } else {
                  setPosition(Number(event.target.value));
                }
              }}
              value={position}
              disabled={disabled}
              onKeyDown={(event) =>
                event.key === 'Enter' &&
                onPositionInputChangedHandler(Number(event.currentTarget.value))
              }
              onBlur={(event) => {
                onPositionInputChangedHandler(
                  Number(event.currentTarget.value),
                );
              }}
            />
          </div>
        </div>
      )}
      {columns.map((column: DynamicListColumn<T>) => {
        const { columnData, tooltip } = renderData<T>(column, data);

        return (
          <div
            className={clsx(classes.wrapper, {
              [classes.editable]:
                column.dataEntryRender !== undefined && allowEditing,
            })}
            key={column.key ?? (column.propertyName as string)}
            style={{
              justifyContent: horizontalTextAlign,
              alignItems: verticalTextAlign,
            }}
          >
            <div
              className={clsx(classes.column, {
                [classes.nowrap]: !textWrap,
              })}
              title={tooltip}
              data-test-id={`dynamic-list-property:${
                column.propertyName as string
              }`}
            >
              {columnData}
            </div>
          </div>
        );
      })}
      {showActionColumn && (
        <div className={classes.actionButtonContainer}>
          {inlineMenuData && !!inlineMenuData.length && (
            <InlineMenu
              actions={inlineMenuData}
              showArrow={false}
              placement="bottom-end"
              onButtonClicked={(e) => e.stopPropagation()}
            />
          )}
          {allowRemove && !inlineMenuActions && (
            <Button
              type="button"
              className={classes.actionButton}
              // TODO: Change IconName to IconName.Plus if an item can be added
              icon={IconName.X}
              buttonContext={ButtonContext.Icon}
              height={actionSize}
              width={actionSize}
              disabled={disabled}
              onButtonClicked={() => {
                onActionClicked(data);
              }}
              dataTestId="dynamic-list-remove-button"
            />
          )}
        </div>
      )}
    </div>
  );
};
const renderData = <T extends Data>(
  column: DynamicListColumn<T>,
  data: T,
): { columnData: React.ReactNode; tooltip: string | undefined } => {
  const getTooltip = (value: unknown): string | undefined =>
    column.tooltip !== false ? getTooltipText(value) : undefined;

  if (!column.propertyName) {
    const columnData = column.render?.(undefined, data);
    return { columnData, tooltip: getTooltip(columnData) };
  }
  const value: unknown = data[column.propertyName];
  if (column.render) {
    const columnData = column.render(value, data);
    return { columnData, tooltip: getTooltip(columnData) };
  }

  if (value === null || value === undefined) {
    return { columnData: <span />, tooltip: undefined };
  }

  return { columnData: String(value), tooltip: getTooltip(String(value)) };
};
