import * as React from "react";
import * as R from "ramda";

import { connectToStore } from "@applicaster/zapp-react-native-redux/utils/connectToStore";

import {
  QUICK_BRICK_EVENTS,
  sendQuickBrickEvent,
} from "@applicaster/zapp-react-native-bridge/QuickBrick";

import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";

import {
  debounce,
  noop,
} from "@applicaster/zapp-react-native-utils/functionUtils";

import {
  AccessibilityManager,
  ARROW_KEYS,
  focusManager,
  keyCode,
  KEYS,
  playerManager,
} from "@applicaster/zapp-react-native-utils/appUtils";

import { showConfirmationDialog } from "@applicaster/zapp-react-native-utils/alertUtils";

import {
  DISPLAY_STATES,
  DisplayStateContext,
} from "@applicaster/zapp-react-native-ui-components/Contexts/DisplayStateContext";

import { PlayerContentContext } from "@applicaster/zapp-react-dom-ui-components/Contexts";

import { withNavigator } from "@applicaster/zapp-react-native-ui-components/Decorators/Navigator";
import {
  getXray,
  createLogger,
} from "@applicaster/zapp-react-native-utils/logger";

import {
  confirmationDialogStore,
  ConfirmationDialogState,
} from "@applicaster/zapp-react-native-ui-components/Contexts/ConfirmDialogState";

import {
  isLgPlatform,
  isSamsungPlatform,
  isVizioPlatform,
} from "@applicaster/zapp-react-native-utils/reactUtils";

import { OnScreenKeyboard } from "@applicaster/zapp-react-dom-ui-components/Components/OnScreenKeyboard";
import { KeyboardLongPressManager } from "@applicaster/zapp-react-dom-ui-components/Utils/KeyboardLongPressManager";
import { KeyInputHandler } from "@applicaster/zapp-react-native-utils/appUtils/keyInputHandler/KeyInputHandler";
import { getHomeRiver } from "@applicaster/zapp-react-native-utils/reactHooks/state";
import { withBackToTopActionHOC } from "./hoc";

const { withXray } = getXray();
const globalAny: any = global;

const { log_debug, log_warning, log_info } = createLogger({
  category: "InteractionManager",
  subsystem: "zapp-react-dom-app",
});

const shouldUseOnScreenKeyboard = () =>
  isVizioPlatform() ||
  toBooleanWithDefaultFalse(window?.applicaster?.useOnScreenKeyboard);

type Props = {
  displayState: string;
  setDisplayState: (state: string) => void;
  resetHudTimer: () => void;
  navigator: QuickBrickAppNavigator;
  rivers: {};
  xray: XRayContext;
  confirmDialog: typeof confirmationDialogStore;
  backToTopAction: () => BackToTopAction;
  emitFocusOnSelectedTopMenuItem: Callback;
  emitFocusOnSelectedTab: Callback;
};

interface State {
  isKeyboardVisible: boolean;
  keyboardInput: string;
  activeInputRef: HTMLInputElement | null;
}

type KeyCode = { code: string; keyCode: number | string };

const VIDEO_PLAYER_CONTROLS_NAVIGATION_MESSAGE =
  "Video player controls - use left/right to navigate";

class InteractionManagerClass extends React.Component<Props, State> {
  onKeyDownListener: (keyCode: KeyCode) => void;
  onMouseDownListener: (event: MouseEvent) => void;
  onMouseMoveListener: (event: MouseEvent) => void;
  onScrollListener: (event: MouseEvent) => void;

  private longPressDetector: KeyboardLongPressManager;
  private longPressActive: boolean = false;
  private timeout: any | null = null;
  private sequenceTimeout: NodeJS.Timeout | null;
  private userSequence: number[] = [];
  private isDismissing = false;
  private confirmDialog: ConfirmationDialogState =
    confirmationDialogStore.getState();

  accessibilityManager: AccessibilityManager;

  state: State = {
    isKeyboardVisible: false,
    keyboardInput: "",
    activeInputRef: null,
  };

