import * as React from 'react';
import { TextControl, tailwind } from '@tesseract-design/web-base';
import { useClientSide, useFallbackId } from '@modal-sh/react-utils';

const { tw } = tailwind;

const MaskedTextInputDerivedElementComponent = 'input' as const;

/**
 * Derived HTML element of the {@link MaskedTextInput} component.
 */
export type MaskedTextInputDerivedElement = HTMLElementTagNameMap[
  typeof MaskedTextInputDerivedElementComponent
];

type SelectionDirection = 'none' | 'forward' | 'backward';

const AVAILABLE_AUTO_COMPLETE_VALUES = ['current-password', 'new-password'] as const;

/**
 * Props of the {@link MaskedTextInput} component.
 */
export interface MaskedTextInputProps extends Omit<React.HTMLProps<MaskedTextInputDerivedElement>, 'autoComplete' | 'size' | 'type' | 'label' | 'pattern'> {
  /**
   * 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,
  /**
   * Visual length of the input.
   */
  length?: number,
  /**
   * Should the component be enhanced?
   */
  enhanced?: boolean,
  /**
   * Autocomplete value type of the component.
   */
  autoComplete?: typeof AVAILABLE_AUTO_COMPLETE_VALUES[number],
}

/**
 * Component for inputting textual values.
 */
export const MaskedTextInput = React.forwardRef<
  MaskedTextInputDerivedElement,
  MaskedTextInputProps
