import type { ComputedRef, HTMLAttributes, Ref } from 'vue';
import { onBeforeUnmount } from 'vue';
import { watchEffect } from 'vue';
import { watch } from 'vue';
import { ref } from 'vue';
import { computed } from 'vue';
import type { FocusEventHandler } from '../../_util/EventInterface';
import KeyCode from '../../_util/KeyCode';
import { addGlobalMousedownEvent, getTargetFromEvent } from '../utils/uiUtil';

export default function usePickerInput({
  open,
  value,
  isClickOutside,
  triggerOpen,
  forwardKeydown,
  onKeydown,
  blurToCancel,
  onSubmit,
  onCancel,
  onFocus,
  onBlur,
}: {
  open: Ref<boolean>;
  value: Ref<string>;
  isClickOutside: (clickElement: EventTarget | null) => boolean;
  triggerOpen: (open: boolean) => void;
  forwardKeydown: (e: KeyboardEvent) => boolean;
  onKeydown: (e: KeyboardEvent, preventDefault: () => void) => void;
  blurToCancel?: ComputedRef<boolean>;
  onSubmit: () => void | boolean;
  onCancel: () => void;
  onFocus?: FocusEventHandler;
  onBlur?: FocusEventHandler;
}): [ComputedRef<HTMLAttributes>, { focused: Ref<boolean>; typing: Ref<boolean> }] {
  const typing = ref(false);
  const focused = ref(false);

  /**
   * We will prevent blur to handle open event when user click outside,
   * since this will repeat trigger `onOpenChange` event.
   */
  const preventBlurRef = ref<boolean>(false);

  const valueChangedRef = ref<boolean>(false);

  const preventDefaultRef = ref<boolean>(false);

  const inputProps = computed<HTMLAttributes>(() => ({
    onMousedown: () => {
      typing.value = true;
      triggerOpen(true);
    },
    onKeydown: e => {
      const preventDefault = (): void => {
        preventDefaultRef.value = true;
      };

      onKeydown(e, preventDefault);

      if (preventDefaultRef.value) return;

      switch (e.which) {
        case KeyCode.ENTER: {
          if (!open.value) {
            triggerOpen(true);
          } else if (onSubmit() !== false) {
            typing.value = true;
          }

          e.preventDefault();
          return;
        }

        case KeyCode.TAB: {
          if (typing.value && open.value && !e.shiftKey) {
            typing.value = false;
            e.preventDefault();
          } else if (!typing.value && open.value) {
            if (!forwardKeydown(e) && e.shiftKey) {
              typing.value = true;
              e.preventDefault();
            }
          }
          return;
        }

        case KeyCode.ESC: {
          typing.value = true;
          onCancel();
          return;
        }
      }

      if (!open.value && ![KeyCode.SHIFT].includes(e.which)) {
        triggerOpen(true);
      } else if (!typing.value) {
        // Let popup panel handle keyboard
        forwardKeydown(e);
      }
    },

    onFocus: e => {
      typing.value = true;
      focused.value = true;

      if (onFocus) {
        onFocus(e);
      }
    },

    onBlur: e => {
      if (preventBlurRef.value || !isClickOutside(document.activeElement)) {
        preventBlurRef.value = false;
        return;
      }

      if (blurToCancel.value) {
        setTimeout(() => {
          let { activeElement } = document;
          while (activeElement && activeElement.shadowRoot) {
            activeElement = activeElement.shadowRoot.activeElement;
          }

          if (isClickOutside(activeElement)) {
            onCancel();
          }
        }, 0);
      } else if (open.value) {
        triggerOpen(false);

        if (valueChangedRef.value) {
          onSubmit();
        }
      }
      focused.value = false;

      if (onBlur) {
        onBlur(e);
      }
    },
  }));

  // check if value changed
  watch(open, () => {
    valueChangedRef.value = false;
  });

  watch(value, () => {
    valueChangedRef.value = true;
  });
  const globalMousedownEvent = ref();
  // Global click handler
  watchEffect(
    () =>
      globalMousedownEvent.value &&
      globalMousedownEvent.value()(
        (globalMousedownEvent.value = addGlobalMousedownEvent((e: MouseEvent) => {
          const target = getTargetFromEvent(e);

          if (open) {
            const clickedOutside = isClickOutside(target);

            if (!clickedOutside) {
              preventBlurRef.value = true;

              // Always set back in case `onBlur` prevented by user
              requestAnimationFrame(() => {
                preventBlurRef.value = false;
              });
            } else if (!focused.value || clickedOutside) {
              triggerOpen(false);
            }
          }
        })),
      ),
  );
  onBeforeUnmount(() => {
    globalMousedownEvent.value && globalMousedownEvent.value();
  });

  return [inputProps, { focused, typing }];
}
