import * as R from "ramda";
import { isEmptyOrNil } from "@applicaster/zapp-react-native-utils/cellUtils";
import { createLogger, utilsLogger } from "../../logger";
import { Player } from "./player";
import { findNodeHandle } from "react-native";
import { PlayerRole } from "./conts";
import { toBooleanWithDefaultFalse } from "../../booleanUtils";
import { RNPlayerManager } from "./nativePlayerManager";

const logger = createLogger({
  category: "PlayerNativeController",
  subsystem: "General",
  parent: utilsLogger,
});

const { log_warning } = logger;

type PlayerPlugnId = "QuickBrickPlayerPlugin" | "KalturaPlayerPlugin";

type Props = {
  player: PlayerRef;
  listener?: QuickBrickPlayer.SharedPlayerCallBacks | null;
  config?: Record<string, any>;
  playerId: string;
  autoplay?: boolean;
  entry: ZappEntry;
  playerPluginId?: PlayerPlugnId;
};

const PlayerModuleFuncNames = {
  seekTo: "seekTo",
  forward: "forward",
  rewind: "rewind",
  play: "play",
  pause: "pause",
  startSleepTimer: "startSleepTimer",
  cancelSleepTimer: "cancelSleepTimer",
  getSleepTimerEnd: "getSleepTimerEnd",
};

export class PlayerNative extends Player {
  private readonly playerComponent?: PlayerRef | null;

  constructor(props: Props) {
    super(props);

    this.entry = props.entry;
    this.playerComponent = props.player;
    this.playerPluginId = props.playerPluginId;

    this.startPosition = this.getContinueWatchingOffset({
      entry: this.getEntry() || (props.listener as any)?.entry,
      ignoreContinueWatching: this.playerRole === PlayerRole.Cell,
    });
  }

  getContentPosition = () => {
    return this.playerState.contentPosition;
  };

  currentPlayerComponent = (): ReactComponent<PlayerProps> =>
    this.playerComponent?.current;

  getState = () => this.playerState;

  // TODO: Try to add module interface
  invokeNativeFunction = (funcName: string, ...params: any[]): boolean => {
    const module = this.getPlayerModule();
    const node = this.getComponentNode();
    const func = module?.[funcName];

    if (!(node && func)) {
      return false;
    }

    func(node, ...params);

    return true;
  };

  getPlayerModule = () => {
    return RNPlayerManager?.[this.playerPluginId];
  };

  getComponentNode = () => {
    const component =
      (this.playerComponent?.current as any)?._root ||
      (this.playerComponent?.current as any);

    return findNodeHandle(component);
  };

  getInstance = () => {
    return this.playerComponent;
  };

  // Methods to call actions on the player
  seekTo = (position: number) => {
    if (!this.playerState.seekableDuration) {
      log_warning("seekTo: Can not seek, video is not seekable");

      return;
    }

    const newPosition = R.clamp(0, this.playerState.seekableDuration, position);

    if (
      !this.invokeNativeFunction(
        PlayerModuleFuncNames.seekTo,
        newPosition,
        1000
      )
    ) {
      if (isEmptyOrNil(this.playerState.seekPosition)) {
        this.playerState.seekPosition = this.playerState.contentPosition;
      }

      this.playerState.seekPosition = newPosition;

      this.currentPlayerComponent()?.["seeking"](this.playerState.seekPosition);
    }

    this.logState(PlayerModuleFuncNames.seekTo, { position });
  };

  forward = (deltaTime: number) => {
    if (isEmptyOrNil(this.playerState.contentPosition)) {
      return;
    }

    if (!this.invokeNativeFunction(PlayerModuleFuncNames.forward, deltaTime)) {
      // Kaltura does not have yet this implementation, use legacy code

      this.currentPlayerComponent()?.["forward"](deltaTime);
    }

    this.notifyPlayHeadPositionUpdate();

    this.logState(PlayerModuleFuncNames.forward, { deltaTime });
  };

  rewind = (deltaTime: number) => {
    if (isEmptyOrNil(this.playerState.contentPosition)) {
      return;
    }

    if (!this.invokeNativeFunction(PlayerModuleFuncNames.rewind, deltaTime)) {
      // Kaltura does not have yet this implementation, use legacy code

      this.currentPlayerComponent()?.["rewind"](deltaTime);
    }

    this.notifyPlayHeadPositionUpdate();

    this.logState(PlayerModuleFuncNames.rewind, { deltaTime });
  };

