import get from 'lodash.get';
import omit from 'lodash.omit';
import isNil from 'lodash.isnil';
import merge from 'lodash.merge';
import { useNativeBase } from '../useNativeBase';
import { usePlatformProps } from '../usePlatformProps';
import { useColorModeProps } from '../useColorModeProps';
import { useColorMode } from '../../core/color-mode';
import {
  resolveValueWithBreakpoint,
  extractPropertyFromFunction,
} from './utils';
import { omitUndefined, extractInObject } from './../../theme/tools';
import { themePropertyMap } from './../../theme/base';
import { useContrastText } from '../useContrastText';
import { shouldEnableNewStyledSystemImplementation } from '../../utils/styled';
import { useNativeBaseConfig } from '../../core/NativeBaseContext';

/**
 * @summary Resolves, simplify and merge components specific theme.
 * @description The function extract and execute component specifc theme (i.e., `defaultProps`, `baseStyle` and `variants`) and returns a merged and simplified object.
 * @arg {string} theme - Theme object of NativeBase (without component theme).
 * @arg {object} componentTheme - Theme for specific components.
 * @arg {object} incomingProps - Props passed by the user.
 * @arg {object} colorModeProps - `colorMode` object.
 * @returns {object} Resolved component specific theme.
 */
const useSimplifyComponentTheme = (
  theme: object,
  componentTheme: {
    defaultProps?: object;
    baseStyle?: object | Function;
    variants?: any | Function;
    sizes?: any | Function;
  },
  incomingProps: object,
  colorModeProps: object,
  currentBreakpoint: number
) => {
  let sizeResolved = false;
  let hasVariant = false;
  // Resolving component's defaultProps.

  // Shallow merge defaultProps and incoming props to pass them in baseStyle, variants and sizes
  const combinedProps: any = {
    ...componentTheme.defaultProps,
    ...incomingProps,
  };

  // Resolving component's baseStyle
  let componentBaseStyle = {};
  if (componentTheme.baseStyle) {
    componentBaseStyle =
      typeof componentTheme.baseStyle !== 'function'
        ? componentTheme.baseStyle
        : componentTheme.baseStyle({
            theme,
            ...combinedProps,
            ...colorModeProps,
          });
  }

  // To handle responsive type variant. i.e. {sm:"x"} | [x, y]
  const variant = resolveValueWithBreakpoint(
    combinedProps.variant,
    currentBreakpoint,
    'variant'
  );

  let componentVariantProps = {};
  // Extracting props from variant
  if (variant && componentTheme.variants && componentTheme.variants[variant]) {
    componentVariantProps =
      typeof componentTheme.variants[variant] !== 'function'
        ? componentTheme.variants[variant]
        : componentTheme.variants[variant]({
            theme,
            ...combinedProps,
            ...colorModeProps,
          });

    hasVariant = true;
    //@ts-ignore
    incomingProps.variant = undefined;
  }

  // To handle responsive size prop. i.e. {sm:"x"} | [x, y]
  const size = resolveValueWithBreakpoint(
    combinedProps.size,
    currentBreakpoint,
    'size'
  );

  let componentSizeProps: any = {};
  // Extracting props from size
  if (size && componentTheme.sizes && componentTheme.sizes[size]) {
    // Type - sizes: {lg: 1}. Refer icon theme
    if (
      typeof componentTheme.sizes[size] === 'string' ||
      typeof componentTheme.sizes[size] === 'number'
    ) {
      //@ts-ignore
      componentSizeProps.size = componentTheme.sizes[size];
    }
    // Type - sizes: (props) => ({lg: {px: 1}}). Refer heading theme
    else if (typeof componentTheme.sizes[size] === 'function') {
      componentSizeProps = componentTheme.sizes[size]({
        theme,
        ...combinedProps,
        ...colorModeProps,
      });
      sizeResolved = true;
    }
    // Type - sizes: {lg: {px: 1}}. Refer button theme
    else {
      componentSizeProps = componentTheme.sizes[size];
      // Type - sizes: { size: 4 }. Refer Images
      if (!componentSizeProps.size) {
        sizeResolved = true;
      }
    }
    // @ts-ignore - Mutating incoming size for now. Fix it after new styled system is implemented
    incomingProps.size = undefined;
  }

  const componentMergedTheme = merge(
    {},
    componentTheme.defaultProps,
    componentBaseStyle,
    componentVariantProps,
    componentSizeProps
  );

  if (sizeResolved) {
    // We remove size from original props if we found it in the componentTheme
    // @ts-ignore
    delete componentMergedTheme.size;
  }

  if (hasVariant) {
    // We remove variant from original props if we found it in the componentTheme
    // @ts-ignore
    delete componentMergedTheme.variant;
  }

  const componentThemeIntegratedProps = merge(
    {},
    useColorModeProps(usePlatformProps(componentMergedTheme)),
    useColorModeProps(usePlatformProps(incomingProps))
  );

  return componentThemeIntegratedProps;
};

/**
 * @summary Translates the prop with it's appropriate value.
 * @description NOTE: Avoid passo  JSX and functions.
 * @arg {any} props - Props object with should be translated.
 * @arg {any} theme - Theme based on which props will be translated.
 * @arg {object} colorModeProps - `colorMode` object.
 * @arg {object} componentTheme - Theme for specific components.
 * @arg {object} windowWidth - Current width of the window / screen.
 * @returns {object} Translated props object.
 */
