import * as React from "react";
import * as R from "ramda";
import { View, StyleSheet } from "react-native";
import { first, filter } from "rxjs/operators";

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

import { Focusable } from "@applicaster/zapp-react-native-ui-components/Components/Focusable/FocusableTvOS";
import { FocusableCell } from "@applicaster/zapp-react-native-ui-components/Components/FocusableCell";
import { getItemType } from "@applicaster/zapp-react-native-utils/navigationUtils";
import { SCREEN_TYPES } from "@applicaster/zapp-react-native-utils/navigationUtils/itemTypes";
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
import { sendSelectCellEvent } from "@applicaster/zapp-react-native-utils/analyticsUtils";
import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
import { toBooleanWithDefaultTrue } from "@applicaster/zapp-react-native-utils/booleanUtils";
import { CellWithFocusable } from "./CellWithFocusable";
import { FocusableWrapper } from "./FocusableWrapper";

import { focusableButtonsRegistration$ } from "@applicaster/zapp-react-native-utils/appUtils/focusManagerAux/utils/utils.ios";
import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";

type Props = {
  item: ZappEntry;
  index: number;
  shouldScrollHorizontally: (
    focusable: any,
    id: string,
    title: string
  ) => boolean | null | undefined;
  shouldScrollVertically: (
    focusable: any,
    id: string,
    title: string
  ) => boolean | null | undefined;
  riverOffsetUpdater: (arg1: string, arg2: number) => number;
  offsetUpdater: (arg1: string, arg2: number) => number;
  componentId: string;
  component: {
    id: number | string;
    component_type: string;
    styles?: {
      component_margin_top?: number;
      component_padding_top?: number;
    };
  };
  selected: boolean;
  CellRenderer: React.FunctionComponent<any> & {
    hasFocusableInside: (item: any) => boolean;
  };
  preferredFocus: boolean;
  navigator: {
    push: (arg1: Record<any, any>) => void;
  };
  onFocus: (arg1?: any, index?: number) => void | null | undefined;
  onBlur: (arg1?: any, index?: number) => void | null | undefined;
  onPress: (arg1?: any, index?: number) => void | null | undefined;
  willReceiveFocus: (arg1?: any, index?: number) => void | null | undefined;
  hasReceivedFocus: (arg1?: any, index?: number) => void | null | undefined;
  hasHeader: boolean;
  isScreenWrappedInContainer: boolean | null | undefined;
  sendOnClickEvent: () => void;
  scrollToIndex: (index: number) => void;
  appData: {
    layoutVersion: "v1" | "v2";
  };
  getHeaderOffset: () => {
    headerOffset: number;
    headersAbove: number;
  };
  componentAnchorPointY: number | null | undefined;
  screenLayout: any;
  headerOffset: number;
  headerOffsetOverride: number | null | undefined;
  focused: boolean;
  groupId: string;
  mainOffsetUpdater: (...args: any[]) => void | null | undefined;
  isFocusable: boolean;
  shouldUpdate: boolean;
  behavior: Behavior;
  componentsMapOffset: number;
  applyFocusableWrapper: boolean;
  hasFocusableInside: boolean;
};

type State = {
  focusedId: string | null;
};

const styles = StyleSheet.create({
  container: { flex: 1 },
});

const baseCellStyles = {
  height: "100%",
  display: "flex",
  flex: 1,
} as const;

class TvOSCell extends React.Component<Props, State> {
  cell: any;
  target: any;
  layout: any;
  constructor(props) {
    super(props);
    this.onPress = this.onPress.bind(this);
    this.onFocus = this.onFocus.bind(this);
    this.onBlur = this.onBlur.bind(this);
    this.willReceiveFocus = this.willReceiveFocus.bind(this);
    this.hasReceivedFocus = this.hasReceivedFocus.bind(this);
    this.scrollTo = this.scrollTo.bind(this);
    this.state = { focusedId: null };
    this.cell = null;
    this.setInitialFocus = this.setInitialFocus.bind(this);
    this.setFocusId = this.setFocusId.bind(this);
    this.unsetFocusId = this.unsetFocusId.bind(this);
    this.onLayout = this.onLayout.bind(this);
  }

  onLayout({ nativeEvent }) {
    const { target, layout } = nativeEvent;
    this.target = target;
    this.layout = layout;
  }

  getFocusedId() {
    return `${this.props?.component?.id}-${this.props?.item?.id}`;
  }

  setInitialFocus() {
    this.setFocusId(this.getFocusedId());
  }

  setFocusId(id) {
    this.setState((prevState) => {
      if (prevState.focusedId === id) {
        return prevState;
      }

      return { focusedId: id };
    });
  }

  unsetFocusId() {
    const itemInFocus = focusManager.getCurrentFocus();
    itemInFocus.onBlur();

    this.setFocusId(null);
  }

  setScreenLayout(componentAnchorPointY, screenLayout) {
    if (componentAnchorPointY && screenLayout) {
      const updateLayout =
        componentAnchorPointY !== screenLayout?.componentAnchorPointY;

      updateLayout && screenLayout?.setScreenLayout({ componentAnchorPointY });
    }
  }

  onPress() {
    const { onPress, item, index } = this.props;

    if (onPress) {
      onPress(item, index);
    } else {
      const { item, navigator, component, appData } = this.props;
      const itemTargetScreen = R.path(["screen_type"], item);
      const componentTargetScreen = R.path(["data", "target"], component);
      const itemType = getItemType(item, appData?.layoutVersion);

      sendSelectCellEvent(item, component, item.title, index);

      if (
        itemType !== SCREEN_TYPES.PLAYABLE &&
        componentTargetScreen &&
        !itemTargetScreen
      ) {
        navigator.push(R.merge(item, { screen_type: componentTargetScreen }));

        return;
      }

      navigator.push(item);
    }
  }

