import { css } from '@emotion/css';
import { Draggable } from '@hello-pangea/dnd';
import { useCallback, useEffect, useRef } from 'react';
import * as React from 'react';

import { GrafanaTheme2, MappingType, SpecialValueMatch, SelectableValue, ValueMappingResult } from '@grafana/data';
import { useStyles2, Icon, Select, HorizontalGroup, ColorPicker, IconButton, Input, Button } from '@grafana/ui';

import { ResourcePickerSize, ResourceFolderName, MediaType } from '../../types';
import { ResourcePicker } from '../ResourcePicker';

export interface ValueMappingEditRowModel {
  type: MappingType;
  from?: number;
  to?: number;
  pattern?: string;
  key?: string;
  isNew?: boolean;
  specialMatch?: SpecialValueMatch;
  result: ValueMappingResult;
  id: string;
}

interface Props {
  mapping: ValueMappingEditRowModel;
  index: number;
  onChange: (index: number, mapping: ValueMappingEditRowModel) => void;
  onRemove: (index: number) => void;
  onDuplicate: (index: number) => void;
  showIconPicker?: boolean;
}

export function ValueMappingEditRow({ mapping, index, onChange, onRemove, onDuplicate, showIconPicker }: Props) {
  const { key, result, id } = mapping;
  const styles = useStyles2(getStyles);
  const inputRef = useRef<HTMLInputElement | null>(null);

  const update = useCallback(
    (fn: (item: ValueMappingEditRowModel) => void) => {
      const copy = {
        ...mapping,
        result: {
          ...mapping.result,
        },
      };
      fn(copy);
      onChange(index, copy);
    },
    [mapping, index, onChange]
  );

  useEffect(() => {
    if (inputRef.current && mapping.isNew) {
      inputRef.current.focus();
      update((mapping) => {
        mapping.isNew = false;
      });
    }
  }, [mapping, inputRef, update]);

  const onChangeColor = (color: string) => {
    update((mapping) => {
      mapping.result.color = color;
    });
  };

  const onClearColor = () => {
    update((mapping) => {
      mapping.result.color = undefined;
    });
  };

  const onChangeIcon = (icon?: string) => {
    update((mapping) => {
      mapping.result.icon = icon;
    });
  };

  const onClearIcon = () => {
    update((mapping) => {
      mapping.result.icon = undefined;
    });
  };

  const onUpdateMatchValue = (event: React.FormEvent<HTMLInputElement>) => {
    update((mapping) => {
      mapping.key = event.currentTarget.value;
    });
  };

  const onChangeText = (event: React.FormEvent<HTMLInputElement>) => {
    update((mapping) => {
      mapping.result.text = event.currentTarget.value;
    });
  };

  const onChangeFrom = (event: React.FormEvent<HTMLInputElement>) => {
    update((mapping) => {
      mapping.from = parseFloat(event.currentTarget.value);
    });
  };

  const onChangeTo = (event: React.FormEvent<HTMLInputElement>) => {
    update((mapping) => {
      mapping.to = parseFloat(event.currentTarget.value);
    });
  };

  const onChangePattern = (event: React.FormEvent<HTMLInputElement>) => {
    update((mapping) => {
      mapping.pattern = event.currentTarget.value;
    });
  };

  const onChangeSpecialMatch = (sel: SelectableValue<SpecialValueMatch>) => {
    update((mapping) => {
      mapping.specialMatch = sel.value;
    });
  };

  const specialMatchOptions: Array<SelectableValue<SpecialValueMatch>> = [
    { label: 'Null', value: SpecialValueMatch.Null, description: 'Matches null and undefined values' },
    { label: 'NaN', value: SpecialValueMatch.NaN, description: 'Matches against Number.NaN (not a number)' },
    { label: 'Null + NaN', value: SpecialValueMatch.NullAndNaN, description: 'Matches null, undefined and NaN' },
    { label: 'True', value: SpecialValueMatch.True, description: 'Boolean true values' },
    { label: 'False', value: SpecialValueMatch.False, description: 'Boolean false values' },
    { label: 'Empty', value: SpecialValueMatch.Empty, description: 'Empty string' },
  ];

  return (
    <Draggable key={id} draggableId={id} index={index}>
      {(provided) => (
        <tr className={styles.dragRow} ref={provided.innerRef} {...provided.draggableProps}>
          <td>
            <div className={styles.dragHandle} {...provided.dragHandleProps}>
              <Icon name="draggabledots" size="lg" />
            </div>
          </td>
          <td className={styles.typeColumn}>{mapping.type}</td>
          <td>
            {mapping.type === MappingType.ValueToText && (
              <Input
                ref={inputRef}
                type="text"
                value={key ?? ''}
                onChange={onUpdateMatchValue}
                placeholder="Exact value to match"
              />
            )}
            {mapping.type === MappingType.RangeToText && (
              <div className={styles.rangeInputWrapper}>
                <Input
                  type="number"
                  value={mapping.from ?? ''}
                  placeholder="Range start"
                  onChange={onChangeFrom}
                  prefix="From"
                />
                <Input
                  type="number"
                  value={mapping.to ?? ''}
                  placeholder="Range end"
                  onChange={onChangeTo}
                  prefix="To"
                />
              </div>
            )}
            {mapping.type === MappingType.RegexToText && (
              <Input
                type="text"
                value={mapping.pattern ?? ''}
                placeholder="Regular expression"
                onChange={onChangePattern}
              />
            )}
            {mapping.type === MappingType.SpecialValue && (
              <Select
                value={specialMatchOptions.find((v) => v.value === mapping.specialMatch)}
                options={specialMatchOptions}
                onChange={onChangeSpecialMatch}
              />
            )}
          </td>
          <td>
            <Input type="text" value={result.text ?? ''} onChange={onChangeText} placeholder="Optional display text" />
          </td>
          <td className={styles.textAlignCenter}>
            {result.color && (
              <HorizontalGroup spacing="sm" justify="center">
                <ColorPicker color={result.color} onChange={onChangeColor} enableNamedColors={true} />
                <IconButton name="times" onClick={onClearColor} tooltip="Remove color" tooltipPlacement="top" />
              </HorizontalGroup>
            )}
            {!result.color && (
              <ColorPicker color={'gray'} onChange={onChangeColor} enableNamedColors={true}>
                {(props) => (
                  <Button variant="primary" fill="text" onClick={props.showColorPicker} ref={props.ref} size="sm">
                    Set color
                  </Button>
                )}
              </ColorPicker>
            )}
          </td>
          {showIconPicker && (
            <td className={styles.textAlignCenter}>
              <HorizontalGroup spacing="sm" justify="center">
                <ResourcePicker
                  onChange={onChangeIcon}
                  onClear={onClearIcon}
                  value={result.icon}
                  size={ResourcePickerSize.SMALL}
                  folderName={ResourceFolderName.Icon}
                  mediaType={MediaType.Icon}
                  color={result.color}
                />
                {result.icon && (
                  <IconButton name="times" onClick={onClearIcon} tooltip="Remove icon" tooltipPlacement="top" />
                )}
              </HorizontalGroup>
            </td>
          )}
          <td className={styles.textAlignCenter}>
            <HorizontalGroup spacing="sm">
              <IconButton
                name="copy"
                onClick={() => onDuplicate(index)}
                data-testid="duplicate-value-mapping"
                aria-label="Duplicate value mapping"
                tooltip="Duplicate"
              />
              <IconButton
                name="trash-alt"
                onClick={() => onRemove(index)}
                data-testid="remove-value-mapping"
                aria-label="Delete value mapping"
                tooltip="Delete"
              />
            </HorizontalGroup>
          </td>
        </tr>
      )}
    </Draggable>
  );
}

const getStyles = (theme: GrafanaTheme2) => ({
  dragRow: css({
    position: 'relative',
  }),
  dragHandle: css({
    cursor: 'grab',
    // create focus ring around the whole row when the drag handle is tab-focused
    // needs position: relative on the drag row to work correctly
    '&:focus-visible&:after': {
      bottom: 0,
      content: '""',
      left: 0,
      position: 'absolute',
      right: 0,
      top: 0,
      outline: `2px solid ${theme.colors.primary.main}`,
      outlineOffset: '-2px',
    },
  }),
  rangeInputWrapper: css({
    display: 'flex',
    '> div:first-child': {
      marginRight: theme.spacing(2),
    },
  }),
  regexInputWrapper: css({
    display: 'flex',
    '> div:first-child': {
      marginRight: theme.spacing(2),
    },
  }),
  typeColumn: css({
    textTransform: 'capitalize',
    textAlign: 'center',
    width: '1%',
  }),
  textAlignCenter: css({
    textAlign: 'center',
  }),
});
