/// <reference types="@types/tizen-tv-webapis" />
import * as R from "ramda";

import { NetInfoState } from "@react-native-community/netinfo";
import { NETWORK_HOOKS_KEYS } from "./const";
import {
  isVizioPlatform,
  platformSelect,
} from "@applicaster/zapp-react-native-utils/reactUtils";
import { Platform } from "react-native";

type ConnectionStatus = boolean;
type NetInfoNotReady = null;

// Cmn eslint... It's not a hook...
// eslint-disable-next-line react-hooks/rules-of-hooks
export const pluckPath = R.useWith(R.map, [R.path, R.identity]);

export const callAllWith = (func: ((...any) => void)[], arg) =>
  R.forEach((x) => R.call(x, arg), func);

export const getNetworkHooks = (plugins): NetworkHooks => {
  const filterModulesWithNetworkHooks = R.filter(
    R.where({ module: R.prop("networkHooks") })
  );

  const networkHooks = R.compose(
    // @ts-ignore
    pluckPath(["module", "networkHooks"]),
    filterModulesWithNetworkHooks
  )(plugins);

  return R.compose(
    R.reject(R.isEmpty),
    R.reduce(
      (acc, key) =>
        R.mergeLeft(
          acc,
          // @ts-ignore
          R.compose(
            // @ts-ignore
            R.objOf(key),
            R.without([undefined]),
            // @ts-ignore
            R.pluck(key)
          )(networkHooks)
        ),
      {}
    )
  )(NETWORK_HOOKS_KEYS);
};

export function isConnectionInfoReady(
  connectionInfo: NetInfoState & { deviceStatus?: NetStatus }
) {
  const { details, isConnected, isInternetReachable, deviceStatus } =
    connectionInfo;

  const hasNetInfo = !R.isNil(isConnected) && !R.isNil(isInternetReachable);
  const hasDeviceInfo = !R.isNil(deviceStatus?.online);

  // Details returns null on web when offline, it is incorrect to label it as non-ready
  return platformSelect({
    native: !!details && !R.isNil(isConnected) && !R.isNil(isInternetReachable),
    web: hasDeviceInfo || hasNetInfo,
  });
}

export const isOnline = (
  connectionInfo: NetInfoState & { deviceStatus?: NetStatus }
): ConnectionStatus | NetInfoNotReady => {
  if (!isConnectionInfoReady(connectionInfo)) {
    return true;
  }

  const netInfoIsOnline =
    connectionInfo.isConnected === true &&
    connectionInfo.isInternetReachable === true;

  const deviceIsOnline = connectionInfo?.deviceStatus?.online;

  return deviceIsOnline || netInfoIsOnline;
};

// REGISTERED DEVICE LISTENERS
let registeredListener = 0;

/**
 * This method detects whether we have the necesarry APIs to check network status
 * We have created a few ways to detect whether the target device is a Samsung Tizen
 * isSamsungPlatform checks if there is a global tizen object, or checks the UserAgent
 * The only thing missing is whether we have the necesarry WebAPIs. This method does that.
 */
export const hasTizenAPIs = () => {
  const hasWebAPI = typeof webapis !== "undefined";
  const isSamsungOS = Platform.OS === ("samsung_tv" as string);

  if (isSamsungOS && hasWebAPI) {
    return true;
  } else {
    return false;
  }
};

/**
 * This method is designed to be a handler for Tizen's networkStateChangeListener
 * The first argument is a callback provided by whatever is requesting the network state info
 * The second argument is invoked by the networkStateChangeListener and it is supposed to return networkState
 * ChangeCb - https://developer.samsung.com/smarttv/develop/api-references/samsung-product-api-references/network-api.html#1.3-NetworkStateChangedCallback
 * NetworkState - https://developer.samsung.com/smarttv/develop/api-references/samsung-product-api-references/network-api.html#1.6-NetworkState
 */
const samsungNetworkChangeHandler = (callback: NetCb) => (networkState) => {
  let online: NetStatus["online"] = null;

  const details: NetStatus["details"] = {
    internetConnected: null,
    lanCableAttached: null,
    gatewayConnected: null,
    wifiModuleStateAttached: null,
    networkState,
  };

  if (webapis.network.NetworkState) {
    const {
      LAN_CABLE_ATTACHED,
      LAN_CABLE_DETACHED,
      LAN_CABLE_STATE_UNKNOWN,
      GATEWAY_CONNECTED,
      GATEWAY_DISCONNECTED,
      WIFI_MODULE_STATE_ATTACHED,
      WIFI_MODULE_STATE_DETACHED,
      WIFI_MODULE_STATE_UNKNOWN,
    } = webapis.network.NetworkState;

    // @ts-ignore
    const { INTERNET_CONNECTED, INTERNET_DISCONNECTED } =
      webapis.network.NetworkState;
    // Our type declaration says internet enums don't exist
    // Link says otherwise ^

    switch (networkState) {
      case GATEWAY_DISCONNECTED:
        details.gatewayConnected = false;
        online = false;
        break;

      case GATEWAY_CONNECTED:
        details.gatewayConnected = true;
        online = true;
        break;

      case INTERNET_CONNECTED:
        details.internetConnected = true;
        online = true;

        break;

      case INTERNET_DISCONNECTED:
        details.internetConnected = false;
        online = false;

        break;

      case LAN_CABLE_ATTACHED:
        details.lanCableAttached = true;
        break;

      case LAN_CABLE_DETACHED:
        details.lanCableAttached = false;
        break;

      case LAN_CABLE_STATE_UNKNOWN:
        details.lanCableAttached = null;
        break;

      case WIFI_MODULE_STATE_ATTACHED:
        details.wifiModuleStateAttached = true;
        break;

      case WIFI_MODULE_STATE_DETACHED:
        details.wifiModuleStateAttached = false;
        break;

      case WIFI_MODULE_STATE_UNKNOWN:
        details.wifiModuleStateAttached = null;
        break;

      default:
        break;
    }
  }

  return callback({ online, details });
};

/**
 * This method helps you subscribe to networkchanges for each device. It is designed to replace NetInfo.
 * The NetInfo library does not seem to work in all test scenarios on the web tvs so this method uses the native
 * APIs for each device. At the moment we only support Tizen which is using the native addNetworkStateChangeListener
 * https://developer.samsung.com/smarttv/develop/api-references/samsung-product-api-references/network-api.html#NetworkManager-addNetworkStateChangeListener
 * The native Samsung listener returns an ID which can be used to unregister the listener.
 * TODO: come up with way to store IDs for unregister, at the moment using local var in memory
 */
export const subscribeToDeviceNetworkChanges = (callback: NetCb) => {
  if (hasTizenAPIs()) {
    registeredListener = webapis.network.addNetworkStateChangeListener(
      samsungNetworkChangeHandler(callback)
    );

    return;
  }

  if (isVizioPlatform()) {
    window.addEventListener("online", function () {
      callback({ online: true });
    });

    window.addEventListener("offline", function () {
      callback({ online: false });
    });
  }
};

/**
 * Method used to unsubscribe to any device network changes or updates
 * The only thing you need to unsubscribe is the id given to you when you subscribed
 * https://developer.samsung.com/smarttv/develop/api-references/samsung-product-api-references/network-api.html#NetworkManager-removeNetworkStateChangeListener
 */
export const unsubscribeToDeviceNetworkChanges = (id = registeredListener) => {
  if (hasTizenAPIs()) {
    webapis.network.removeNetworkStateChangeListener(id);

    return;
  }

  if (isVizioPlatform()) {
    window.removeEventListener("online", function () {});
    window.removeEventListener("offline", function () {});
  }
};
