import { clsx } from 'clsx';
import { useCallback, useId, useRef, useState } from 'react';
import { useIntl } from 'react-intl';

import Body from '../body';
import { Sentiment } from '../common';
import messages from './Field.messages';
import { InlinePrompt, type InlinePromptProps } from '../prompt';
import {
  TextareaCharacterCountProvider,
  type TextareaCharacterCountState,
  FieldLabelContextProvider,
  InputDescribedByProvider,
  InputIdContextProvider,
  InputInvalidProvider,
} from '../inputs/contexts';
import { Label } from '../label';

export type FieldProps = {
  /** `null` disables auto-generating the `id` attribute, falling back to nesting-based label association over setting `htmlFor` explicitly. */
  id?: string | null;
  /** Should be specified unless the wrapped control has its own labeling mechanism, e.g. `Checkbox`. */
  label?: React.ReactNode;
  /** @default true */
  required?: boolean;
  /** @deprecated Use `description` prop instead. */
  hint?: React.ReactNode;
  message?: React.ReactNode;
  /**
   * Override for the [InlinePrompt icon's default, accessible name](/?path=/docs/other-statusicon-accessibility--docs)
   * announced by the screen readers
   * */
  messageIconLabel?: string;
  /**
   * If true, shows a loading spinner in place of the message icon of the InlinePrompt
   * @default false
   */
  messageLoading?: boolean;
  description?: React.ReactNode;
  /** @deprecated Use `message` and `type={Sentiment.NEGATIVE}` prop instead. */
  error?: React.ReactNode;
  /** @default Sentiment.NEUTRAL */
  sentiment?: InlinePromptProps['sentiment'];
  className?: string;
  children?: React.ReactNode;
};

export const Field = ({
  id,
  label,
  required = true,
  message: propMessage,
  messageIconLabel,
  messageLoading,
  hint,
  description = hint,
  sentiment: propType = Sentiment.NEUTRAL,
  className,
  children,
  ...props
}: FieldProps) => {
  const { formatMessage } = useIntl();
  const labelRef = useRef<HTMLLabelElement>(null);
  const sentiment = props.error ? Sentiment.NEGATIVE : propType;
  const message = propMessage || props.error;
  const hasError = sentiment === Sentiment.NEGATIVE;

  const labelId = useId();

  const fallbackInputId = useId();
  const inputId = id !== null ? (id ?? fallbackInputId) : undefined;

  const messageId = useId();
  const descriptionId = useId();
  const textareaCharCounterId = useId();

  const [textareaCharacterCount, setTextareaCharacterCount] =
    useState<TextareaCharacterCountState>(null);
  const handleTextareaCharacterCount = useCallback(
    (state: TextareaCharacterCountState) => setTextareaCharacterCount(state),
    [],
  );

  const isNearCharLimit =
    textareaCharacterCount != null &&
    textareaCharacterCount.current >= textareaCharacterCount.max * 0.8;

  /**
   * form control can have multiple messages to describe it,
   * e.g the description underneath the label and inline alert
   */
  function ariaDescribedbyByIds() {
    const messageIds = [];
    if (description) {
      messageIds.push(descriptionId);
    }
    if (message) {
      messageIds.push(messageId);
    }
    if (textareaCharacterCount) {
      messageIds.push(textareaCharCounterId);
    }
    return messageIds.length > 0 ? messageIds.join(' ') : undefined;
  }

  return (
    <FieldLabelContextProvider value={{ id: labelId, ref: labelRef }}>
      <InputIdContextProvider value={inputId}>
        <InputDescribedByProvider value={ariaDescribedbyByIds()}>
          <InputInvalidProvider value={hasError}>
            <TextareaCharacterCountProvider value={handleTextareaCharacterCount}>
              <div
                className={clsx(
                  'np-field form-group d-block',
                  {
                    'has-success': sentiment === Sentiment.POSITIVE,
                    'has-warning': sentiment === Sentiment.WARNING,
                    'has-error': hasError,
                    'has-info': sentiment === Sentiment.NEUTRAL,
                  },
                  className,
                )}
              >
                {label != null ? (
                  <>
                    <Label ref={labelRef} id={labelId} htmlFor={inputId}>
                      {required ? label : <Label.Optional>{label}</Label.Optional>}
                    </Label>
                    <Label.Description id={descriptionId}>{description}</Label.Description>
                    <div className="np-field-control">{children}</div>
                  </>
                ) : (
                  children
                )}

                {(message || textareaCharacterCount) && (
                  <div className="np-field-validation">
                    {message && (
                      <InlinePrompt
                        sentiment={sentiment}
                        id={messageId}
                        mediaLabel={messageIconLabel}
                        className="np-field__prompt"
                        loading={messageLoading}
                        width="full"
                      >
                        {message}
                      </InlinePrompt>
                    )}
                    {textareaCharacterCount && (
                      <Body
                        as="span"
                        id={textareaCharCounterId}
                        {...(isNearCharLimit
                          ? {
                              role: 'status' as const,
                              'aria-live': 'polite' as const,
                              'aria-atomic': 'true' as const,
                            }
                          : {})}
                        aria-label={formatMessage(messages.characterCount, {
                          current: textareaCharacterCount.current,
                          max: textareaCharacterCount.max,
                        })}
                        className="np-field-textarea-char-counter"
                      >
                        {textareaCharacterCount.current}/{textareaCharacterCount.max}
                      </Body>
                    )}
                  </div>
                )}
              </div>
            </TextareaCharacterCountProvider>
          </InputInvalidProvider>
        </InputDescribedByProvider>
      </InputIdContextProvider>
    </FieldLabelContextProvider>
  );
};
