import { R, React } from '../../libs';
import { value as valueUtil } from '../value';
import {
  IFormatCss,
  IImageOptions,
  IBackgroundImageStyles,
  Falsy,
  GlamorValue,
} from './types';
import { css as glamorCss } from 'glamor';
import { arrayToEdges } from './util';

export * from './util';
export const MEDIA_QUERY_RETINA = `@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)`;

/**
 * Constructs a style object for an image.
 *
 *    For turning image files (PNG/JPG/SVG) into data-uri's see:
 *    https://github.com/webpack/url-loader
 *
 * @param {string} image1x: The normal image resolution (base64 encoded)
 * @param {string} image2x: The retina image resolution (base64 encoded)
 * @param {integer} width: Optional. The width of the image.
 * @param {integer} height: Optional. The height of the image.
 */
export const image = (
  image1x: string | undefined,
  image2x: string | undefined,
  options: IImageOptions = { width: 10, height: 10 },
): IBackgroundImageStyles => {
  // Prepare image based on current screen density.
  if (!image1x) {
    throw new Error('Must have at least a 1x image.');
  }
  const { width, height } = options;
  const result: any = {
    width,
    height,
    backgroundImage: `url(${image1x})`,
    backgroundSize: `${width}px ${height}px`,
    backgroundRepeat: 'no-repeat',
  };

  if (image2x) {
    result[MEDIA_QUERY_RETINA] = {
      backgroundImage: `url(${image2x})`,
    };
  }

  // Finish up.
  return result;
};

const mergeAndReplace = (key: string, value: any, target: any) => {
  Object.assign(target, value);
  delete target[key];
  return target;
};

const formatImage = (
  key: string,
  value: Array<string | number | undefined>,
  target: any,
) => {
  // Wrangle parameters.
  let [image1x, image2x, width, height] = value; // tslint:disable-line

  if (R.is(Number, image2x)) {
    height = width;
    width = image2x;
    image2x = undefined;
  }
  const options = {
    width: width as number,
    height: height as number,
  };
  const style = image(image1x as string, image2x as string, options);
  mergeAndReplace(key, style, target);
};

export const toPositionEdges = (
  key: string,
  value: any = undefined,
):
  | {
      position: string;
      top: number | string | undefined;
      right: number | string | undefined;
      bottom: number | string | undefined;
      left: number | string | undefined;
    }
  | undefined => {
  const edges = arrayToEdges(value);

  if (!edges) {
    return undefined;
  }
  const { left, top, right, bottom } = edges;

  if (
    top === undefined &&
    right === undefined &&
    bottom === undefined &&
    left === undefined
  ) {
    return undefined;
  }
  return {
    position: key.toLowerCase(),
    top,
    right,
    bottom,
    left,
  };
};

export const formatPositionEdges = (key: string, target: any) => {
  const styles = toPositionEdges(key, target[key]);
  mergeAndReplace(key, styles, target);
};

/**
 * AbsoluteCenter
 *      - x
 *      - y
 *      - xy
 */
const formatAbsoluteCenter = (
  key: string,
  value: string | boolean | number,
  target: any,
) => {
  if (value === true) {
    value = 'xy';
  }
  if (value === false || value === undefined || value === null) {
    return;
  }
  const styles = {
    position: 'absolute',
    left: target.left,
    top: target.top,
    transform: '',
  };
  const stringValue = value
    .toString()
    .trim()
    .toLowerCase();
  if (stringValue.includes('x')) {
    styles.left = '50%';
  }
  if (stringValue.includes('y')) {
    styles.top = '50%';
  }
  let transform: string;
  switch (value) {
    case 'yx':
    case 'xy':
      transform = 'translate(-50%, -50%)';
      break;
    case 'x':
      transform = 'translateX(-50%)';
      break;
    case 'y':
      transform = 'translateY(-50%)';
      break;
    default:
      throw new Error(`AbsoluteCenter value '${value}' not supported.`);
  }
  styles.transform = `${target.transform || ''} ${transform}`.trim();
  mergeAndReplace(key, styles, target);
};

/**
 * Spacing on the X:Y plane.
 */
function formatSpacingPlane(
  plane: 'x' | 'y' | 'xy',
  prefix: 'margin' | 'padding',
  key: string,
  value: any,
  target: any,
) {
  const styles = {};
  const edges = arrayToEdges(value);
  if (edges && plane.includes('x')) {
    styles[`${prefix}Left`] = edges.left;
    styles[`${prefix}Right`] = edges.right;
  }
  if (edges && plane.includes('y')) {
    styles[`${prefix}Top`] = edges.top;
    styles[`${prefix}Bottom`] = edges.bottom;
  }
  mergeAndReplace(key, styles, target);
}

/**
 * Sets up vertical scrolling including iOS momentum scrolling.
 * See:
 *    https://css-tricks.com/snippets/css/momentum-scrolling-on-ios-overflow-elements/
 */
function formatScroll(key: string, value: any, target: any) {
  if (value === true) {
    const styles = {
      overflowX: 'hidden',
      overflowY: 'scroll',
      WebkitOverflowScrolling: 'touch',
    };
    mergeAndReplace(key, styles, target);
  }

  if (value === false) {
    const styles = {
      overflow: 'hidden',
    };
    mergeAndReplace(key, styles, target);
  }
}

