import {CSSProperties, MediaQuery, NestedCSSProperties} from '../types';

/** Raf for node + browser */
export const raf: (cb: () => void) => void =
  typeof requestAnimationFrame === 'undefined'
    /**
     * Make sure setTimeout is always invoked with
     * `this` set to `window` or `global` automatically
     **/
    ? (cb) => setTimeout(cb)
    /**
     * Make sure window.requestAnimationFrame is always invoked with `this` window
     * We might have raf without window in case of `raf/polyfill` (recommended by React)
     **/
    : typeof window === 'undefined'
      ? requestAnimationFrame
      : requestAnimationFrame.bind(window);

/**
 * Utility to join classes conditionally
 */
export function classes(...classes: (string | false | undefined | null | { [className: string]: any })[]): string {
  return classes
    .map(c => c && typeof c === 'object' ? Object.keys(c).map(key => !!c[key] && key) : [c])
    .reduce((flattened, c) => flattened.concat(c), [] as string[])
    .filter(c => !!c)
    .join(' ');
}

/**
 * Merges various styles into a single style object.
 * Note: if two objects have the same property the last one wins
 */
export function extend(...objects: (NestedCSSProperties | undefined | null | false)[]): NestedCSSProperties {
  /** The final result we will return */
  const result: CSSProperties & Record<string, any> = {};
  for (const object of objects) {
    if (object == null || object === false) {
      continue;
    }
    for (const key in object) {

      /** Falsy values except a explicit 0 is ignored */
      const val: any = (object as any)[key];
      if (!val && val !== 0) {
        continue;
      }

      /** if nested media or pseudo selector */
      if (key === '$nest' && val) {
        result[key] = result['$nest'] ? extend(result['$nest'], val) : val;
      }
      /** if freestyle sub key that needs merging. We come here due to our recursive calls */
      else if ((key.indexOf('&') !== -1 || key.indexOf('@media') === 0)) {
        result[key] = result[key] ? extend(result[key], val) : val;
      }
      else {
        result[key] = val;
      }
    }
  }

  return result;
}


/**
 * Utility to help customize styles with media queries. e.g.
 * ```
 * style(
 *  media({maxWidth:500}, {color:'red'})
 * )
 * ```
 */
export const media = (mediaQuery: MediaQuery, ...objects: (NestedCSSProperties | undefined | null | false)[]): NestedCSSProperties => {
  const mediaQuerySections: string[] = [];
  if (mediaQuery.type) mediaQuerySections.push(mediaQuery.type);
  if (mediaQuery.orientation) mediaQuerySections.push(`(orientation: ${mediaQuery.orientation})`);
  if (mediaQuery.minWidth) mediaQuerySections.push(`(min-width: ${mediaLength(mediaQuery.minWidth)})`);
  if (mediaQuery.maxWidth) mediaQuerySections.push(`(max-width: ${mediaLength(mediaQuery.maxWidth)})`);
  if (mediaQuery.minHeight) mediaQuerySections.push(`(min-height: ${mediaLength(mediaQuery.minHeight)})`);
  if (mediaQuery.maxHeight) mediaQuerySections.push(`(max-height: ${mediaLength(mediaQuery.maxHeight)})`);
  if (mediaQuery.prefersColorScheme) mediaQuerySections.push(`(prefers-color-scheme: ${mediaQuery.prefersColorScheme})`);

  const stringMediaQuery = `@media ${mediaQuerySections.join(' and ')}`;

  const object: NestedCSSProperties = {
    $nest: {
      [stringMediaQuery]: extend(...objects)
    }
  };
  return object;
}

const mediaLength = (value: number | string) =>
  typeof value === 'string' ? value : `${value}px`;
