import * as R from "ramda";
import dayjs from "dayjs";
import validateColor from "validate-color";

import { toNumberWithDefault, toNumberWithDefaultZero } from "../numberUtils";
import { transformColorCode as fixColorHexCode } from "../transform";
import { capitalize, isString } from "../stringUtils";
import { cellUtilsLogger } from "./logger";
import { isWeb } from "../reactUtils";
import { isNotNil } from "../reactUtils/helpers";

const CUSTOM_KEY = "other";

/**
 * Gets key for label if other return custom key, otherwise returns selected data key.
 * @param {String} dataKey selected data key for label.
 * @param {String} customKey custom data key for label.
 * @returns {String[]} keys splitted by '.' separator
 */
export const getKeyForLabel = (dataKey, customKey) => {
  const keyToUse = dataKey === CUSTOM_KEY ? customKey : dataKey;

  if (keyToUse === null || keyToUse === undefined) {
    return [];
  }

  return keyToUse.split(".");
};

/**
 * This method will return true if the argument passed to it is either empty or nil
 * The method prevents zero from being evaluated as falsey in an || condition
 * i.e. - NaN, null, {}, "", [], should all return true
 * @returns {Boolean} returns true if empty or nil
 * @param value
 */

export const isEmptyOrNil = (value: any): boolean =>
  Number.isNaN(value) || value == null || R.isEmpty(value);

/**
 * This method is used to find the appropriate label for any given text field.
 * We don't know if users want to use properties from feed, or write the value
 * so we give them the option to do both.
 * - dataKeyValue is either a path to a string, or a string that will be used as a label.
 * - customDataKeyValue follows the same as above
 * @param {String} dataKeyValue - path (extensions.free) or string (More).
 * @param {String} customDataKeyValue - path (extensions.duration) or string (Watch Series).
 * @param {Object} entry - feed item or entry
 * @returns {String} label - a string retrieved either from path, or default to string.
 */
export const getLabel = (dataKeyValue, customDataKeyValue) => (entry) => {
  const label = getKeyForLabel(dataKeyValue, customDataKeyValue);
  const isPath = label && R.length(label) > 0;
  const fallback = isEmptyOrNil(label) ? "" : R.head(label);

  try {
    return isPath ? R.pathOr(fallback, label, entry) : fallback;
  } catch (error) {
    cellUtilsLogger.warning({
      data: { error, dataKeyValue, customDataKeyValue },
      message: error.message,
    });

    return "";
  }
};

/**
 * Gets a string like "16x9" and returns a number value (float) for aspect ratio
 * @param {String} ratioString specified on zapp
 * @param {String} customRatio user defined aspect ratio string
 * @returns {Number} aspectRatio, floating point number extracted from string, or from cell default
 */
export const getAspectRatio = (ratioString, customRatio = "16x9") => {
  const defaultAspectRatio = "16x9";

  const replaceColon = (ratio) => ratio && R.replace(":", "x", R.__)(ratio);
  const conversionFn = R.compose(R.apply(R.divide), R.split("x"), R.toLower);

  const aspectRatio =
    ratioString === "other"
      ? replaceColon(customRatio)
      : replaceColon(ratioString);

  const convertRatio = R.compose(
    R.when(R.equals(NaN), R.always(conversionFn(defaultAspectRatio))),
    Number,
    conversionFn
  );

  return aspectRatio
    ? convertRatio(aspectRatio)
    : convertRatio(defaultAspectRatio);
};

/**
 * This method builds upon isEmptyOrNil method and lets you check if the value is empty
 * If it is empty, return the fallback value provided
 * If not return the original value, 1st argument
 * @param {*} value - value will be evaluated, if it has a value it will be returned
 * @param {*} fallback - if value was empty or nil, return this fallback
 * @returns {*} returns value or fallback
 */
export const ifEmptyUseFallback = (value, fallback) => {
  if (isEmptyOrNil(value)) {
    return fallback;
  } else {
    return value;
  }
};

/**
 * This method transforms text for platforms that do not support the react native prop
 * 'default' returns original string, otherwise it returns transformed string
 * @param {String} transformType - 'default', 'uppercase', 'lowercase', 'capitalize
 * @param {String} value - string to be transformed
 * @returns {String} returns original string or transformed string
 */
const capitalizeArray = R.compose(R.join(" "), R.map(capitalize));

export const textTransform = (transformType, value) => {
  const transformation = ifEmptyUseFallback(transformType, "default");

  if (!isString(value) || transformation === "default") return value;

  if (transformation === "uppercase") {
    return value.toUpperCase();
  }

  if (transformation === "lowercase") {
    return value.toLowerCase();
  }

  if (transformation === "capitalize") {
    const valueArray = value.trim().split(" ");

    if (valueArray.length > 1) {
      return capitalizeArray(valueArray);
    }

    return capitalize(value);
  }
};