  constructor(props) {
    super(props);

    this.onMouseMoveListener = debounce({
      fn: this.onMouseMove,
      context: this,
    });

    this.onScrollListener = debounce({
      fn: this.onScroll,
      wait: 100,
      context: this,
    });

    this.onMouseDownListener = debounce({
      fn: this.onMouseDown,
      context: this,
    });

    this.onKeyDownListener = this.onKeyDown.bind(this);
    this.resetHudTimeout = this.resetHudTimeout.bind(this);
    this.cursorVisibilityChange = this.cursorVisibilityChange.bind(this);
    this.onKeyDown = this.onKeyDown.bind(this);
    this.onKeyUp = this.onKeyUp.bind(this);
    this.onMouseMove = this.onMouseMove.bind(this);
    this.onScroll = this.onScroll.bind(this);
    this.onMouseDown = this.onMouseDown.bind(this);
    this.onKeyboardStateChange = this.onKeyboardStateChange.bind(this);
    this.onConfirmDialogOpen = this.onConfirmDialogOpen.bind(this);
    this.onConfirmDialogClose = this.onConfirmDialogClose.bind(this);
    this.performExit = this.performExit.bind(this);
    this.handleInputFocus = this.handleInputFocus.bind(this);
    this.handleInputBlur = this.handleInputBlur.bind(this);
    this.handleKeyboardInput = this.handleKeyboardInput.bind(this);
    this.handleKeyboardDismiss = this.handleKeyboardDismiss.bind(this);
    this.onMouseMoveListener = this.onMouseMove.bind(this);
    this.onScrollListener = this.onScroll.bind(this);
    this.onMouseDownListener = this.onMouseDown.bind(this);

    this.handlePhysicalKeyboardDismiss =
      this.handlePhysicalKeyboardDismiss.bind(this);

    this.confirmDialog.setConfirmAction(this.performExit);
    this.confirmDialog.setCancelAction(this.onConfirmDialogClose);
    this.longPressDetector = new KeyboardLongPressManager();
    this.accessibilityManager = AccessibilityManager.getInstance();
  }

  componentDidMount() {
    window.addEventListener("keydown", this.onKeyDownListener, true);
    window.addEventListener("keyup", this.onKeyUp);
    window.addEventListener("mousemove", this.onMouseMoveListener);

    document.addEventListener("wheel", this.onScrollListener, {
      passive: true,
    });

    document.addEventListener("mousedown", this.onMouseDownListener);

    document.addEventListener(
      "keyboardStateChange",
      this.onKeyboardStateChange,
      false
    );

    document.addEventListener("focusin", this.handleInputFocus);

    document.addEventListener(
      "cursorStateChange",
      this.cursorVisibilityChange,
      false
    );

    window.addEventListener("onLongPress", this.handleLongPress.bind(this));

    window.addEventListener(
      "onPressRelease",
      this.handleLongPressRelease.bind(this)
    );

    window.addEventListener(
      "onLongPressRelease",
      this.handleLongPressRelease.bind(this)
    );

    document.addEventListener(
      "keyboardDismissedByPhysicalKeypress",
      this.handlePhysicalKeyboardDismiss
    );
  }

  componentWillUnmount() {
    window.removeEventListener("keydown", this.onKeyDownListener);
    window.removeEventListener("keyup", this.onKeyUp);
    document.removeEventListener("wheel", this.onScrollListener);

    document.removeEventListener(
      "cursorStateChange",
      this.cursorVisibilityChange
    );

    document.removeEventListener("focusin", this.handleInputFocus);

    document.removeEventListener("mousedown", this.onMouseDownListener);

    if (this.timeout) {
      this.clearTimeout();
    }

    this.isDismissing = false;
    this.longPressDetector.cleanup();
    window.removeEventListener("onLongPress", this.handleLongPress);

    window.removeEventListener(
      "onLongPressRelease",
      this.handleLongPressRelease
    );

    document.removeEventListener(
      "keyboardDismissedByPhysicalKeypress",
      this.handlePhysicalKeyboardDismiss
    );
  }

  performExit() {
    if (isSamsungPlatform()) {
      this.onConfirmDialogClose();
      sendQuickBrickEvent(QUICK_BRICK_EVENTS.MOVE_APP_TO_BACKGROUND);
    }

    if (isLgPlatform()) {
      // webOS recommend to use this method when implemention a custom exit
      globalAny.close();
    }

    if (isVizioPlatform()) {
      window?.VIZIO?.exitApplication();
    }
  }

  handleWebOsBack() {
    const systemBack = globalAny?.webOS?.platformBack;

    if (systemBack) {
      systemBack();
    } else {
      /**
       * In case webOS failed to show the exit pop-up, we show a regular promtp as fallback
       */
      showConfirmationDialog({
        title: "",
        confirmCompletion: this.performExit,
      });
    }
  }

