import * as React from "react";

import { BaseFocusable as BaseFocusableInterface } from "./BaseFocusable.interface";

import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
import * as FOCUS_EVENTS from "@applicaster/zapp-react-native-utils/appUtils/focusManager/events";
import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";

type Props = {
  initialFocus?: boolean;
  id: string;
  groupId: string;
  preferredFocus?: boolean;
  onRegister?: (focusable: FocusManager.TouchableRef) => void;
  onUnregister?: (focusable: FocusManager.TouchableRef) => void;
  willReceiveFocus?: FocusManager.FocusEventCB;
  hasReceivedFocus?: FocusManager.FocusEventCB;
  willLoseFocus?: FocusManager.FocusEventCB;
  hasLostFocus?: FocusManager.FocusEventCB;
  failedLostFocus?: FocusManager.FocusEventCB;
  onPress?: (nativeEvent: React.SyntheticEvent) => void;
  onFocus?: FocusManager.FocusEventCB;
  onBlur?: FocusManager.FocusEventCB;
  selected?: boolean;
};

export class BaseFocusable<
  T extends Props = Props,
> extends BaseFocusableInterface<T> {
  constructor(props) {
    super(props);

    this._isMounted = false;

    this.state = {
      focused: false,
      boundingRect: {
        x: 0,
        y: 0,
        width: 0,
        height: 0,
        top: 0,
        bottom: 0,
        left: 0,
        right: 0,
      },
    };

    this.onRegister = this.onRegister.bind(this);
    this.onUnregister = this.onUnregister.bind(this);
    this.setFocusedState = this.setFocusedState.bind(this);
    this.willReceiveFocus = this.willReceiveFocus.bind(this);
    this.onFocus = this.onFocus.bind(this);
    this.hasReceivedFocus = this.hasReceivedFocus.bind(this);
    this.willLoseFocus = this.willLoseFocus.bind(this);
    this.onBlur = this.onBlur.bind(this);
    this.hasLostFocus = this.hasLostFocus.bind(this);
    this.failedLostFocus = this.failedLostFocus.bind(this);
    this.onPress = this.onPress.bind(this);
    this.ref = React.createRef();
  }

  componentDidMount() {
    const { id } = this.props;
    const component = this;
    this.node = this.ref.current;

    focusManager.register({
      id,
      component: component,
    });
  }

  measureView() {
    this.ref.current.measure((x, y, width, height, pageX, pageY) => {
      const top = pageY;
      const bottom = top + height;
      const left = pageX;
      const right = left + width;

      if (this._isMounted) {
        this.setState({
          boundingRect: {
            x,
            y,
            width,
            height,
            top,
            bottom,
            left,
            right,
          },
        });
      }
    });
  }

  getId() {
    const { id } = this.props;

    return id;
  }

  getRect() {
    const { boundingRect } = this.state;

    return {
      top: boundingRect?.top,
      left: boundingRect?.left,
      right: boundingRect?.right,
      bottom: boundingRect?.bottom,
      width: boundingRect?.width,
      height: boundingRect?.height,
      x: boundingRect?.x,
      y: boundingRect?.y,
    };
  }

  componentWillUnmount() {
    this._isMounted = false;
    const { id } = this.props;
    focusManager.unregister(id, { group: this.isGroup || false });
  }

  onRegister(focusable) {
    this._isMounted = true;
    const { onRegister = noop } = this.props;
    onRegister(focusable);
  }

  onUnregister(focusable) {
    const { onUnregister = noop } = this.props;
    onUnregister(focusable);
  }

  willLoseFocus(focusable, scrollDirection) {
    const { willLoseFocus = noop } = this.props;
    willLoseFocus(focusable, scrollDirection);
  }

  willReceiveFocus(focusable, scrollDirection) {
    const { willReceiveFocus = noop } = this.props;
    willReceiveFocus(focusable, scrollDirection);
  }

  /**
   * will invoke the underlying component's focus method
   * @param {Object} focusable - sender
   * @param {Object} scrollDirection
   * @returns {Promise}
   */
  onFocus(focusable, scrollDirection) {
    const { onFocus = noop } = this.props;
    this.setFocusedState(true);
    onFocus(focusable, scrollDirection);

    focusManager.invokeHandler?.(
      FOCUS_EVENTS.FOCUS,
      focusable,
      scrollDirection
    );
  }

  /**
   * will invoke the underlying component's hasReceivedFocus method
   * @param {Object} focusable - sender
   * @param {Object} scrollDirection
   * @returns {Promise}
   */
  hasReceivedFocus(focusable, scrollDirection) {
    const { hasReceivedFocus = noop } = this.props;
    hasReceivedFocus(focusable, scrollDirection);
  }

  /**
   * will invoke the underlying component's hasLostFocus method
   * @param {Object} focusable - sender
   * @param {Object} scrollDirection
   * @returns {Promise}
   */
  hasLostFocus(focusable, scrollDirection) {
    const { hasLostFocus = noop } = this.props;
    hasLostFocus(focusable, scrollDirection);
  }

  /**
   * will invoke the underlying component's failedLostFocus method
   * @param {Object} focusable - sender
   * @param {Object} scrollDirection
   * @returns {Promise}
   */
  failedLostFocus(focusable, scrollDirection) {
    const { failedLostFocus = noop } = this.props;
    failedLostFocus(focusable, scrollDirection);
  }

  /**
   * will invoke the underlying component's onBlur method
   * @param {Object} focusable - sender
   * @param {Object} scrollDirection
   * @returns {Promise}
   */
  onBlur(focusable, scrollDirection) {
    const { onBlur = noop } = this.props;
    this.setFocusedState(false);
    onBlur(focusable, scrollDirection);
  }

  /**
   * will invoke the underlying component's onPress method
   * @param {Object} keyEvent
   * @returns {Promise}
   */
  onPress(keyEvent) {
    const { onPress = noop } = this.props;
    onPress(keyEvent);
  }

  isInGroup(group) {
    const { groupId } = this.props;
    const { id } = group.props;

    return id === groupId;
  }

  setFocusedState(focused) {
    if (this._isMounted) {
      this.setState({ focused });
    }
  }
}