  play = () => {
    if (!this.invokeNativeFunction(PlayerModuleFuncNames.play)) {
      this.currentPlayerComponent()?.[PlayerModuleFuncNames.play]?.();
    }
  };

  pause = () => {
    if (!this.invokeNativeFunction(PlayerModuleFuncNames.pause)) {
      this.currentPlayerComponent()?.[PlayerModuleFuncNames.pause]?.();
    }
  };

  startSleepTimer = (sleepTimestamp: number) => {
    if (
      !this.invokeNativeFunction(
        PlayerModuleFuncNames.startSleepTimer,
        sleepTimestamp
      )
    ) {
      this.currentPlayerComponent()?.["startSleepTimer"](sleepTimestamp);
    }
  };

  cancelSleepTimer = () => {
    if (!this.invokeNativeFunction(PlayerModuleFuncNames.cancelSleepTimer)) {
      this.currentPlayerComponent()?.[
        PlayerModuleFuncNames.cancelSleepTimer
      ]?.();
    }
  };

  disableBufferAnimation = (): boolean => false;

  selectTrack = (
    selected: QuickBrickPlayer.TextTrack | QuickBrickPlayer.AudioTrack
  ) => {
    this.currentPlayerComponent()?.["selectTrack"]?.(selected);
  };

  setPlaybackRate = (rate: number) => {
    this.currentPlayerComponent()?.["setPlaybackRate"]?.(rate);
  };

  getPluginConfiguration = () => {
    return (this.currentPlayerComponent() as any)?.props?.pluginConfiguration;
  };

  getProps = () => (this.currentPlayerComponent() as any)?.props;

  appStateChange = (appState, previousAppState) => {
    this.currentPlayerComponent()?.["appStateChange"]?.(
      appState,
      previousAppState
    );
  };

  closeNativePlayer = () => {
    // TODO: Delete does not work
    this.currentPlayerComponent()?.["closeNativePlayer"]?.();
  };

  togglePlayPause = () => {
    this.currentPlayerComponent()?.["togglePlayPause"]?.();
  };

  onVideoLoad = (event) => {
    if (this.startPosition) {
      super.onVideoLoad({ ...event, ignoreContinueWatching: true });
      this.seekTo(this.startPosition);
      this.startPosition = null;
    } else {
      super.onVideoLoad(event);
    }
  };

  // This function preserves current chromecast content position,
  // So we can resume from the same position if chromecast session is closed by user
  // Because, current position is lost when view is recreated.
  onVideoProgress = (event) => {
    super.onVideoProgress(event);

    // TODO: Use native player view reference
    const nativePlayerModule = (this.playerComponent?.current as any)?._root;

    if (!nativePlayerModule) {
      if (this.playerRole === PlayerRole.Cell) {
        return;
      }

      this.startPosition = event.currentTime;
    }
  };

  // This function is called when native player used directly without wrapped component,
  // Since event from native comes with wrapped in NativeEvent property
  getNativeViewListener = (): QuickBrickPlayer.SharedPlayerCallBacks => {
    const listeners = this.getListener();

    const wrapNativeEvent = (event, callerFunc) => {
      if (!event) {
        callerFunc();
      }

      callerFunc(event?.nativeEvent || event);
    };

    return R.map((f) => {
      return (event) => wrapNativeEvent(event, f);
    }, listeners);
  };

  isAudioItem = () =>
    this.getEntry()?.content?.type?.includes?.("audio") ||
    this.getEntry()?.type?.value === "audio";

  isFullScreenSupported = (): boolean => {
    const config = this.getConfig();

    const disableFullScreen = config?.["disable_fullscreen"];

    if (disableFullScreen) {
      return false;
    }

    const isFullScreenAudioPlayer = config?.["full_screen_audio_player"];

    return !(isFullScreenAudioPlayer && this.isAudioItem());
  };

  supportsNativeControls = (): boolean =>
    toBooleanWithDefaultFalse(this.getConfig()?.["supports_native_controls"]);

  supportNativeCast = (): boolean =>
    toBooleanWithDefaultFalse(
      this.playerPluginId === "quick-brick-player-brightcove"
    );
}
