import { css, cx } from '@emotion/css';
import { useCallback, useState, forwardRef } from 'react';
import * as React from 'react';

import { type GrafanaTheme2 } from '@grafana/data';
import { t, Trans } from '@grafana/i18n';

import { useStyles2, useTheme2 } from '../../themes/ThemeContext';
import { Button } from '../Button/Button';
import { Input } from '../Input/Input';

import { TagItem } from './TagItem';

export interface Props {
  placeholder?: string;
  /** Array of selected tags */
  tags?: string[];
  onChange: (tags: string[]) => void;
  width?: number;
  id?: string;
  className?: string;
  /** Toggle disabled state */
  disabled?: boolean;
  /** Enable adding new tags when input loses focus */
  addOnBlur?: boolean;
  /** Toggle invalid state */
  invalid?: boolean;
  /** Colours the tags 'randomly' based on the name. Defaults to true */
  autoColors?: boolean;
}

/**
 * A set of an input field and a button next to it that allows the user to add new tags. The added tags are previewed next to the input and can be removed by clicking the "X" icon. You can customize the width of the input.
 *
 * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-tagsinput--docs
 */
export const TagsInput = forwardRef<HTMLInputElement, Props>(
  (
    {
      placeholder: placeholderProp,
      tags = [],
      onChange,
      width,
      className,
      disabled,
      addOnBlur,
      invalid,
      id,
      autoColors = true,
    },
    ref
  ) => {
    const placeholder = placeholderProp ?? t('grafana-ui.tags-input.placeholder-new-tag', 'New tag (enter key to add)');
    const [newTagName, setNewTagName] = useState('');
    const styles = useStyles2(getStyles);
    const theme = useTheme2();
    const isTagTooLong = newTagName.length > 50;

    const onNameChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
      setNewTagName(event.target.value);
    }, []);

    const onRemove = (tagToRemove: string) => {
      onChange(tags.filter((x) => x !== tagToRemove));
    };

    const onAdd = (event?: React.MouseEvent | React.KeyboardEvent) => {
      event?.preventDefault();
      if (newTagName.length > 50) {
        return;
      }
      if (!tags.includes(newTagName)) {
        onChange(tags.concat(newTagName));
      }
      setNewTagName('');
    };

    const onBlur = () => {
      if (addOnBlur && newTagName) {
        onAdd();
      }
    };

    const onKeyboardAdd = (event: React.KeyboardEvent) => {
      if (event.key === 'Enter' && newTagName !== '') {
        onAdd(event);
      }
    };

    return (
      <div className={cx(styles.wrapper, className, width ? css({ width: theme.spacing(width) }) : '')}>
        <Input
          ref={ref}
          id={id}
          disabled={disabled}
          placeholder={placeholder}
          onChange={onNameChange}
          value={newTagName}
          onKeyDown={onKeyboardAdd}
          onBlur={onBlur}
          invalid={invalid || isTagTooLong}
          suffix={
            <Button
              fill="text"
              className={styles.addButtonStyle}
              onClick={onAdd}
              size="md"
              disabled={newTagName.length <= 0 || isTagTooLong}
              title={
                isTagTooLong ? t('grafana-ui.tags-input.tag-too-long', 'Tag too long, max 50 characters') : undefined
              }
            >
              <Trans i18nKey="grafana-ui.tags-input.add">Add</Trans>
            </Button>
          }
        />
        {tags?.length > 0 && (
          <ul className={styles.tags}>
            {tags.map((tag) => (
              <TagItem key={tag} name={tag} onRemove={onRemove} disabled={disabled} autoColors={autoColors} />
            ))}
          </ul>
        )}
      </div>
    );
  }
);

TagsInput.displayName = 'TagsInput';

const getStyles = (theme: GrafanaTheme2) => ({
  wrapper: css({
    minHeight: theme.spacing(4),
    display: 'flex',
    flexDirection: 'column',
    gap: theme.spacing(1),
    flexWrap: 'wrap',
  }),
  tags: css({
    display: 'flex',
    justifyContent: 'flex-start',
    flexWrap: 'wrap',
    gap: theme.spacing(0.5),
  }),
  addButtonStyle: css({
    margin: `0 -${theme.spacing(1)}`,
  }),
});