>((
  {
    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,
    length,
    disabled,
    onKeyDown,
    onKeyUp,
    autoComplete,
    enhanced: enhancedProp = false as const,
    ...etcProps
  }: MaskedTextInputProps,
  forwardedRef,
) => {
  const { clientSide: enhanced } = useClientSide({ clientSide: enhancedProp });
  const labelId = React.useId();
  const id = useFallbackId(idProp);
  const [visible, setVisible] = React.useState(false);
  const [visibleViaKey, setVisibleViaKey] = React.useState(false);
  const defaultRef = React.useRef<MaskedTextInputDerivedElement>(null);
  const ref = forwardedRef ?? defaultRef;

  const handleKeyDown: React.KeyboardEventHandler<
    MaskedTextInputDerivedElement
  > = React.useCallback((e) => {
    if (e.ctrlKey && e.code === 'Space') {
      setVisibleViaKey(true);
    }
    onKeyDown?.(e);
  }, [onKeyDown]);

  const handleKeyUp: React.KeyboardEventHandler<
    MaskedTextInputDerivedElement
  > = React.useCallback((e) => {
    if (e.ctrlKey && e.code === 'Space') {
      setVisibleViaKey(false);
      setVisible((prev) => !prev);
    }
    onKeyUp?.(e);
  }, [onKeyUp]);

  const handleToggleVisible = React.useCallback(() => {
    const effectiveRef = typeof ref === 'object' ? ref : defaultRef;
		const current = effectiveRef.current as MaskedTextInputDerivedElement;
    const selectionStart = current.selectionStart as number;
    const selectionEnd = current.selectionEnd as number;
    const selectionDirection = current.selectionDirection as SelectionDirection;

    setVisible((prev) => !prev);
    setTimeout(() => {
      current.focus();
      current.setSelectionRange(selectionStart, selectionEnd, selectionDirection);
    });
  }, [ref, defaultRef]);

  // const preserveFocus: React.MouseEventHandler<HTMLButtonElement> = React.useCallback(() => {
  //   const { current } = typeof ref === 'object' ? ref : defaultRef;
  //   setVisibleViaKey(true);
  //   setTimeout(() => {
  //     current?.focus();
  //   });
  // }, [ref, defaultRef]);

  // const handleToggleMouseUp: React.MouseEventHandler<HTMLButtonElement> = React.useCallback(() => {
  //   const { current } = typeof ref === 'object' ? ref : defaultRef;
  //   setVisibleViaKey(false);
  //   setTimeout(() => {
  //     current?.focus();
  //   });
  // }, [ref, defaultRef]);

  React.useEffect(() => {
    if (typeof ref === 'function') {
      const defaultElement = defaultRef.current as MaskedTextInputDerivedElement;
      ref(defaultElement);
    }
  }, [defaultRef, ref]);

  return (
    <div
      className={tw(
        'relative rounded ring-secondary/50 overflow-hidden 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-1': !enhanced,
              },
              {
                'pr-10': enhanced && size === 'small',
                'pr-12': enhanced && size === 'medium',
                'pr-16': enhanced && size === 'large',
              },
            )}
          >
            <span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
              {label}
            </span>
          </label>
          {' '}
        </>
      )}
      <MaskedTextInputDerivedElementComponent
        {...etcProps}
        size={length}
        ref={typeof ref === 'function' ? defaultRef : ref}
        disabled={disabled}
        id={id}
        aria-labelledby={labelId}
        type={!visible ? 'password' : 'text'}
        data-testid="input"
        autoComplete={autoComplete ?? 'off'}
        onKeyUp={enhanced ? handleKeyUp : undefined}
        onKeyDown={enhanced ? handleKeyDown : undefined}
        className={tw(
          'bg-negative rounded-inherit w-full peer block font-inherit',
          'focus:outline-0',
          'disabled:opacity-50 disabled:cursor-not-allowed',
          {
            'text-xxs': size === 'small',
            'text-xs': size === 'medium',
          },
          {
            'pl-4': variant === 'default',
            'pl-1.5': variant === 'alternate',
          },
          {
            'pt-4': variant === 'alternate',
          },
          {
            'pr-4': variant === 'default' && !enhanced,
            'pr-1.5': variant === 'alternate' && !enhanced,
          },
          {
            'pr-10': enhanced && size === 'small',
            'pr-12': enhanced && size === 'medium',
            'pr-16': enhanced && size === 'large',
          },
          {
            'h-10': size === 'small',
            'h-12': size === 'medium',
            'h-16': size === 'large',
          },
        )}
      />
      {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-4': !enhanced && variant === 'default',
              'pr-1': !enhanced && variant === 'alternate',
            },
            {
              'pr-10': enhanced && size === 'small',
              'pr-12': enhanced && size === 'medium',
              'pr-16': enhanced && size === 'large',
            },
          )}
        >
          <div
            className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
          >
            {hint}
          </div>
        </div>
      )}
      {enhanced && (
        <button
          disabled={disabled}
          type="button"
          data-testid="indicator"
          tabIndex={-1}
          className={tw(
            'text-center z-[1] focus:outline-0 flex items-center justify-center aspect-square absolute bottom-0 right-0 select-none',
            {
              'text-primary group-focus-within:text-secondary group-focus-within:active:text-tertiary': !visibleViaKey,
              'text-tertiary': visibleViaKey,
            },
            {
              'w-10': size === 'small',
              'w-12': size === 'medium',
              'w-16': size === 'large',
            },
          )}
          onClick={handleToggleVisible}
					title={visible ? 'Hide' : 'Show'}
        >
					<span className="sr-only">
						{visible ? 'Hide' : 'Show'}
					</span>
          <svg
            className="w-6 h-6 fill-none stroke-current stroke-2 linejoin-round linecap-round"
            viewBox="0 0 24 24"
            role="presentation"
          >
            {!visible && (
              <>
                <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
                <circle cx="12" cy="12" r="3" />
              </>
            )}
            {visible && (
              <>
                <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" />
                <line x1="1" y1="1" x2="23" y2="23" />
              </>
            )}
          </svg>
        </button>
      )}
      {border && (
        <span
          data-testid="border"
          className="absolute z-[1] inset-0 rounded-inherit border-2 border-primary pointer-events-none group-focus-within:border-secondary"
        />
      )}
    </div>
  );
});

MaskedTextInput.displayName = 'MaskedTextInput';

MaskedTextInput.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,
  length: undefined,
  enhanced: false as const,
  autoComplete: undefined,
};
