import * as React from 'react';
import { ImageStyle, TextStyle, ViewStyle } from 'react-native';

export type Theme = Record<string, any>;
export type Themes = Record<string, Theme>;
export type ExtractThemeNames<T extends Themes> = keyof T;
export type ExtractThemes<T extends Themes> = T[keyof T];
export type NamedStyles<T> = {
  [P in keyof T]: ViewStyle | TextStyle | ImageStyle;
};

export type Styles<S> = NamedStyles<S> | NamedStyles<any>;
export type BaseStyles<S> = NamedStyles<S> | NamedStyles<any> | undefined;
export type StyleObj<S extends NamedStyles<S> | NamedStyles<any>> = S;
export type BaseStyleObj<BS extends BaseStyles<BS>> =
  BS extends NamedStyles<any> ? StyleObj<BS> : undefined;
// export type CombinedStyleObj<
//   S extends Styles<S>,
//   BS extends BaseStyles<BS>,
//   BSKey extends string,
// > = BS extends undefined ? S : S & Record<BSKey, BS>;
export type CombinedStyleObj<
  S extends Styles<S>,
  BS extends BaseStyles<BS>,
  BSKey extends string,
> = BS extends undefined ? S : S & { [key in BSKey]: BS };

export type ThemeContextValue<T extends Themes, ThemeKey extends string, TP> = {
  selectedTheme: ExtractThemeNames<T>;
  themes: T;
  themeParams: TP;
  themeKey: ThemeKey;
} & { [key in ThemeKey]: ExtractThemes<T> };
export type ThemeBaseStylesContextValue<
  BS extends BaseStyles<BS>,
  BSKey extends string,
> = {
  baseStyles: BaseStyleObj<BS>;
  baseStylesKey: BSKey;
};

export type ThemeDispatchContextValue<T extends Themes, TP> = {
  setTheme: (t: ExtractThemeNames<T>) => void;
  setThemeParams: (p: TP) => void;
};
export type ThemeContextProps<
  T extends Themes,
  BS extends Styles<BS>,
  BSKey extends string,
  ThemeKey extends string,
  TP,
> = {
  initialThemeParams?: TP;
  themes: T | ((params: TP) => T);
  initialTheme: ExtractThemeNames<T>;
  onThemeParamsChange?: (nextParams: TP) => void;
  onThemeChange?: (nextThemeName: ExtractThemeNames<T>) => void;
  baseStylesCreator?: BaseStyleCreator<T, BS>;
  baseStylesKey?: BSKey;
  children: React.ReactNode;
  styleCacheManager?: StyleCacheManager<T, any, any>;
  themeKey?: ThemeKey;
};

export type InitThemeProviderParams<
  T extends Themes,
  BS extends Styles<BS>,
  BSKey extends string,
  ThemeKey extends string,
  P = undefined,
> = Omit<ThemeContextProps<T, BS, BSKey, ThemeKey, P>, 'children'>;

export type StyleCreator<
  T extends Themes,
  S extends Styles<S>,
  P = undefined,
> = (theme: ExtractThemes<T>, params: P) => StyleObj<S>;
/*
  `BS extends Styles<BS>` is correct here. The creator has to return styles, never undefined
 */
export type BaseStyleCreator<T extends Themes, BS extends Styles<BS>> = (
  theme: ExtractThemes<T>,
) => StyleObj<BS>;

export type StyleCacheManager<
  T extends Themes,
  S extends Styles<S>,
  P = undefined,
> = {
  /**
   * Called when mounting the ThemeProvider.
   */
  onProviderMount?: () => void;
  /**
    Modify your cache if theme changes
  */
  onThemeChange: (themeName: keyof T) => void;
  /**
    Modify your cache if theme params change
  */
  onThemeParamsChange: (themeName: keyof T) => void;
  /**
    Called for each style creator - function inside createStyle or createUseStyle.
    It receives the style creator and allows to return cached value instead of running the style creator again leading to styles recalculation.
  */
  onCacheStyleCreator: (
    styleCreator: StyleCreator<T, S, P>,
  ) => StyleCreator<T, S, P>;
};

// export type WithUseStyleProps<
//   S extends Styles<S>,
//   BS extends BaseStyles<BS>,
// > = {
//   styles: CombinedStyleObj<S, BS>;
// };

export type WithUseStyleProps<
  F extends (...args: any) => CombinedStyleObj<any, any, string>,
> = {
  styles: ReturnType<F>;
};
