import { ReactNode, useMemo } from 'react';

import { IconGlyph } from '../../components/Icon/constants';
import { IconHelp } from '../../components/IconHelp/IconHelp';
import { useTranslation } from '../../core/hooks/useTranslation';
import { useTestIdAttribute } from '../../hooks/useTestIdAttribute';
import { CommonProps } from '../../types';
import { assertEmptyObject } from '../../utils/assertEmptyObject';
import { makeTestId } from '../../utils/makeTestId';

import { FormMessage } from './components/FormMessage/FormMessage';
import { FormFieldDirection, FormMessageType } from './constants';
import {
  StyledHorizontalFieldContainer,
  StyledHorizontalFieldControl,
  StyledHorizontalFieldLabel,
  StyledHorizontalFieldLabelContainer,
  StyledHorizontalFieldMessage,
  StyledHorizontalFormItem,
  StyledVerticalFieldLabel,
  StyledVerticalFieldLabelContainer,
  StyledVerticalFormItem,
  TunedRequiredMark,
} from './styled';

/** Props for {@link FormField} component */
export interface FormFieldProps extends CommonProps {
  /** ID of field */
  id: string;
  /**
   * ID of error message.
   *
   * The same ID should be pass to aria-errormessage of field control.
   *
   * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-errormessage
   */
  errorMessageId?: string;
  /**
   * ID of description message.
   *
   * The same ID should be pass to aria-describedby of field control.
   *
   * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-describedby
   */
  descriptionMessageId?: string;
  /**
   * Label for field.
   *
   * If not set, don't forget to add `aria-label` to form control for a11y.
   */
  label?: string;
  /** Text of description at the bottom */
  description?: string;
  /** Show required mark, if given true. Show optional - if false. Show nothing, if not given. */
  requirement?: boolean;
  /** It shows a help icon with tooltip next to the label. */
  hintText?: string;
  /** It shows a help icon next to the label. Clicking on the icon calls the action. */
  onHintClick?: () => void;
  /** Text of error */
  error?: string;
  /** Direction of field */
  direction?: FormFieldDirection;
  /** Field */
  children: ReactNode;
}

/**
 * FormField is a component that defines the general look and logic of the field- and switch-like
 * components that includes FormikTextField, FormikSelectField, FormikFileUpload, FormikCheckbox,
 * FormikSwitch, and others.
 *
 * ```tsx
 * import { FormField } from 'ui-kit';
 *
 * <FormField {...props}>
 *   <TextField {...inputProps} />
 * </FormField>
 * ```
 */
export function FormField(props: FormFieldProps) {
  const {
    id,
    label,
    direction,
    children,
    error,
    description,
    requirement,
    hintText,
    onHintClick,
    descriptionMessageId,
    errorMessageId,
    className,
    testId,
    ariaDescribedBy,
    ...rest
  } = props;
  assertEmptyObject(rest);

  const testIdAttribute = useTestIdAttribute();

  const { t } = useTranslation();

  if ((requirement !== undefined || hintText) && !label) {
    throw Error("Fields 'requirement', 'hintText' can't be used without 'label'");
  }

  if (onHintClick && !hintText) {
    throw Error("Action 'onHintClick' can't be used without 'hintText'");
  }

  const { componentsAfterLabel, ariaLabel } = useMemo(() => {
    if (!label) {
      return {};
    }

    let fieldAriaLabel = label;

    const components: ReactNode[] = [];

    if (requirement === true) {
      components.push(<TunedRequiredMark key="required" glyph={IconGlyph.Asterisk} />);
      fieldAriaLabel += t('ui.formField.requiredAria');
    } else if (requirement === false) {
      fieldAriaLabel += t('ui.formField.optionalAria');
    }

    if (hintText) {
      components.push(
        <IconHelp
          key="iconHelp"
          ariaLabel={t('ui.formField.helpIconAria')}
          hintText={hintText}
          onClick={onHintClick}
        />,
      );
    }

    return {
      componentsAfterLabel: components,
      ariaLabel: fieldAriaLabel,
    };
  }, [t, label, requirement, hintText, onHintClick]);

  if (direction === FormFieldDirection.Horizontal) {
    return (
      <StyledHorizontalFieldContainer
        aria-describedby={ariaDescribedBy}
        className={className}
        {...{ [testIdAttribute]: testId }}
      >
        <StyledHorizontalFormItem>
          <StyledHorizontalFieldControl>{children}</StyledHorizontalFieldControl>
          {!!label && (
            <StyledHorizontalFieldLabelContainer>
              <StyledHorizontalFieldLabel
                aria-label={ariaLabel}
                htmlFor={id}
                {...{ [testIdAttribute]: makeTestId(testId, 'label') }}
              >
                {label}
              </StyledHorizontalFieldLabel>
              {componentsAfterLabel}
            </StyledHorizontalFieldLabelContainer>
          )}
          <StyledHorizontalFieldMessage>
            <FormMessage
              id={error ? errorMessageId : descriptionMessageId}
              testId={makeTestId(testId, 'error')}
              type={error ? FormMessageType.Error : FormMessageType.Description}
            >
              {error ?? description}
            </FormMessage>
          </StyledHorizontalFieldMessage>
        </StyledHorizontalFormItem>
      </StyledHorizontalFieldContainer>
    );
  }

  return (
    <StyledVerticalFormItem
      aria-describedby={ariaDescribedBy}
      className={className}
      {...{ [testIdAttribute]: testId }}
    >
      {!!label && (
        <StyledVerticalFieldLabelContainer>
          <StyledVerticalFieldLabel
            aria-label={ariaLabel}
            htmlFor={id}
            {...{ [testIdAttribute]: makeTestId(testId, 'label') }}
          >
            {label}
          </StyledVerticalFieldLabel>
          {componentsAfterLabel}
        </StyledVerticalFieldLabelContainer>
      )}
      {children}
      <FormMessage
        id={error ? errorMessageId : descriptionMessageId}
        testId={makeTestId(testId, 'error')}
        type={error ? FormMessageType.Error : FormMessageType.Description}
      >
        {error ?? description}
      </FormMessage>
    </StyledVerticalFormItem>
  );
}