  /**
   * This methods sends the app to background in whatever way is appropriate for each platform
   * In the case of Tizen it shows a custom dialog on confirm it exits the app
   * WebOS behavior depends on the SDK version, WebOS 4 would pull up the launcher menu
   * following versions would show a native exit prompt
   */
  platformBack() {
    try {
      // TODO: temporary hack until this code is refactored
      if (isSamsungPlatform() || isVizioPlatform()) {
        const { isDialogVisible } = confirmationDialogStore.getState();

        if (isDialogVisible) {
          this.onConfirmDialogClose();
        } else {
          showConfirmationDialog({
            title: "",
            confirmCompletion: this.performExit,
            cancelCompletion: this.onConfirmDialogClose,
          });
        }
      } else if (isLgPlatform()) {
        this.handleWebOsBack();
      }
    } catch (e) {
      log_warning("Failed to execute platformBack", e);
    }
  }

  cursorVisibilityChange(event) {
    const visible = event.detail.visibility;
    const { setDisplayState, displayState } = this.props;

    if (visible && displayState === DISPLAY_STATES.PLAYER) {
      setDisplayState(DISPLAY_STATES.HUD);
      this.resetHudTimeout();
    }
  }

  onMouseMove() {
    const { setDisplayState, displayState } = this.props;

    if (displayState === DISPLAY_STATES.PLAYER) {
      setDisplayState(DISPLAY_STATES.HUD);
      this.resetHudTimeout();
    }
  }

  isHomeScreen() {
    const { navigator } = this.props;
    const { rivers } = this.props;

    const homeRiver = getHomeRiver(rivers);

    const homePath = `/river/${homeRiver?.id}`;

    return homePath === navigator.currentRoute;
  }

  goToHome() {
    const { navigator } = this.props;
    const { rivers } = this.props;
    const homeRiver = getHomeRiver(rivers);

    navigator.replace(homeRiver);
  }

  resetHudTimeout() {
    const { resetHudTimer = noop } = this.props;

    resetHudTimer?.();
  }

  clearTimeout() {
    if (this.timeout) {
      clearTimeout(this.timeout);
      this.timeout = null;
    }
  }

  cancelHudTimeout() {
    this.resetHudTimeout();
  }

  onKeyUp(event: KeyboardEvent) {
    const { setDisplayState } = this.props;

    if (this.keySupportsLongPress(event)) {
      this.longPressDetector.endKeyPressMonitoring(event.code);
    }

    if (
      keyCode(event).matches(KEYS.Enter) ||
      (__DEV__ && keyCode(event).matches(KEYS.X))
    ) {
      focusManager.pressOut();
    }

    if (
      keyCode(event).matches(KEYS.Forward) ||
      (__DEV__ && keyCode(event).matches(KEYS.C))
    ) {
      setDisplayState(DISPLAY_STATES.HUD);
      KeyInputHandler.getInstance().onForwardPressOut();
    }

    if (
      keyCode(event).matches(KEYS.Rewind) ||
      (__DEV__ && keyCode(event).matches(KEYS.Z))
    ) {
      setDisplayState(DISPLAY_STATES.HUD);
      KeyInputHandler.getInstance().onRewindPressOut();
    }
  }