/**
 * Get cell width specified at component level
 * Used for components that do not use cell scaling
 * @param {Object} componentStyles i.e. { horizontal_list_cell_width: 384 }
 * @returns {Number} i.e. 384
 */
export const getCellWidth = (componentStyles) => {
  const DEFAULT_CELL_WIDTH = 1920;

  const cellWidth = R.compose(
    R.prop(R.__, componentStyles),
    R.find(R.includes("cell_width")),
    R.keys
  )(componentStyles);

  return Number(cellWidth) || DEFAULT_CELL_WIDTH;
};

/**
 * Get cell height with given asepect ratio and width
 * @param {Number} width i.e. 1920
 * @param {Number} aspectRatio i.e. 1.7777
 * @returns {Number | String} i.e. 1080
 */
export const getHeight = (width, aspectRatio) => {
  const DEFAULT_CELL_HEIGHT = 216;
  const height = R.divide(width, aspectRatio);

  return toNumberWithDefault(DEFAULT_CELL_HEIGHT, height);
};

/**
 *
 *  This function for provided number of seconds returns formatted duration time mm:ss
 * @param {number} seconds
 * @returns {string} duration
 */

export const getDurationInMinutes = (seconds?: number): string => {
  if (!seconds) return "00:00";
  const minutes = Math.floor(Number(seconds) / 60);
  const secondsReminder = Math.floor(Number(seconds)) % 60;
  const pad = (num: number): string => `${num < 10 ? "0" : ""}${num}`;
  const hours = Math.floor(Number(minutes) / 60);
  const minutesReminder = Number(minutes) % 60;

  if (hours < 1) {
    return `${pad(minutes)}:${pad(secondsReminder)}`;
  }

  return `${pad(hours)}:${pad(minutesReminder)}:${pad(secondsReminder)}`;
};

const isTruthy = R.either(R.equals(true), R.equals("true"));

/**
 * This functionr returns the url of the lock badge depending on the
 * data in the cell. We look for the value of the lock_badge_data_key if it exists,
 * and return undefined if it doesn't. if the key doesn't exist, the badge isn't shown.
 * If the key exists, a truthy value will show the unlocked_badge, and a falsy value
 * will show the locked_badge. This is because the default key is called "free", and a true
 * value expects to show the unlocked badge, when a falsy value should show the lock. While
 * this makes sense for content locking, it could prove counter-intuitive when using this
 * mechanism to toggle other badges.
 * @param {Function} getValueFromCellConfig - function to get the value for a specific key
 * from the cell config
 * @param {Object} data - data used to populate the cell. always a ZappEntry
 * @param {string} lockBadgeDataKey - key pointing to the property in the entry used to decide
 * if the lock badge should be shown
 * @returns {string | null}
 */
export const getLockBadge = R.curryN(
  2,
  (getValueFromCellConfig, data, lockBadgeDataKey) => {
    try {
      return R.compose(
        R.unless(
          R.isNil,
          R.ifElse(
            isTruthy,
            R.always(getValueFromCellConfig("unlocked_badge")),
            R.always(getValueFromCellConfig("locked_badge"))
          )
        ),
        R.pathOr(null, R.split(".", lockBadgeDataKey))
      )(data);
    } catch (e) {
      return null;
    }
  }
);

/**
 *
 *  This function format date according to provided
 * @param {string} format - format mask
 * @param {string} text - string to be formatted
 * @returns {string} formatted string
 */
export const dateFormat = (format, text) => {
  if (isEmptyOrNil(format)) {
    cellUtilsLogger.debug({
      data: { format, text },
      message: "Date format not provided",
    });

    return text;
  }

  const dayjsObject = dayjs(text);

  if (dayjsObject.isValid()) {
    return dayjsObject.format(format);
  } else {
    cellUtilsLogger.debug({
      data: { format, text },
      message: "Text is not valid date",
    });

    return text;
  }
};

/**
 *
 *  This function returns the intended custom badge for tv cells
 * @param {function} value - return the needed value of style from cell styles layout
 * @param {boolean} focused - whether the focused type should be returned
 */
export const customTypesMap = (value, focused) => {
  const suffix = focused ? "_focused" : "";

  return {
    [value("content_badge_content_type_custom_badge_1")]: value(
      `content_badge_custom_badge_1${suffix}`
    ),
    [value("content_badge_content_type_custom_badge_2")]: value(
      `content_badge_custom_badge_2${suffix}`
    ),
    [value("content_badge_content_type_custom_badge_3")]: value(
      `content_badge_custom_badge_3${suffix}`
    ),
    [value("content_badge_content_type_custom_badge_4")]: value(
      `content_badge_custom_badge_4${suffix}`
    ),
    [value("content_badge_content_type_custom_badge_5")]: value(
      `content_badge_custom_badge_5${suffix}`
    ),
  };
};

export const ifElseFocusedSelected = (
  state: string,
  focusedValue,
  selectedValue,
  focusedAndSelectedValue,
  defaultValue
) => {
  switch (state) {
    case "focused":
      return focusedValue;
    case "selected":
      return selectedValue;
    case "focused_selected":
      return focusedAndSelectedValue;
    default:
      return defaultValue;
  }
};

