import * as React from 'react';
import { TextControl, tailwind } from '@tesseract-design/web-base';
import { useClientSide, useFallbackId, useProxyInput } from '@modal-sh/react-utils';
import PhoneInput, { Country, Value } from 'react-phone-number-input/input';

const { tw } = tailwind;

const PhoneNumberInputDerivedElementComponent = 'input' as const;

/**
 * Derived HTML element of the {@link PhoneNumberInput} component.
 */
export type PhoneNumberInputDerivedElement = HTMLElementTagNameMap[
  typeof PhoneNumberInputDerivedElementComponent
];

/**
 * Props of the {@link PhoneNumberInput} component.
 */
export interface PhoneNumberInputProps extends Omit<React.HTMLProps<PhoneNumberInputDerivedElement>, 'autoComplete' | 'size' | 'type' | 'label' | 'inputMode'> {
  /**
   * Short textual description indicating the nature of the component's value.
   */
  label?: React.ReactNode,
  /**
   * Short textual description as guidelines for valid input values.
   */
  hint?: React.ReactNode,
  /**
   * Size of the component.
   */
  size?: TextControl.Size,
  /**
   * Should the component display a border?
   */
  border?: boolean,
  /**
   * Should the component occupy the whole width of its parent?
   */
  block?: boolean,
  /**
   * Style of the component.
   */
  variant?: TextControl.Variant,
  /**
   * Is the label hidden?
   */
  hiddenLabel?: boolean,
  /**
   * Should the component be enhanced?
   */
  enhanced?: boolean,
  /**
   * Country where the phone number should be formatted for.
   */
  country: Country,
  /**
   * Visual length of the input.
   */
  length?: number,
}

/**
 * Component for inputting national and international phone numbers.
 */
export const PhoneNumberInput = React.forwardRef<
  PhoneNumberInputDerivedElement,
  PhoneNumberInputProps
>((
  {
    label,
    hint,
    size = 'medium' as const,
    border = false as const,
    block = false as const,
    variant = 'default' as const,
    hiddenLabel = false as const,
    className,
    id: idProp,
    style,
    enhanced = false as const,
    country,
    value,
    onChange,
    name,
    length,
    defaultValue,
    ...etcProps
  }: PhoneNumberInputProps,
  forwardedRef,
) => {
  const { clientSide } = useClientSide({ clientSide: enhanced });
  const [phoneNumber, setPhoneNumber] = React.useState<Value>(
    value?.toString() ?? defaultValue?.toString() ?? '',
  );
  const labelId = React.useId();
  const id = useFallbackId(idProp);
  const {
    defaultRef,
    handleChange: handlePhoneInputChange,
  } = useProxyInput<Value, PhoneNumberInputDerivedElement>({
    forwardedRef,
    valueSetterFn: (v) => {
      setPhoneNumber(v);
    },
    transformChangeHandlerArgs: (v) => (v ?? '') as unknown as Value,
  });

  const commonInputStyles = tw(
    'bg-negative rounded-inherit w-full peer block font-inherit tabular-nums',
    'focus:outline-0',
    'disabled:cursor-not-allowed',
    {
      'pl-4': variant === 'default',
      'pl-1.5 pt-4': variant === 'alternate',
    },
    {
      'pr-10 h-10 text-xxs': size === 'small',
      'pr-12 h-12 text-xs': size === 'medium',
      'pr-16 h-16': size === 'large',
    },
  );

  return (
    <div
      className={tw(
        'relative rounded ring-secondary/50 group has-[:disabled]:opacity-50',
        'focus-within:ring-4',
        {
          'block': block,
          'inline-block align-middle': !block,
        },
        className,
      )}
      style={style}
      data-testid="base"
    >
      {label && (
        <>
          <label
            data-testid="label"
            id={labelId}
            htmlFor={id}
            className={tw(
              'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold group-focus-within:text-secondary text-primary leading-none bg-negative select-none',
              {
                'sr-only': hiddenLabel,
              },
              {
                'pr-10': size === 'small',
                'pr-12': size === 'medium',
                'pr-16': size === 'large',
              },
            )}
          >
            <span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
              {label}
            </span>
          </label>
          {' '}
        </>
      )}
      <PhoneNumberInputDerivedElementComponent
        {...etcProps}
        size={length}
        value={value}
        onChange={onChange}
        ref={defaultRef}
        defaultValue={defaultValue}
        aria-labelledby={labelId}
        type="tel"
        id={id}
        name={name}
        data-testid="input"
        tabIndex={clientSide ? -1 : undefined}
        className={tw(commonInputStyles, clientSide && 'sr-only')}
      />
      {clientSide && (
        <PhoneInput
          {...etcProps}
          ref={undefined}
          value={phoneNumber}
          onChange={handlePhoneInputChange}
          defaultCountry={country}
          className={commonInputStyles}
        />
      )}
      {hint && (
        <div
          data-testid="hint"
          className={tw(
            'absolute left-0 px-1 pointer-events-none text-xxs leading-none w-full bg-negative select-none',
            {
              'bottom-0 pl-4 pb-1': variant === 'default',
              'top-0.5': variant === 'alternate',
            },
            {
              'pt-2': variant === 'alternate' && size === 'small',
              'pt-3': variant === 'alternate' && size !== 'small',
            },
            {
              'pr-10': size === 'small',
              'pr-12': size === 'medium',
              'pr-16': size === 'large',
            },
          )}
        >
          <div
            className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
          >
            {hint}
          </div>
        </div>
      )}
      <div
        data-testid="indicator"
        className={tw(
          'text-center flex items-center justify-center aspect-square absolute bottom-0 right-0 pointer-events-none select-none text-primary group-focus-within:text-secondary',
          {
            'w-10': size === 'small',
            'w-12': size === 'medium',
            'w-16': size === 'large',
          },
        )}
      >
        <svg
          className="w-6 h-6 fill-none stroke-current stroke-2 linejoin-round linecap-round"
          viewBox="0 0 24 24"
          role="presentation"
        >
          <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" />
        </svg>
      </div>
      {border && (
        <span
          data-testid="border"
          className="absolute z-[1] inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
        />
      )}
    </div>
  );
});

PhoneNumberInput.displayName = 'PhoneNumberInput';

PhoneNumberInput.defaultProps = {
  label: undefined,
  hint: undefined,
  size: 'medium' as const,
  border: false as const,
  block: false as const,
  variant: 'default' as const,
  hiddenLabel: false as const,
  enhanced: false as const,
  length: undefined,
};
