import React, { useMemo } from "react";
import * as R from "ramda";
import memoizee from "memoizee";

import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
import { isValidColor } from "@applicaster/zapp-react-native-utils/colorUtils";
import { getColorFromData } from "@applicaster/zapp-react-native-utils/cellUtils";
import { pathOr, get, isNil } from "@applicaster/zapp-react-native-utils/utils";
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks";
import { useScreenStateStore } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useScreenStateStore";

import { isNotEmptyString } from "@applicaster/zapp-react-native-utils/stringUtils";
import { masterCellLogger } from "../logger";
import { getCellState } from "../../Cell/utils";
import { isCellSelected, useBehaviorUpdate } from "./behaviorProvider";

const hasElementSpecificViewType = (viewType) => (element) => {
  if (isNil(element)) {
    return false;
  }

  if (element.type === viewType) {
    return true;
  }

  // eslint-disable-next-line @typescript-eslint/no-use-before-define
  return hasElementsSpecificViewType(viewType)(element.elements);
};

export const hasElementsSpecificViewType = (viewType) => (elements) => {
  if (isNilOrEmpty(elements)) {
    return false;
  }

  return R.any(hasElementSpecificViewType(viewType))(elements);
};

const logWarning = (
  colorValueFromCellStyle,
  colorFromProp,
  colorValueFromEntry
) => {
  if (isNil(colorValueFromEntry)) {
    masterCellLogger.warn({
      message: `Cannot resolve property ${colorValueFromCellStyle} from the entry.`,
      data: {
        configurationValue: colorValueFromCellStyle,
        colorFromProp,
      },
    });
  }
};

export function resolveColorForProp(
  entry: any,
  colorFromProp: string | undefined
) {
  if (!colorFromProp) {
    return undefined;
  }

  const nestedEntryValue: string | undefined = get(
    entry,
    colorFromProp.split(".")
  );

  if (nestedEntryValue === undefined && !isValidColor(colorFromProp)) {
    logWarning(colorFromProp, colorFromProp, nestedEntryValue);

    return undefined;
  }

  const colorValue = getColorFromData({
    data: entry,
    valueFromLayout: colorFromProp,
  });

  if (!colorValue) {
    logWarning(colorFromProp, colorFromProp, nestedEntryValue);

    return undefined;
  }

  return colorValue;
}

const getColorKeys = memoizee((style) => {
  const styleKeys = Object.keys(style);

  return styleKeys.filter((key) => /color/i.test(key));
});

/**
 * A color style value is either a literal color (e.g. "#FFFFFF") or a
 * data-mapping path into the entry. Data mappings always point at the entry
 * `extensions` object (e.g. "extensions.brandColor"), so we only need to do a
 * path lookup when the value starts with "extensions" — everything else is a
 * literal color and can be returned as-is.
 *
 * This avoids the previous, very expensive approach that ran `validateColor`
 * on every value and memoized on a full `fast-json-stable-stringify` of the
 * entire entry.
 */
const EXTENSIONS_PREFIX = "extensions";

const isDataMappingPath = (value: string): boolean =>
  typeof value === "string" && value.startsWith(EXTENSIONS_PREFIX);

export const resolveColor = (
  entry,
  style,
  allowDynamicColorsOutsideExtensions
) => {
  if (style === null || style === undefined) {
    return style;
  }

  const colorKeys = getColorKeys(style);

  if (colorKeys.length === 0) {
    return style;
  }

  return colorKeys.reduce(
    (acc, key) => {
      const value = acc[key];

      // 1. The Expensive Edge-Case (Only for the app with root mappings)
      if (allowDynamicColorsOutsideExtensions && isNotEmptyString(value)) {
        const possibleColor = resolveColorForProp(entry, value);

        acc[key] = isValidColor(possibleColor) ? possibleColor : null;

        return acc;
      }

      // 2. The Fast Path (For 99% of apps)
      if (isDataMappingPath(value)) {
        // resolve the mapped color from the entry; fall back to null
        // (RN ignores null style values) when the path is missing
        const possibleColor = pathOr(null, value.split("."), entry);

        acc[key] = possibleColor;

        return acc;
      }

      // 3. Default Case: Treat as a raw color string
      acc[key] = value;

      return acc;
    },
    { ...style }
  );
};