/**
 * Gets a style key from the style object and returns a value that can be used in styles
 * @param {String} key i.e. button_icon_width
 * @param {Object} stylesObj styles object with keys from zapp { button_icon_width: "10" }
 * @returns {*} value, returns any type that can be used as styles
 */

export const getValue = (stylesObj) => (key) => {
  return R.propOr(null, key, stylesObj);
};

export const CELL_CONFIG_ID_PATH = ["styles", "cell_plugin_configuration_id"];

export const getCellConfigPath = R.path(CELL_CONFIG_ID_PATH);

export const getPropComponentType = R.prop("component_type");

/**
 *
 * Returns second argument if state equals focused
 * returns third otherwise
 * curried
 * @param {string} state
 * @param {*} focusedValue
 * @param {*} defaultValue
 */
export const ifElseFocused = R.curry(
  (state: string, focusedValue, defaultValue) =>
    state === "focused" ? focusedValue : defaultValue
);

export const ifElseSelected = R.curry(
  (state: string, selectedValue, defaultValue) => {
    switch (state) {
      case "selected":
        return selectedValue;
      case "focused_selected":
        return selectedValue;
      default:
        return defaultValue;
    }
  }
);

/**
 * Map textAlign values to their corresponding alignSelf values
 * This is so that we could use the same field for both properties
 * @param {String} horizontalPosition i.e. bottom_text_label_3
 * @returns {String} alignSelf property i.e. flex-end, flex-start, center
 */
export const mapSelfAlignment = (horizontalPosition) => {
  switch (horizontalPosition) {
    case "left":
      return "flex-start";
    case "right":
      return "flex-end";
    case "center":
      return "center";
    case "middle":
      return "center";
    default:
      return "flex-start";
  }
};

export const castAndFallbackNumber = toNumberWithDefaultZero;

const DEFAULT_COLOR = "#FFFFFF";

export const castAndFallbackColor = R.ifElse(
  R.isNil,
  R.always(DEFAULT_COLOR),
  fixColorHexCode
);

export const getTextTransform = (textTransform: string) => {
  if (R.isNil(textTransform) || R.isEmpty(textTransform)) {
    return "none";
  }

  if (textTransform.toLowerCase() === "default") {
    return "none";
  }

  return textTransform;
};

export const withAntialising = () => {
  if (isWeb()) {
    return {
      WebkitFontSmoothing: "antialiased",
      MozOsxFontSmoothing: "grayscale",
    };
  }

  return {};
};

export const compact = (xs) => xs.filter(isNotNil);

export const mergeConfiguration = (configuration, config) =>
  R.evolve({ configuration }, config);

export const extractCellConfiguration =
  (manifestConfiguration) => (component, cellStyles) => {
    const cellConfigId = getCellConfigPath(component);
    const componentType = getPropComponentType(component);

    const cellStylesConfig = Object.assign(cellStyles[cellConfigId], {
      componentType,
    });

    return mergeConfiguration(manifestConfiguration, cellStylesConfig);
  };

export const toResizeMode = (
  imageSizing: string,
  isDisplayModeFixed: boolean
) => {
  if (isDisplayModeFixed) {
    const resizeMode = imageSizing === "fit" ? "contain" : "cover";

    return {
      resizeMode,
    };
  }

  return {
    resizeMode: "contain",
  };
};

type GetColorFromData = {
  data: Record<string, any>;
  valueFromLayout: string;
};

export const getColorFromData = ({
  data,
  valueFromLayout,
}: GetColorFromData): string => {
  // Temporary hack to fix color validation when alpha is floating point number
  // https://github.com/dreamyguy/validate-color/issues/44
  if (validateColor(valueFromLayout.replace(".00", ""))) {
    return valueFromLayout;
  }

  const pathValue = R.path(valueFromLayout.split("."), data);

  if (pathValue && validateColor(pathValue)) {
    return pathValue;
  }

  return valueFromLayout;
};

export const getImageStyles = ({
  image,
  value,
}: {
  image: any;
  value: any;
}) => {
  return {
    width: image.imageContentWidth,
    height: image.imageContentHeight,
    aspectRatio: image.aspectRatio,
    borderRadius: toNumberWithDefaultZero(value("image_corner_radius")),
  };
};

export const getImageContainerPaddingStyles = ({ value }: { value: any }) => {
  return {
    paddingTop: value("image_padding_top"),
    paddingBottom: value("image_padding_bottom"),
    paddingLeft: value("image_padding_left"),
    paddingRight: value("image_padding_right"),
  };
};

export const getImageContainerMarginStyles = ({ value }: { value: any }) => {
  return {
    marginTop: value("image_margin_top"),
    marginBottom: value("image_margin_bottom"),
    marginLeft: value("image_margin_left"),
    marginRight: value("image_margin_right"),
  };
};
