import { useCallback, useEffect, useRef, useState } from 'react';

import type { FC } from 'react';
import type { TextInputProps } from '.';
import type { FormOnSubmit } from '../Form';
import type { TransitionInOut2Api } from '../TransitionInOut2';

import TextInput from '.';
import Clickable from '../Clickable';
import Form from '../Form';
import HoverBackground from '../HoverBackground';
import Loading from '../Loading';
import Text from '../Text';
import TransitionInOut2 from '../TransitionInOut2';
import TextInputInnerBox from './TextInputInnerBox';

interface TextInputWithSubmitProps
  extends Omit<TextInputProps, 'value' | 'defaultValue' | 'onSubmit'> {
  buttonText: string;
  isLoading?: boolean;
  initialValue: string;
  onSubmit: (params: {
    value: string;
    setButtonVisible: TransitionInOut2Api['setVisible'];
  }) => void | Promise<void>;
}

const SUBMIT_ANIMATION_DURATION = 250;

const TextInputWithSubmit: FC<TextInputWithSubmitProps> = ({
  buttonText,
  initialValue,
  onSubmit,
  isDisabled,
  isLoading,
  ...textInputProps
}) => {
  const [inputPaddingRight, setInputPaddingRight] = useState(0);

  const transitionApiRef = useRef<TransitionInOut2Api>(null);
  const buttonRef = useRef<HTMLButtonElement>(null);
  const hasChangesRef = useRef(false);

  // Initially disable the button so it's not in the tab index.
  // This could be done with react props but we don't want to cause
  // a react render during the transition to avoid jank.
  useEffect(() => {
    const button = buttonRef.current;
    if (button) button.disabled = true;
  }, []);

  return (
    <Form
      onSubmit={useCallback<FormOnSubmit<{ input: string }>>(
        async ({ values }) => {
          const newValue = values.input;
          const didChange = newValue !== initialValue;

          if (didChange) {
            await onSubmit({
              value: newValue as string,
              setButtonVisible: transitionApiRef.current!.setVisible,
            });
          }
        },
        [initialValue, onSubmit]
      )}
    >
      <TextInput
        {...textInputProps}
        // HACK: make sure input re-renders
        key={initialValue}
        name="input"
        padding={`0 ${inputPaddingRight} 0 0`}
        defaultValue={initialValue}
        isDisabled={isDisabled}
        onInputEnd={useCallback(({ value, isValid }) => {
          const hasChanges = value !== initialValue;
          hasChangesRef.current = hasChanges;

          const isButtonVisible = isValid && hasChanges;
          transitionApiRef.current?.setVisible(isButtonVisible);

          // PERF: when the button is shown, enable it so it's keyboard focusable we're
          // doing this directly to avoid triggering a react render and killing fps.
          const buttonEl = buttonRef.current;

          if (buttonEl) {
            buttonEl.disabled = !isButtonVisible;

            // Add padding right to the input so user can visually see the full content
            if (isButtonVisible) {
              // delay the padding update to avoid flickering
              setTimeout(() => {
                setInputPaddingRight(buttonEl.offsetWidth);
              }, SUBMIT_ANIMATION_DURATION);
            } else {
              setInputPaddingRight(0);
            }
          }
        }, [])}
        renderAfter={useCallback(
          () => (
            <TransitionInOut2
              apiRef={transitionApiRef}
              isVisibleInitial={false}
              delay={0}
              duration={SUBMIT_ANIMATION_DURATION}
              centerContent
              style={{
                position: 'absolute',
                right: 0,
                top: 0,
                bottom: 0,
                background: '#000',
              }}
              styleFrom={{
                transform: 'translateX(100%)',
              }}
            >
              <TextInputInnerBox position="right" padding="0">
                <div style={{ opacity: isLoading ? 0 : 1, height: '100%' }}>
                  <HoverBackground>
                    <Clickable
                      isInline
                      nodeRef={buttonRef}
                      isSubmit
                      fullHeight
                      padding=".8em .84em"
                      testId="submit"
                    >
                      <Text isBold color="#fff" size="0.93em">
                        {buttonText}
                      </Text>
                    </Clickable>
                  </HoverBackground>
                </div>
                {isLoading && <Loading coverParent size="1.1em" />}
              </TextInputInnerBox>
            </TransitionInOut2>
          ),
          [buttonText, isLoading]
        )}
      />
    </Form>
  );
};

export default TextInputWithSubmit;