  /**
     When pressing escape or the Native "Back" button:
     if on the transport controls: we show the player
     if on the player : we show the UI
     if on the UI : we exit the app
     note: backspace affects the keyboard input, so it behaves differently
     */
  onBackPress(event) {
    const {
      displayState,
      setDisplayState,
      navigator,
      backToTopAction,
      emitFocusOnSelectedTopMenuItem,
      emitFocusOnSelectedTab,
    } = this.props;

    const { isDialogVisible } = confirmationDialogStore.getState();
    const { isKeyboardVisible } = this.state;

    log_debug("onBackPress", {
      eventCode: event.code,
      displayState,
      isDialogVisible,
      isKeyboardVisible,
    });

    if (isKeyboardVisible && shouldUseOnScreenKeyboard()) {
      this.handleKeyboardDismiss();
      event.preventDefault();
      event.stopPropagation();

      return;
    }

    const action = backToTopAction();

    switch (displayState) {
      case DISPLAY_STATES.DEFAULT:
        if (action === "PLATFORM_BACK") {
          log_info(action);

          this.platformBack();
        }

        if (action === "GO_HOME") {
          log_info(action);

          this.goToHome();
        }

        if (action === "FOCUS_ON_SELECTED_TOP_MENU_ITEM") {
          log_info(action);

          // we don't have enough context at this point to set focus properly on selected top-menu-item, just emit event about it.
          // TopMenu component is supposed to handle this event.
          emitFocusOnSelectedTopMenuItem();
        }

        if (action === "FOCUS_ON_SELECTED_TAB_ITEM") {
          log_info(action);

          // we don't have enough context at this point to set focus properly on selected tab-menu-item, just emit event about it
          // Tabs component is supposed to handle this event.
          emitFocusOnSelectedTab();
        }

        if (action === "GO_BACK") {
          log_info(action);

          if (navigator.canGoBack()) {
            navigator.goBack();
          }
        }

        if (action === "GO_BACK_FROM_HOOK") {
          log_info(action);

          const fallbackToHome = false;
          const fromHook = true;

          navigator.goBack(fallbackToHome, fromHook);
        }

        break;
      case DISPLAY_STATES.PLAYER:
        if (isDialogVisible) {
          this.onConfirmDialogClose();
        } else if (navigator.canGoBack()) {
          navigator.goBack();
        } else if (this.isHomeScreen()) {
          this.platformBack();
        } else if (navigator.currentRoute.includes("hook")) {
          // If code reaches this block navigator cant go back, it is not home,
          // and this is a login plugin that was pushed on top of the home
          // Treat it like a home screen and run platformBack()
          const entryData = navigator?.data?.entry;
          const isHookInHomescreen = entryData?.payload?.home;

          const isHookFlowBlocker =
            typeof entryData?.hookPlugin?.module?.isFlowBlocker === "function"
              ? entryData?.hookPlugin?.module?.isFlowBlocker()
              : entryData?.hookPlugin?.module?.isFlowBlocker;

          if (isHookInHomescreen && isHookFlowBlocker) {
            this.platformBack();
          } else {
            navigator.goBack(false, true);
          }
        } else {
          this.goToHome();
        }

        break;
      case DISPLAY_STATES.HUD:
        // IF transport controls are open, close them instead of closing player
        setDisplayState(DISPLAY_STATES.PLAYER);
        break;
      default:
        setDisplayState(DISPLAY_STATES.PLAYER);
        break;
    }
  }

  onBackspacePress(event) {
    const isInputFocused =
      document.activeElement instanceof HTMLInputElement ||
      document.activeElement instanceof HTMLTextAreaElement;

    // Don't handle backspace if input is focused
    if (isInputFocused) {
      return;
    }

    this.onBackPress(event);
  }