// --------------------------------------------------

const AlignMap: { [k: string]: string } = {
  center: 'center',
  left: 'flex-start',
  top: 'flex-start',
  start: 'flex-start',
  right: 'flex-end',
  bottom: 'flex-end',
  end: 'flex-end',
  full: 'stretch',
  stretch: 'stretch',
  baseline: 'baseline',
};
function convertCrossAlignToFlex(token: string): string | undefined {
  return AlignMap[token] || undefined; // undefined if not recognised;
}

const MainAlignMap: { [k: string]: string } = {
  center: 'center',
  left: 'flex-start',
  top: 'flex-start',
  start: 'flex-start',
  right: 'flex-end',
  bottom: 'flex-end',
  end: 'flex-end',
  spaceBetween: 'space-between',
  spaceAround: 'space-around',
  spaceEvenly: 'space-evenly',
};
function convertMainAlignToFlex(token: string): string | undefined {
  return MainAlignMap[token] || undefined; // undefined if not recognised;
}

/**
 * Format a flex css helper
 * Format: [<direction>]-<crossAlignment>-<mainAlignment>
 */
function formatFlexPosition(
  key: string,
  value: string,
  target: React.CSSProperties,
) {
  let direction: 'row' | 'column' | undefined; // Assume horizontal
  let mainAlignment: string | undefined;
  let crossAlignment: string | undefined;

  // Tokenize string
  const tokens: string[] = value.split('-').map(token => token.trim());

  tokens.map(token => {
    const tokenIsOneOf = (options: string[]) => options.includes(token);
    if (direction == null && tokenIsOneOf(['horizontal', 'vertical'])) {
      direction = token === 'vertical' ? 'column' : 'row'; // tslint:disable-line
      return;
    }

    if (
      tokenIsOneOf([
        'center',
        'start',
        'end',
        'left',
        'right',
        'top',
        'bottom',
        'full',
        'baseline',
      ])
    ) {
      if (crossAlignment == null) {
        if (direction == null && tokenIsOneOf(['left', 'right'])) {
          direction = 'column';
        }
        if (direction == null && tokenIsOneOf(['top', 'bottom'])) {
          direction = 'row';
        }
        crossAlignment = convertCrossAlignToFlex(token);
        return;
      }
      mainAlignment = convertMainAlignToFlex(token);
      return;
    }

    if (tokenIsOneOf(['spaceAround', 'spaceBetween', 'spaceEvenly'])) {
      mainAlignment = convertMainAlignToFlex(token);
      return;
    }
  });

  const styles = {
    display: 'flex',
    flexDirection: direction,
    alignItems: crossAlignment,
    justifyContent: mainAlignment,
  };

  mergeAndReplace(key, styles, target);
}

export const transformStyle = (
  style: React.CSSProperties | GlamorValue | Falsy = {},
): React.CSSProperties | GlamorValue => {
  if (style == null) {
    return {};
  }
  if (style === false) {
    return {};
  }
  if (!R.is(Object, style)) {
    return style;
  }
  Object.keys(style).forEach(key => {
    const value = style[key];
    if (value === false || R.isNil(value)) {
      delete style[key];
    } else if (valueUtil.isPlainObject(value)) {
      // NB: This is not using formatCss, as we only want the transform, we don't want to convert it to a glamor value.
      style[key] = transformStyle(value); // <== RECURSION.
    } else {
      switch (key) {
        case 'Image':
          formatImage(key, value, style);
          break;
        case 'Absolute':
          formatPositionEdges(key, style);
          break;
        case 'Fixed':
          formatPositionEdges(key, style);
          break;
        case 'AbsoluteCenter':
          formatAbsoluteCenter(key, value, style);
          break;
        case 'Margin':
          formatSpacingPlane('xy', 'margin', key, value, style);
          break;
        case 'MarginX':
          formatSpacingPlane('x', 'margin', key, value, style);
          break;
        case 'MarginY':
          formatSpacingPlane('y', 'margin', key, value, style);
          break;
        case 'Padding':
          formatSpacingPlane('xy', 'padding', key, value, style);
          break;
        case 'PaddingX':
          formatSpacingPlane('x', 'padding', key, value, style);
          break;
        case 'PaddingY':
          formatSpacingPlane('y', 'padding', key, value, style);
          break;
        case 'Flex':
          formatFlexPosition(key, value, style);
          break;
        case 'Scroll':
          formatScroll(key, value, style);
          break;
        default:
        // Ignore.
      }
    }
  });

  return style;
};

/**
 * Helpers for constructing a CSS object.
 * NB: This doesn't *actually* return React.CSSProperties, but
 */
const formatCss = (
  ...styles: Array<React.CSSProperties | GlamorValue | Falsy>
): GlamorValue => {
  const newStyles = styles.map(transformStyle);

  // Finish up.
  return glamorCss(...newStyles) as {};
};

(formatCss as any).image = image;
export const format = formatCss as IFormatCss;