  onFocus(focusable, _idx: number) {
    const {
      item: { id, title },
      shouldScrollVertically,
      screenLayout = null,
      componentAnchorPointY = null,
      mainOffsetUpdater,
      getHeaderOffset,
      groupId,
      component,
      index,
      componentsMapOffset,
    } = this.props;

    this.setScreenLayout(componentAnchorPointY, screenLayout);
    this.setFocusId(id);

    if (
      this.props.shouldUpdate &&
      shouldScrollVertically?.(focusable, String(id), String(title))
    ) {
      const { headerOffset } = getHeaderOffset();

      const extraAnchorPointYOffset = toNumberWithDefaultZero(
        screenLayout?.extraAnchorPointYOffset
      );

      const componentMarginTop = toNumberWithDefaultZero(
        component?.styles?.component_margin_top
      );

      const componentPaddingTop = toNumberWithDefaultZero(
        component?.styles?.component_padding_top
      );

      const totalOffset =
        headerOffset +
        toNumberWithDefaultZero(componentAnchorPointY) +
        extraAnchorPointYOffset -
        toNumberWithDefaultZero(componentsMapOffset) +
        componentMarginTop +
        componentPaddingTop;

      mainOffsetUpdater?.(
        { tag: this.target },
        totalOffset,
        null,
        groupId,
        id,
        component.id,
        index
      );
    }
  }

  onBlur() {
    this.setFocusId(null);
  }

  willReceiveFocus() {}

  hasReceivedFocus() {}

  scrollTo() {}

  render() {
    const {
      item,
      index,
      selected,
      component,
      offsetUpdater,
      preferredFocus,
      CellRenderer,
      onFocus,
      onBlur,
      willReceiveFocus,
      hasReceivedFocus,
      groupId,
      isFocusable,
      behavior,
      applyFocusableWrapper,
      hasFocusableInside,
    } = this.props;

    const { id } = item;
    const focusedId = this.state.focusedId;
    const selectedCell = focusedId || selected;

    const focusableId = R.join("-", [component?.id, id, index]);

    const handleFocus = (arg1: any, index?: number) => {
      const focusFn = onFocus || noop;
      focusFn(arg1, index);

      this.onFocus(arg1, index);
    };

    if (hasFocusableInside) {
      return (
        <View onLayout={this.onLayout}>
          <FocusableWrapper
            id={focusableId}
            groupId={String(groupId || component?.id)}
            isParallaxDisabled={this.layout?.width > 1740}
            onFocus={handleFocus}
            onBlur={onBlur || this.onBlur}
            applyWrapper={applyFocusableWrapper}
          >
            {(focused) => (
              <CellWithFocusable
                CellRenderer={CellRenderer}
                item={item}
                id={focusableId}
                groupId={(groupId || component?.id).toString()}
                onFocus={handleFocus}
                index={index}
                scrollTo={this.scrollTo}
                preferredFocus={preferredFocus}
                focused={focused || this.props.focused}
                behavior={behavior}
                isFocusable={isFocusable}
              />
            )}
          </FocusableWrapper>
        </View>
      );
    }

    return (
      <View onLayout={this.onLayout} style={styles.container}>
        <Focusable
          id={focusableId}
          groupId={String(groupId || component?.id)}
          onFocus={handleFocus}
          onBlur={onBlur || this.onBlur}
          isParallaxDisabled={this.layout?.width > 1740}
          onPress={this.onPress}
          willReceiveFocus={willReceiveFocus || this.willReceiveFocus}
          hasReceivedFocus={hasReceivedFocus || this.hasReceivedFocus}
          preferredFocus={preferredFocus}
          offsetUpdater={offsetUpdater}
          style={baseCellStyles}
          isFocusable={isFocusable}
        >
          {(focused) => (
            <FocusableCell
              {...{
                index,
                CellRenderer,
                item,
                focused: !!(this.props.focused || selectedCell || focused),
                scrollTo: this.scrollTo,
                behavior,
              }}
            />
          )}
        </Focusable>
      </View>
    );
  }
}

export function withFocusableWrapperHOC(Component) {
  return function WrappedComponent(props) {
    const [focusableViewIsRendered, setFocusableViewIsRendered] =
      React.useState(false);

    const { CellRenderer, item, groupId, component } = props;

    const isFocusable = toBooleanWithDefaultTrue(props?.isFocusable);

    const focusableGroupId = String(groupId || component?.id);

    const hasFocusableInside = CellRenderer.hasFocusableInside?.(item);

    React.useEffect(() => {
      // start waiting any first registration of FocusableButton inside this focusableGroup
      // after it we could get rid of applying focusable-wrapper
      const subscription = focusableButtonsRegistration$(focusableGroupId)
        .pipe(
          filter(() => isFocusable),
          first()
        )
        .subscribe(() => {
          setFocusableViewIsRendered(true);
        });

      return () => {
        subscription.unsubscribe();
      };
    }, [isFocusable, focusableGroupId]);

    const applyFocusableWrapper = React.useMemo(() => {
      return isFocusable && hasFocusableInside && !focusableViewIsRendered;
    }, [isFocusable, hasFocusableInside, focusableViewIsRendered]);

    return (
      <Component
        {...props}
        applyFocusableWrapper={applyFocusableWrapper}
        hasFocusableInside={hasFocusableInside}
      />
    );
  };
}

export const TvOSCellComponent = compose(withFocusableWrapperHOC)(TvOSCell);