  onKeyDown(event) {
    const { displayState, setDisplayState, navigator, xray } = this.props;
    const { isDialogVisible } = confirmationDialogStore.getState();
    const { isKeyboardVisible } = this.state;

    if (
      xray?.isPromptEnabled ||
      (isKeyboardVisible && shouldUseOnScreenKeyboard())
    ) {
      return;
    }

    if (keyCode(event).matches("Backspace")) {
      this.onBackspacePress(event);

      return;
    }

    if (keyCode(event).matchesAny(KEYS.Back, KEYS.Escape, KEYS.EscapeDevice)) {
      this.onBackPress(event);

      return;
    }

    if (this.keySupportsLongPress(event)) {
      this.longPressDetector.monitorKeyPress(event.code, event);
    }

    // Only handle repeat events for arrow keys
    if (event.repeat && !keyCode(event).matchesAny(...ARROW_KEYS)) return;

    /**
     When the player is displayed, we listen to Native Play / Pause / Stop
     buttons. Any other key press will display the transport controls
     */

    if (
      displayState === DISPLAY_STATES.PLAYER ||
      displayState === DISPLAY_STATES.HUD
    ) {
      /**
       registering native keys for playback
       */
      if (keyCode(event).matches(KEYS.TogglePlayPause)) {
        const { playing } = playerManager.getState() || {};

        if (!playing) {
          return playerManager.togglePlayPause();
        }
      }

      if (keyCode(event).matches(KEYS.Play)) {
        return playerManager.play();
      }

      if (keyCode(event).matches(KEYS.Pause)) {
        setDisplayState(DISPLAY_STATES.HUD);

        return playerManager.pause();
      }

      if (keyCode(event).matches(KEYS.Stop)) {
        setDisplayState(DISPLAY_STATES.DEFAULT);
        navigator.goBack();

        return;
      }

      if (
        keyCode(event).matches(KEYS.Forward) ||
        (__DEV__ && keyCode(event).matches(KEYS.C))
      ) {
        setDisplayState(DISPLAY_STATES.HUD);
        KeyInputHandler.getInstance().onForwardPress();

        return;
      }

      if (
        keyCode(event).matches(KEYS.Rewind) ||
        (__DEV__ && keyCode(event).matches(KEYS.Z))
      ) {
        setDisplayState(DISPLAY_STATES.HUD);
        KeyInputHandler.getInstance().onRewindPress();

        return;
      }
    }

    if (
      keyCode(event).matches(KEYS.Enter) ||
      (__DEV__ && keyCode(event).matches(KEYS.X))
    ) {
      if (displayState === DISPLAY_STATES.PLAYER) {
        setDisplayState(DISPLAY_STATES.HUD);

        this.accessibilityManager.addHeading(
          VIDEO_PLAYER_CONTROLS_NAVIGATION_MESSAGE
        );

        focusManager.recoverFocus();
        this.resetHudTimeout();
      } else {
        focusManager.pressIn();
        focusManager.press();
      }

      return true;
    }

    if (keyCode(event).matches(KEYS.Exit)) {
      if (isSamsungPlatform() || shouldUseOnScreenKeyboard()) {
        this.performExit();

        return;
      }

      this.platformBack();
    }

    if (keyCode(event).matchesAny(...ARROW_KEYS)) {
      if (displayState === DISPLAY_STATES.PLAYER) {
        setDisplayState(DISPLAY_STATES.HUD);
      }

      this.checkSequence(event.keyCode);

      const direction = keyCode(event).direction();

      if (!isDialogVisible || !["up", "down"].includes(direction.value)) {
        focusManager.moveFocus(direction);
      }

      if (!isDialogVisible) {
        // run focus recovery on user interaction attempt
        // probably we failed to set initial focus for some reason
        focusManager.recoverFocus();
      }

      if (
        displayState === DISPLAY_STATES.PLAYER ||
        displayState === DISPLAY_STATES.HUD
      ) {
        this.resetHudTimeout();
      }

      if (displayState === DISPLAY_STATES.PLAYER) {
        this.accessibilityManager.addHeading(
          VIDEO_PLAYER_CONTROLS_NAVIGATION_MESSAGE
        );
      }

      return true;
    }
  }

  onScroll(event) {
    const { displayState } = this.props;

    const deltaY = event?.deltaY;

    const DIRECTION = {
      axis: "y",
      isHorizontal: false,
      isVertical: true,
      value: "",
    };

    if (deltaY > 0) {
      DIRECTION.value = isLgPlatform() ? "down" : "up";
    } else if (deltaY < 0) {
      DIRECTION.value = isLgPlatform() ? "up" : "down";
    }

    if (
      displayState === DISPLAY_STATES.PLAYER ||
      displayState === DISPLAY_STATES.HUD
    ) {
      this.resetHudTimeout();
    } else {
      focusManager.moveFocus(DIRECTION);
    }
  }

  onMouseDown(event) {
    const { button } = event;
    const { displayState } = this.props;

    if (
      button === 1 && // number 1 represents press on the scroll wheel
      (displayState === DISPLAY_STATES.PLAYER ||
        displayState === DISPLAY_STATES.HUD)
    ) {
      this.resetHudTimeout();
    }
  }

  onKeyboardStateChange(event) {
    this.setState({ isKeyboardVisible: event.visibility });
  }

  onConfirmDialogOpen() {
    this.confirmDialog.showDialog();
  }

  onConfirmDialogClose() {
    this.confirmDialog.hideDialog();

    const context: FocusManager.FocusContext = {
      source: "cancel",
      preserveScroll: true,
    };

    // restore initial focus after closing dialog(with preserve scrolling)
    focusManager.setInitialFocus(undefined, context);
  }

  handleInputFocus = (event: FocusEvent) => {
    if (!shouldUseOnScreenKeyboard()) return;
    const target = event.target as HTMLInputElement;

    if (this.isDismissing) return;

    if (
      (target.tagName === "INPUT" && target.type === "text") ||
      (target.tagName === "INPUT" && target.type === "password") ||
      target.tagName === "TEXTAREA"
    ) {
      this.setState({
        isKeyboardVisible: true,
        keyboardInput: target.value || "",
        activeInputRef: target,
      });
    }
  };

