import { clsx } from 'clsx';
import { useId } from 'react';

import { Sentiment } from '../common';
import InlineAlert from '../inlineAlert/InlineAlert';
import {
  FieldLabelIdContextProvider,
  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;
  required?: boolean;
  /** @deprecated use `description` prop instead */
  hint?: React.ReactNode;
  message?: React.ReactNode;
  description?: React.ReactNode;
  /** @deprecated use `message` and `type={Sentiment.NEGATIVE}` prop instead */
  error?: React.ReactNode;
  sentiment?: `${Sentiment.NEGATIVE | Sentiment.NEUTRAL | Sentiment.POSITIVE | Sentiment.WARNING}`;
  className?: string;
  children?: React.ReactNode;
};

export const Field = ({
  id,
  label,
  required = true,
  message: propMessage,
  hint,
  description = hint,
  sentiment: propType = Sentiment.NEUTRAL,
  className,
  children,
  ...props
}: FieldProps) => {
  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();

  /**
   * 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);
    }
    return messageIds.length > 0 ? messageIds.join(' ') : undefined;
  }

  return (
    <FieldLabelIdContextProvider value={labelId}>
      <InputIdContextProvider value={inputId}>
        <InputDescribedByProvider value={ariaDescribedbyByIds()}>
          <InputInvalidProvider value={hasError}>
            <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 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 && (
                <InlineAlert type={sentiment} id={messageId}>
                  {message}
                </InlineAlert>
              )}
            </div>
          </InputInvalidProvider>
        </InputDescribedByProvider>
      </InputIdContextProvider>
    </FieldLabelIdContextProvider>
  );
};
