import React from 'react';
import {
  FieldErrorProps,
  InputProps,
  LabelProps,
  FieldError as RACFieldError,
  Input as RACInput,
  Label as RACLabel,
  TextProps,
  composeRenderProps,
  LabelContext,
  GroupContext,
  Group as RACGroup,
  GroupProps,
  TextFieldProps as RACTextFieldProps,
  TextField as RACTextField,
  TextArea as RACTextArea,
  TextAreaProps as RACTextAreaProps,
  Text as RACText,
  SearchField as RACSearchField,
  SearchFieldProps as RACSearchFieldProps,
} from 'react-aria-components';
import { twMerge } from 'tailwind-merge';
import { composeTailwindRenderProps, inputFieldStyle } from './tw-utils';
import { Text } from './tw-text';
import { CloseButton } from './tw-button';

// eslint-disable-next-line react/display-name
export const LabeledGroup = React.forwardRef<HTMLDivElement, GroupProps>(function (props, ref) {
  const labelId = React.useId();

  return (
    <LabelContext.Provider value={{ id: labelId, elementType: 'span' }}>
      <GroupContext.Provider value={{ 'aria-labelledby': labelId }}>
        <RACGroup
          {...props}
          ref={ref}
          className={composeRenderProps(props.className, (className) => {
            return twMerge('relative flex flex-col', className);
          })}
        />
      </GroupContext.Provider>
    </LabelContext.Provider>
  );
});

export function Label({
  requiredHint,
  isDisabled,
  ...props
}: LabelProps & {
  requiredHint?: boolean;
  isDisabled?: boolean;
}) {
  return (
    <RACLabel
      {...props}
      data-slot="label"
      className={twMerge(
        requiredHint && "after:ml-0.5 after:text-red-800 after:content-['*']",
        isDisabled && 'opacity-50',
        props.className,
      )}
    />
  );
}

export function WithLabelContext({
  children,
}: {
  children: (
    context: {
      'aria-labelledBy'?: string;
    } | null,
  ) => React.ReactNode;
}) {
  const context = (React.useContext(LabelContext) ?? {}) as { id?: string };

  return children({
    'aria-labelledBy': context?.id,
  });
}

export const DescriptionContext = React.createContext<{
  'aria-describedby'?: string;
} | null>(null);

export function WithDescriptionContext({
  children,
}: {
  children: (
    context: {
      'aria-describedby'?: string;
    } | null,
  ) => React.ReactNode;
}) {
  const context = React.useContext(DescriptionContext);

  return children(context);
}

export function DescriptionProvider({ children }: { children: React.ReactNode }) {
  const descriptionId: string | null = React.useId();
  const [descriptionRendered, setDescriptionRendered] = React.useState(true);

  React.useLayoutEffect(() => {
    if (!document.getElementById(descriptionId)) {
      setDescriptionRendered(false);
    }
  }, [descriptionId]);

  return (
    <DescriptionContext.Provider
      value={{
        'aria-describedby': descriptionRendered ? descriptionId : undefined,
      }}
    >
      {children}
    </DescriptionContext.Provider>
  );
}

export function Description({ className, ...props }: TextProps) {
  return (
    <WithDescriptionContext>
      {(context) => {
        const describedby = context?.['aria-describedby'];

        return describedby ? (
          <Text
            {...props}
            id={describedby}
            data-slot="description"
            className={twMerge('group-disabled:opacity-50', className)}
          />
        ) : (
          <RACText
            {...props}
            data-slot="description"
            slot="description"
            className={twMerge(
              'text-muted text-pretty text-base/6 sm:text-sm/6',
              'group-disabled:opacity-50',
              className,
            )}
          />
        );
      }}
    </WithDescriptionContext>
  );
}

export function TextField(props: RACTextFieldProps) {
  return (
    <RACTextField
      {...props}
      className={composeTailwindRenderProps(props.className, inputFieldStyle)}
    />
  );
}

export function FieldError(props: FieldErrorProps) {
  return (
    <RACFieldError
      {...props}
      className={composeTailwindRenderProps(
        props.className,
        'text-destructive my-1 text-base/6 sm:text-sm/6',
      )}
    />
  );
}

export const InputFieldGroup = React.forwardRef<HTMLDivElement, GroupProps>(
  function InputFieldGroup(props, ref) {
    return (
      <RACGroup
        {...props}
        data-slot="control"
        ref={ref}
        className={composeRenderProps(props.className, (className) => {
          return twMerge(
            'group relative flex w-full items-center overflow-hidden rounded-md border bg-inherit shadow-sm',
            '[&_svg]:text-muted',
            'group-invalid:border-destructive',

            // Disabled and readonly style
            '[&:has(_[data-disabled=true])]:opacity-50',
            '[&:has([readonly])]:opacity-50',
            // Prevent double opacity
            '[&:has(_[data-disabled=true])_[class*=opacity-]]:opacity-100',
            '[&:has([readonly])_[class*=opacity-]]:opacity-100',

            // Remove inside input/data-input border style
            '[&_:is(input,[data-slot=control])]:border-none',
            '[&_:is(input,[data-slot=control])]:shadow-none',
            '[&_:is(input,[data-slot=control])]:ring-0',

            className,
          );
        })}
      />
    );
  },
);

export const Input = React.forwardRef<HTMLInputElement, InputProps>(function Input(props, ref) {
  return (
    <RACInput
      {...props}
      ref={ref}
      className={composeRenderProps(props.className, (className, { isDisabled, isInvalid }) => {
        return twMerge(
          'flex w-full rounded-md border-none bg-inherit px-2 py-[5px] shadow-none outline-none',
          'text-sm placeholder:text-neutral-600',
          '[&[readonly]]:opacity-50',
          isInvalid && 'border-destructive',
          isDisabled && 'opacity-50',
          className,
        );
      })}
    />
  );
});

export function TextArea(props: RACTextAreaProps) {
  return (
    <RACTextArea
      {...props}
      className={composeRenderProps(props.className, (className, { isDisabled, isInvalid }) => {
        return twMerge(
          'w-full rounded-md border bg-inherit p-2 outline-none',
          'placeholder:text-muted text-base/6 sm:text-sm/6',
          '[&[readonly]]:opacity-50',
          isDisabled && 'opacity-50',
          isInvalid && 'border-destructive',
          className,
        );
      })}
    />
  );
}

export interface SearchFieldProps extends RACSearchFieldProps {}

export function SearchField(props: SearchFieldProps) {
  return (
    <RACSearchField
      {...props}
      className={composeTailwindRenderProps(props.className, inputFieldStyle)}
    ></RACSearchField>
  );
}

export function SearchInput({
  className,
  ...props
}: InputProps & { className?: GroupProps['className'] }) {
  return (
    <InputFieldGroup
      className={composeTailwindRenderProps(className, [
        '[&_input::-webkit-search-cancel-button]:hidden',
      ])}
    >
      <svg
        aria-hidden
        xmlns="http://www.w3.org/2000/svg"
        fill="none"
        viewBox="0 0 24 24"
        strokeWidth={2}
        stroke="currentColor"
        className="ms-2 size-5"
      >
        <path
          strokeLinecap="round"
          strokeLinejoin="round"
          d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"
        />
      </svg>

      <Input {...props} />
      <CloseButton plain size="sm" className="me-1 group-empty:invisible" />
    </InputFieldGroup>
  );
}
