import styled from '@emotion/styled';
import clsx from 'clsx';
import React, {
  Dispatch,
  PropsWithChildren,
  RefObject,
  SetStateAction,
  forwardRef,
  useCallback,
  useEffect,
  useRef,
} from 'react';
import { twMerge } from 'tailwind-merge';

const Wrapper = styled.div`
  position: relative;
  width: 100%;
  height: 100%;
  textarea {
    padding-bottom: 60px;
    ::selection {
      background-color: rgb(var(--rgb-accent-pink));
    }
  }
`;

export const SelectionOverlay = ({
  top,
  width,
  className = '',
  children,
  ref,
}: PropsWithChildren<{
  top: number;
  width: number;
  className?: string;
  ref?: RefObject<HTMLDivElement | null>;
}>) => {
  return (
    <div
      className={twMerge(
        'pointer-events-none absolute bottom-0 text-base whitespace-pre-wrap text-transparent md:text-sm',
        className
      )}
      style={{
        top: `${top}px`,
        width: `${width}px`,
      }}
      ref={ref}
    >
      {children}
    </div>
  );
};

export const TextSelection = styled.span<{ isLoading: boolean }>`
  background-color: rgb(var(--rgb-accent-pink));
  padding-top: 2px;
  padding-bottom: 3px;
  padding-left: 1px;
  padding-right: 1px;
  margin-left: -1px;
  color: rgb(var(--rgb-foreground-primary));
  filter: ${({ isLoading }) => (isLoading ? 'blur(2px)' : '')};
`;

interface CreateTextareaProps
  extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
  selectionOverlayRange?: { start: number; end: number } | null;
  setSelectionOverlayRange?: Dispatch<
    SetStateAction<{ start: number; end: number } | null>
  >;
  isRegeneratingSelection?: boolean;
  textareaClassName?: string;
}

const CreateTextarea = forwardRef<HTMLTextAreaElement, CreateTextareaProps>(
  function CreateTextarea(
    {
      selectionOverlayRange,
      setSelectionOverlayRange,
      isRegeneratingSelection,
      textareaClassName,
      ...props
    },
    textareaRef
  ) {
    const selectionOverlayRef = useRef<HTMLDivElement>(null);

    useEffect(() => {
      if (selectionOverlayRange && isRegeneratingSelection) {
        setSelectionRange(
          textareaRef as RefObject<HTMLTextAreaElement>,
          selectionOverlayRange?.start ?? 0,
          selectionOverlayRange?.end ?? 0
        );
      }
    }, [selectionOverlayRange, isRegeneratingSelection]);

    const clearSelectionRange = useCallback(() => {
      setSelectionOverlayRange?.(null);
    }, [setSelectionOverlayRange]);

    const handleSelect = useCallback(
      (textareaRef: RefObject<HTMLTextAreaElement | null>) => {
        if (
          textareaRef.current?.selectionStart ===
          textareaRef.current?.selectionEnd
        ) {
          setSelectionOverlayRange?.(null);
          return;
        }
        setSelectionOverlayRange?.({
          start: textareaRef.current?.selectionStart ?? 0,
          end: textareaRef.current?.selectionEnd ?? 0,
        });
      },
      [setSelectionOverlayRange]
    );

    const setSelectionRange = useCallback(
      (
        textareaRef: RefObject<HTMLTextAreaElement | null>,
        start: number,
        end: number
      ) => {
        if (textareaRef.current && start !== end) {
          textareaRef.current.focus();
          setTimeout(() => {
            textareaRef.current?.setSelectionRange(start, end, 'forward');
          }, 0);
        }
      },
      []
    );

    const restoreSelection = useCallback(
      (textareaRef: RefObject<HTMLTextAreaElement | null>) => {
        setSelectionRange?.(
          textareaRef,
          selectionOverlayRange?.start ?? 0,
          selectionOverlayRange?.end ?? 0
        );
      },
      [selectionOverlayRange, setSelectionRange]
    );
    return (
      <Wrapper
        className={twMerge(
          clsx({
            'blur-[2px]': isRegeneratingSelection && !selectionOverlayRange,
          }),
          props.className
        )}
      >
        <textarea
          ref={textareaRef}
          {...props}
          className={twMerge(
            `w-full resize-none border-none bg-transparent text-base outline-none md:text-sm`,
            textareaClassName ?? ''
          )}
          onSelect={() =>
            handleSelect?.(textareaRef as RefObject<HTMLTextAreaElement>)
          }
          disabled={isRegeneratingSelection || props.disabled}
          onMouseDown={clearSelectionRange}
          onKeyDown={(e) => {
            clearSelectionRange();
            props.onKeyDown?.(e);
          }}
          onScroll={() => {
            if (selectionOverlayRef.current) {
              selectionOverlayRef.current.style.top = `${-1 * ((textareaRef as RefObject<HTMLTextAreaElement>)?.current?.scrollTop ?? 0)}px`;
              restoreSelection(textareaRef as RefObject<HTMLTextAreaElement>);
            }
          }}
        />
        {selectionOverlayRange && textareaRef ? (
          <SelectionOverlay
            ref={selectionOverlayRef}
            top={
              -1 *
              ((textareaRef as RefObject<HTMLTextAreaElement>).current
                ?.scrollTop ?? 0)
            }
            width={
              (textareaRef as RefObject<HTMLTextAreaElement>).current
                ?.scrollWidth ?? 0
            }
            className={textareaClassName}
          >
            {props.value?.toString().slice(0, selectionOverlayRange.start)}
            <TextSelection isLoading={!!isRegeneratingSelection}>
              {props.value
                ?.toString()
                .slice(selectionOverlayRange.start, selectionOverlayRange.end)}
            </TextSelection>
            {props.value?.toString().slice(selectionOverlayRange.end)}
          </SelectionOverlay>
        ) : null}
      </Wrapper>
    );
  }
);

export default CreateTextarea;
