import { generateGuid } from '@vev/utils';
import { isString, upperFirst } from 'lodash';
import React, {
  Children,
  HTMLAttributes,
  InputHTMLAttributes,
  ReactNode,
  useEffect,
  useMemo,
  useRef,
} from 'react';
import { useUniqId } from '../../hooks';
import { useFormField } from '../../hooks/form';
import { FormFieldProps } from '../silke-form';
import { SilkeTextFieldItem } from './silke-text-field-item';
import { SilkeTextFieldOutline, SilkeTextFieldOutlineProps } from './silke-text-field-outline';
import styles from './silke-text-field.scss';
import { useTextFieldContext } from './text-field-context';

export type SilkeInputSize = 's' | 'base';

type BaseProps<T> = FormFieldProps<T> &
  SilkeTextFieldOutlineProps & {
    autocomplete?: boolean;
    autoFocus?: boolean;
    /** Field required input (if string the string will be used as error msg) */
    required?: boolean | string;
    maxLength?: number;
    minLength?: number;
    type?: string;
  };

type OmitAttrs =
  | 'name'
  | 'value'
  | 'size'
  | 'kind'
  | 'children'
  | 'required'
  | 'onChange'
  | 'onFocus'
  | 'readonly'
  | 'onBlur';

type MultilineProps = {
  multiline: boolean | number | undefined;
  type?: string;
} & BaseProps<string> &
  Omit<HTMLAttributes<HTMLTextAreaElement>, OmitAttrs>;
export type SingleLineProps = {
  multiline?: undefined | false;
} & BaseProps<string> &
  Omit<InputHTMLAttributes<HTMLInputElement>, OmitAttrs>;

type SingleLineNumberProps = { multiline?: undefined | false; type: 'number' } & BaseProps<number> &
  Omit<InputHTMLAttributes<HTMLInputElement>, OmitAttrs>;

export type SilkeTextFieldProps = SingleLineProps | MultilineProps | SingleLineNumberProps;

const validateText = (props: SilkeTextFieldProps, value: string | number | undefined) => {
  if (props.required && !value)
    return isString(props.required) ? props.required : 'Please fill out this field';
  if (isString(value) && props.minLength && (value.length || 0) < props.minLength)
    return `This field must be at least ${props.minLength} characters`;
};

export const SilkeTextField = React.forwardRef<HTMLDivElement, SilkeTextFieldProps>(
  (props, ref) => {
    const {
      autoFocus,
      autocomplete,
      children,
      className,
      disabled,
      error,
      flex,
      help,
      inline,
      kind,
      multiline,
      name,
      onChange,
      onError,
      placeholder,
      readOnly,
      size,
      tooltip,
      validate,
      validator,
      value,
      width,
      label = upperFirst(name),
      ...rest
    } = useFormField(props, validateText);

    const context = useTextFieldContext();

    const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null);
    const helpId = useMemo(() => {
      return `help-${generateGuid()}`;
    }, []);

    const uid = useUniqId('silke-input');
    const inputId = rest.id || uid;

    const { type } = rest as Omit<InputHTMLAttributes<HTMLInputElement>, OmitAttrs>;

    useEffect(() => {
      if (autoFocus && inputRef && inputRef.current) {
        const inputField = inputRef.current;
        // Need to wrap this in setTimeout to increase the chance of it being focused when inside a popover
        setTimeout(() => {
          inputField.focus();
          // For some strange reason, you can't do range select on email and password fields
          if (!type || type === 'text') inputField.setSelectionRange(0, inputField.value.length);
        }, 0);
      }
    }, [autoFocus]);

    const before: ReactNode[] = [];
    const after: ReactNode[] = [];
    Children.forEach(children, (child) => {
      if (React.isValidElement(child) && child.type === SilkeTextFieldItem && child.props.before) {
        before.push(child);
      } else {
        after.push(child);
      }
    });

    const inputStyle = { ...rest.style };
    if (width) inputStyle.width = width;

    const maxLengthHelp =
      rest.maxLength && isString(value) && `${value?.length || 0}/${rest.maxLength}`;

    return (
      <SilkeTextFieldOutline
        size={size}
        kind={kind}
        ref={ref}
        className={className}
        error={error}
        inline={inline}
        helpId={helpId}
        labelId={inputId}
        help={help}
        helpRight={maxLengthHelp}
        label={label}
        flex={flex}
        disabled={disabled}
        tooltip={tooltip}
      >
        {before}
        {React.createElement(multiline ? 'textarea' : 'input', {
          ...rest,
          disabled,
          readOnly: readOnly || context.readOnly,
          ref: inputRef,
          className: multiline ? styles.input + ' ' + styles.inputMultiline : styles.input,
          placeholder,
          id: inputId,
          name,
          style: inputStyle,
          value: value ?? '',
          rows: multiline === true ? 2 : multiline || 2,
          autoComplete: autocomplete ? 'on' : 'off',
          ...(autoFocus && { autofocus: 'true' }),
          'aria-invalid': !!error,
          'aria-describedby': help ? helpId : undefined,
          onChange: (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
            onChange(event.target.value);
          },
        })}
        {after}
      </SilkeTextFieldOutline>
    );
  },
);

SilkeTextField.displayName = 'SilkeTextField';
