import * as React from 'react';
import { TagsInput } from 'react-tag-input-component';
import * as TextControlBase from '@tesseract-design/web-base-textcontrol';
import clsx from 'clsx';
import {useEnhanced} from '@/packages/react-utils';
import styles from './style.module.css';
import {delegateTriggerEvent} from '@/utils/event';

type TagInputSeparator = ',' | '\n';

export type TagInputDerivedElement = HTMLTextAreaElement;

export interface TagInputProps extends Omit<React.HTMLProps<TagInputDerivedElement>, 'size' | 'type' | 'style' | 'label' | 'list'> {
  /**
   * Short textual description indicating the nature of the component's value.
   */
  label?: React.ReactNode,
  /**
   * Short textual description as guidelines for valid input values.
   */
  hint?: React.ReactNode,
  /**
   * Size of the component.
   */
  size?: TextControlBase.TextControlSize,
  /**
   * Additional description, usually graphical, indicating the nature of the component's value.
   */
  indicator?: React.ReactNode,
  /**
   * Should the component display a border?
   */
  border?: boolean,
  /**
   * Should the component occupy the whole width of its parent?
   */
  block?: boolean,
  /**
   * Type of the component value.
   */
  type?: TextControlBase.TextControlInputType,
  /**
   * Style of the component.
   */
  variant?: TextControlBase.TextControlVariant,
  /**
   * Is the label hidden?
   */
  hiddenLabel?: boolean,
  enhanced?: boolean,
  separator?: TagInputSeparator[],
}

/**
 * Component for inputting textual values.
 *
 * This component supports multiline input and adjusts its layout accordingly.
 */