// Todo - move responsive calculation in styled system
const propTranslator = ({
  props,
  theme,
  colorModeProps,
  componentTheme,
  currentBreakpoint,
}: {
  props: any;
  theme: any;
  colorModeProps: object;
  componentTheme: object;
  currentBreakpoint: number;
}) => {
  let translatedProps: any = {};
  for (const property in props) {
    // STEP 1 - Responsive prop check and resolve
    if (property.startsWith('_')) {
      // STEP 1.a - Resolving _ porps
      const nestedTranslatedProps = propTranslator({
        props: props[property],
        theme,
        colorModeProps,
        componentTheme,
        currentBreakpoint,
      });
      translatedProps[property] = nestedTranslatedProps;
    } else if (themePropertyMap[property]) {
      // STEP 1.b Resolving themed props
      const propValues = extractPropertyFromFunction(
        property,
        props,
        theme,
        componentTheme
      );

      // NOTE: Direct value identified.
      if (typeof propValues === 'string' || typeof propValues === 'number') {
        translatedProps[property] = propValues;
        // NOTE: Nested object (excluding _props) (To be specific, only for key exist in themePropertyMap)
      } else if (!isNil(propValues)) {
        // TODO: This setion new needs to handle stuff differently
        for (let nestedProp in propValues) {
          translatedProps[nestedProp] = get(
            theme,
            `${themePropertyMap[nestedProp]}.${propValues[nestedProp]}`,
            propValues[nestedProp]
          );
        }
        delete translatedProps[property];
        // Manually handeling shadow props (example of Mapped tokens)
      } else if (property === 'shadow') {
        const resolveValueWithBreakpointValue = resolveValueWithBreakpoint(
          props.shadow,
          currentBreakpoint,
          property
        );
        let shadowProps = theme[themePropertyMap[property]](colorModeProps)[
          resolveValueWithBreakpointValue
        ];
        translatedProps.style = merge({}, shadowProps, props.style);
        delete translatedProps[property];
      } else {
        translatedProps[property] = resolveValueWithBreakpoint(
          props[property],
          currentBreakpoint,
          property
        );
      }
    } else {
      // STEP 1.d Resolving Direct Values
      translatedProps[property] = resolveValueWithBreakpoint(
        props[property],
        currentBreakpoint,
        property
      );
    }
  }

  return translatedProps;
};

/**
 * @summary Combines provided porps with component's theme props and resloves them.
 * @description NOTE: Avoid passing JSX and functions.
 * @arg {string} component - Name of the component.
 * @arg {object} incomingProps - Props passed by the user.
 * @returns {object} Resolved props.
 */
export function usePropsResolution(
  component: string,
  incomingProps: any,
  config?: any
) {
  const [ignoredProps, cleanIncomingProps] = extractInObject(
    incomingProps,
    ['children', 'onPress', 'icon', 'onOpen', 'onClose'].concat(
      config?.ignoreProps || []
    )
  );

  const { theme } = useNativeBase();
  const colorModeProps = useColorMode();

  const componentTheme = get(theme, `components.${component}`, {});
  const notComponentTheme = omit(theme, ['components']);

  const currentBreakpoint = useNativeBaseConfig('usePropsResolution')
    .currentBreakpoint;

  // TODO: using usePlatformProps here to simplify the component theme. So that on on component level it shouldn't have to maintain the Specificity.
  const componentThemeIntegratedProps = useSimplifyComponentTheme(
    notComponentTheme,
    componentTheme,
    cleanIncomingProps,
    colorModeProps,
    currentBreakpoint
  );
  // const platformSpecificProps = usePlatformProps(componentThemeIntegratedProps);

  // NOTE: sperating removing props while should be translated
  let ignore: any = [];
  if (
    componentThemeIntegratedProps.bg?.linearGradient ||
    componentThemeIntegratedProps.background?.linearGradient ||
    componentThemeIntegratedProps.bgColor?.linearGradient ||
    componentThemeIntegratedProps.backgroundColor?.linearGradient
  ) {
    let bgProp = 'bg';
    if (componentThemeIntegratedProps.background?.linearGradient) {
      bgProp = 'background';
    } else if (componentThemeIntegratedProps.bgColor?.linearGradient) {
      bgProp = 'bgColor';
    } else if (componentThemeIntegratedProps.backgroundColor?.linearGradient) {
      bgProp = 'backgroundColor';
    }
    componentThemeIntegratedProps[
      bgProp
    ].linearGradient.colors = componentThemeIntegratedProps[
      bgProp
    ].linearGradient.colors.map((color: string) => {
      return get(theme.colors, color, color);
    });
    ignore = ['bg', 'background', 'backgroundColor', 'bgColor'];
  }
  // NOTE: seprating bg props when linearGardiant is available
  const [gradientProps, nonGradientProps] = extractInObject(
    componentThemeIntegratedProps,
    ignore
  );

  let translatedProps = nonGradientProps;

  if (!shouldEnableNewStyledSystemImplementation) {
    translatedProps = propTranslator({
      props: nonGradientProps,
      theme: notComponentTheme,
      colorModeProps,
      componentTheme,
      currentBreakpoint,
    });
  }

  let bgColor =
    translatedProps.bg ??
    translatedProps.backgroundColor ??
    translatedProps.bgColor;

  const contrastTextColor = useContrastText(
    bgColor,
    translatedProps?._text?.color
  );

  translatedProps._text = contrastTextColor
    ? {
        color: contrastTextColor,
        ...translatedProps._text,
      }
    : translatedProps._text;

  const resolvedProps = omitUndefined({
    ...translatedProps,
    ...ignoredProps,
    ...gradientProps,
  });

  return resolvedProps;
}