export function isVideoPreviewEnabled({
  enable_video_preview = false,
  player_screen_id = null,
}: {
  enable_video_preview: boolean;
  player_screen_id: string | null;
}) {
  return enable_video_preview && !R.isEmpty(player_screen_id);
}

export const useFilterChildren = <
  T extends {
    props: {
      item: any;
      pluginIdentifier: string;
    };
  },
>(
  children: T[]
): T[] => {
  const actions = useActions("");

  const filteredChildren = children.filter((child) => {
    const item = child.props.item;
    const actionIdentifier = child.props.pluginIdentifier;
    const action = actions.actions[actionIdentifier];

    // context value of specific plugin
    if (action?.module && action.module.context) {
      const currentValue = action.module.context._currentValue;

      return currentValue?.isActionAvailable
        ? currentValue.isActionAvailable(item)
        : true;
    }

    masterCellLogger.error({
      message: `Action plugin for ${actionIdentifier} could not be found, check the configuration of your action button`,
      data: { item, action: child.props.pluginIdentifier },
    });

    return false;
  });

  return filteredChildren;
};

export const insertBetween = (separator, items) => {
  return items.reduce((acc, item, index) => {
    if (index + 1 >= items.length) {
      // last element
      acc.push(item);

      return acc;
    }

    acc.push(item);
    acc.push(separator(index));

    return acc;
  }, []);
};

const recursiveCloneElement = (focused: boolean) => (element) => {
  const { children, ...otherProps } = element.props;
  const state = focused ? "focused" : "default";

  return React.cloneElement(element, {
    ...otherProps,
    state,
    // eslint-disable-next-line @typescript-eslint/no-use-before-define
    children: recursiveCloneElementsWithState(focused, children),
  });
};

export const recursiveCloneElementsWithState = (focused: boolean, children) => {
  if (isNilOrEmpty(children)) {
    return undefined;
  }

  return React.Children.map(children, recursiveCloneElement(focused));
};

const next = (currentIndex, items) => items[currentIndex + 1];
const previous = (currentIndex, items) => items[currentIndex - 1];

export const cloneElementsWithIds = (
  ids: string[],
  children: React.ReactElement[]
) => {
  if (isNilOrEmpty(children)) {
    return undefined;
  }

  return React.Children.map(children, (element, index) =>
    React.cloneElement(element, {
      nextFocusLeft: previous(index, ids),
      nextFocusRight: next(index, ids),
    })
  );
};

export const getFocusedButtonId = (focusable) => {
  return platformSelect({
    tvos: R.path(["itemID"], focusable),
    default: R.path(["props", "id"], focusable),
  });
};

export const useCellState = ({
  item,
  behavior,
  focused,
}: {
  item: ZappEntry;
  behavior: Behavior;
  focused: boolean;
}): CellState => {
  const lastUpdate = useBehaviorUpdate(behavior);
  const router = useRoute();
  const screenStateStore = useScreenStateStore();

  const _isSelected = useMemo(
    () =>
      isCellSelected({
        item,
        screenRoute: router?.pathname,
        screenStateStore,
        behavior,
      }),
    [behavior, item, lastUpdate]
  );

  return getCellState({ focused, selected: _isSelected });
};

export const hasFocusableInsideBuilder = (elementsBuilder) => (item) => {
  const elements = elementsBuilder({ entry: item });

  return R.anyPass([
    hasElementsSpecificViewType("ButtonContainerView"),
    hasElementsSpecificViewType("FocusableView"),
  ])(elements);
};

export function getEntryState(state, selected) {
  if (state === "focused_selected") {
    return state;
  }

  if (state === "focused" && selected) {
    return "focused_selected";
  }

  if (state === "focused" && !selected) {
    return "focused";
  }

  if (state === "selected") {
    return "selected";
  }

  return selected ? "selected" : "default";
}