export const TagInput = React.forwardRef<TagInputDerivedElement, TagInputProps>(
  (
    {
      label = '',
      hint = '',
      indicator,
      size = 'medium' as const,
      border = false,
      block = false,
      type = 'text' as const,
      variant = 'default' as const,
      hiddenLabel = false,
      className,
      enhanced: enhancedProp = false,
      separator = ['\n'],
      defaultValue,
      disabled,
      ...etcProps
    }: TagInputProps,
    forwardedRef,
  ) => {
    const {enhanced} = useEnhanced({ enhanced: enhancedProp });
    const defaultRef = React.useRef<TagInputDerivedElement>(null);
    const ref = forwardedRef ?? defaultRef;
    const labelId = React.useId();
    const tags = React.useMemo(() => {
      if (defaultValue === undefined) {
        return [];
      }
      if (typeof defaultValue === 'string') {
        return separator.reduce((theDefaultValues, separator) => {
          return theDefaultValues.join(separator).split(separator);
        }, [defaultValue]);
      }
      if (typeof defaultValue === 'number') {
        return [defaultValue.toString()];
      }
      return defaultValue as string[];
    }, [defaultValue, separator]);

    const handleTagsInputChange = (tags: string[]) => {
      if (!(typeof ref === 'object' && ref)) {
        return;
      }
      const { current: input } = ref;
      if (!input) {
        return;
      }
      setTimeout(() => {
        delegateTriggerEvent('change', input, tags.map((t => t.trim())).join('\n'));
      });
    };

    const handleBlur = () => {
      if (!(typeof ref === 'object' && ref)) {
        return;
      }
      const { current: input } = ref;
      if (!input) {
        return;
      }
      setTimeout(() => {
        delegateTriggerEvent('blur', input);
      });
    }

    return (
      <div
        className={clsx(
          'relative rounded ring-secondary/50 group',
          'focus-within:ring-4',
          {
            'block': block,
            'inline-block align-middle': !block,
          },
          enhanced && {
            'min-h-10': size === 'small',
            'min-h-12': size === 'medium',
            'min-h-16': size === 'large',
          },
          styles['tag-input'],
          size === 'small' && styles['tag-input-small'],
          size === 'medium' && styles['tag-input-medium'],
          size === 'large' && styles['tag-input-large'],
          indicator && styles['tag-input-indicator'],
          variant === 'default' && styles['tag-input-default'],
          variant === 'alternate' && styles['tag-input-alternate'],
          className,
        )}
      >
        <textarea
          {...etcProps}
          disabled={disabled}
          ref={ref}
          aria-labelledby={labelId}
          data-testid="input"
          defaultValue={defaultValue}
          style={{
            height: enhanced ? undefined : 0,
          }}
          className={clsx(
            'bg-negative rounded-inherit peer block',
            'focus:outline-0',
            'disabled:opacity-50 disabled:cursor-not-allowed',
            {
              'resize': !block,
              'resize-y': block,
            },
            {
              'text-xxs': size === 'small',
              'text-xs': size === 'medium',
            },
            !enhanced && {
              'pl-4': variant === 'default',
              'pl-1.5': variant === 'alternate',
            },
            !enhanced && {
              'pt-4': variant === 'alternate' && size === 'small',
              'pt-5': variant === 'alternate' && size === 'medium',
              'pt-8': variant === 'alternate' && size === 'large',
            },
            !enhanced && {
              'py-2.5': variant === 'default' && size === 'small',
              'py-3': variant === 'default' && size === 'medium',
              'py-5': variant === 'default' && size === 'large',
            },
            !enhanced && {
              'pr-4': variant === 'default' && !indicator,
              'pr-1.5': variant === 'alternate' && !indicator,
            },
            !enhanced && {
              'pr-10': indicator && size === 'small',
              'pr-12': indicator && size === 'medium',
              'pr-16': indicator && size === 'large',
            },
            !enhanced && {
              'min-h-10': size === 'small',
              'min-h-12': size === 'medium',
              'min-h-16': size === 'large',
            },
            !enhanced && 'peer',
            !enhanced && 'w-full',
            enhanced && 'sr-only',
          )}
        />
        {enhanced && (
          <TagsInput
            value={tags}
            classNames={{
              input: 'peer bg-transparent',
              tag: 'text-xs p-2',
            }}
            disabled={disabled}
            onChange={handleTagsInputChange}
            onBlur={handleBlur}
            separators={separator.map((separator) => separator === '\n' ? 'Enter' : separator)}
          />
        )}
        {
          label && (
            <label
              data-testid="label"
              id={labelId}
              className={clsx(
                'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 group-focus-within:text-secondary text-primary leading-none bg-negative',
                {
                  'sr-only': hiddenLabel,
                },
                {
                  'pr-1': !indicator,
                },
                {
                  'pr-10': indicator && size === 'small',
                  'pr-12': indicator && size === 'medium',
                  'pr-16': indicator && size === 'large',
                },
              )}
            >
              <span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
                {label}
              </span>
            </label>
          )
        }
        {hint && (
          <div
            data-testid="hint"
            className={clsx(
              'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative',
              {
                'bottom-0 pl-4 pb-1': variant === 'default',
                'top-0.5': variant === 'alternate',
              },
              {
                'pt-2': variant === 'alternate' && size === 'small',
                'pt-3': variant === 'alternate' && size !== 'small',
              },
              {
                'pr-4': !indicator && variant === 'default',
                'pr-1': !indicator && variant === 'alternate',
              },
              {
                'pr-10': indicator && size === 'small',
                'pr-12': indicator && size === 'medium',
                'pr-16': indicator && size === 'large',
              },
            )}
          >
            <div
              className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
            >
              {hint}
            </div>
          </div>
        )}
        {indicator && (
          <div
            className={clsx(
              'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none',
              {
                'w-10': size === 'small',
                'w-12': size === 'medium',
                'w-16': size === 'large',
              },
            )}
          >
            {indicator}
          </div>
        )}
        {
          border && (
            <span
              data-testid="border"
              className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none group-focus-within:border-secondary"
            />
          )
        }
      </div>
    );
  }
);

TagInput.displayName = 'TagInput';