  handleInputBlur(event: FocusEvent) {
    if (!shouldUseOnScreenKeyboard()) return;

    const target = event.target as HTMLElement;

    if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
      const relatedTarget = event.relatedTarget as HTMLElement;

      if (
        !relatedTarget ||
        (relatedTarget.tagName !== "INPUT" &&
          relatedTarget.tagName !== "TEXTAREA")
      ) {
        this.setState({ isKeyboardVisible: false });
      }
    }
  }

  handleKeyboardDismiss() {
    this.isDismissing = true;

    this.setState(
      {
        isKeyboardVisible: false,
        keyboardInput: "",
        activeInputRef: null,
      },
      () => {
        this.isDismissing = false;
        focusManager.recoverFocus();
      }
    );
  }

  handleKeyboardInput = (value: string) => {
    const { activeInputRef } = this.state;

    if (activeInputRef) {
      // Update the input's value
      activeInputRef.value = value;

      // Dispatch keyboard update event
      const keyboardEvent = new CustomEvent("keyboardUpdate", {
        detail: { value },
      });

      document.dispatchEvent(keyboardEvent);
    }

    this.setState({ keyboardInput: value });
  };

  // TODO: use RemoteSequenceHandler class
  private checkSequence(key: number) {
    const SINK_SEQUENCE = [
      KEYS.ArrowUp.keyCode,
      KEYS.ArrowUp.keyCode,
      KEYS.ArrowDown.keyCode,
      KEYS.ArrowDown.keyCode,
      KEYS.ArrowLeft.keyCode,
      KEYS.ArrowRight.keyCode,
      KEYS.ArrowLeft.keyCode,
      KEYS.ArrowRight.keyCode,
    ];

    this.resetSequenceTimeout();
    this.userSequence.push(key);

    const shouldAddSink =
      this.userSequence.toString() === SINK_SEQUENCE.toString();

    if (shouldAddSink) {
      this.props.xray.addRemoteSink({});
    }
  }

  private resetSequenceTimeout() {
    const SEQUENCE_ENTRY_TIME = 1900;

    if (this.sequenceTimeout) {
      clearTimeout(this.sequenceTimeout);
    }

    this.sequenceTimeout = setTimeout(() => {
      this.sequenceTimeout = null;
      this.userSequence = [];
    }, SEQUENCE_ENTRY_TIME);
  }

  // TODO: Delete it on next PR if not needed
  private handleLongPress = (event: CustomEvent) => {
    const { keyCode } = event.detail;
    this.longPressActive = true;
    this.cancelHudTimeout();

    switch (keyCode) {
      case KEYS.Enter.code:
      case KEYS.X.code:
        focusManager.longPress();
        break;
    }
  };

  // TODO: Delete it on next PR if not needed
  private handleLongPressRelease = (event: CustomEvent) => {
    const { keyCode } = event.detail;
    this.longPressActive = false;
    this.resetHudTimeout();

    switch (keyCode) {
      case KEYS.Enter.code:
      case KEYS.X.code:
        // focusManager.pressOut();
        break;
    }
  };

  private keySupportsLongPress(event: KeyboardEvent): boolean {
    return [
      KEYS.Forward.code,
      KEYS.Rewind.code,
      KEYS.Enter.code,
      KEYS.Z.code,
      KEYS.X.code,
      KEYS.C.code,
    ].includes(event.code);
  }

  handlePhysicalKeyboardDismiss(event: CustomEvent) {
    const { value, position, inputElement } = event.detail;

    this.isDismissing = true;

    this.setState(
      {
        isKeyboardVisible: false,
        keyboardInput: "",
        activeInputRef: null,
      },
      () => {
        if (inputElement) {
          inputElement.value = value;
          inputElement.focus();
          inputElement.setSelectionRange(position, position);

          const keyboardEvent = new CustomEvent("keyboardUpdate", {
            detail: { value },
          });

          document.dispatchEvent(keyboardEvent);
        }

        this.isDismissing = false;
      }
    );
  }

  render() {
    return (
      (shouldUseOnScreenKeyboard() && this.state.isKeyboardVisible && (
        <OnScreenKeyboard
          value={this.state.keyboardInput}
          onInput={this.handleKeyboardInput}
          onDismiss={this.handleKeyboardDismiss}
        />
      )) ||
      null
    );
  }
}

export const InteractionManager = R.compose(
  withNavigator,
  withXray,
  connectToStore(R.pick(["rivers"])),
  PlayerContentContext.withConsumer,
  DisplayStateContext.withConsumer,
  withBackToTopActionHOC
)(InteractionManagerClass);
