text: const Promise = require(`bluebird`)
  const {
    GraphQLObjectType,
    GraphQLBoolean,
    GraphQLString,
    GraphQLInt,
    GraphQLFloat,
    GraphQLJSON,
  } = require(`gatsby/graphql`)
  const qs = require(`qs`)
  const base64Img = require(`base64-img`)
  const _ = require(`lodash`)
  const path = require(`path`)
  
  const cacheImage = require(`./cache-image`)
  
  const {
    ImageFormatType,
    ImageResizingBehavior,
    ImageCropFocusType,
  } = require(`./schemes`)
  
  // @see https://www.contentful.com/developers/docs/references/images-api/#/reference/resizing-&-cropping/specify-width-&-height
  const CONTENTFUL_IMAGE_MAX_SIZE = 4000
  
  const isImage = image =>
    _.includes(
      [`image/jpeg`, `image/jpg`, `image/png`, `image/webp`, `image/gif`],
      _.get(image, `file.contentType`)
    )
  
  const getBase64Image = imageProps => {
    if (!imageProps) return null
  
    const requestUrl = `https:${imageProps.baseUrl}?w=20`
    // TODO add caching.
    return new Promise(resolve => {
      base64Img.requestBase64(requestUrl, (a, b, body) => {
        resolve(body)
      })
    })
  }
  
  const getBasicImageProps = (image, args) => {
    let aspectRatio
    if (args.width && args.height) {
      aspectRatio = args.width / args.height
    } else {
      aspectRatio =
        image.file.details.image.width / image.file.details.image.height
    }
  
    return {
      baseUrl: image.file.url,
      contentType: image.file.contentType,
      aspectRatio,
      width: image.file.details.image.width,
      height: image.file.details.image.height,
    }
  }
  
  const createUrl = (imgUrl, options = {}) => {
    // Convert to Contentful names and filter out undefined/null values.
    const args = _.pickBy(
      {
        w: options.width,
        h: options.height,
        fl: options.jpegProgressive ? `progressive` : null,
        q: options.quality,
        fm: options.toFormat || ``,
        fit: options.resizingBehavior || ``,
        f: options.cropFocus || ``,
        bg: options.background || ``,
      },
      _.identity
    )
    return `${imgUrl}?${qs.stringify(args)}`
  }
  exports.createUrl = createUrl
  
  const resolveFixed = (image, options) => {
    if (!isImage(image)) return null
  
    const { baseUrl, width, aspectRatio } = getBasicImageProps(image, options)
  
    let desiredAspectRatio = aspectRatio
  
    // If no dimension is given, set a default width
    if (options.width === undefined && options.height === undefined) {
      options.width = 400
    }
  
    // If we're cropping, calculate the specified aspect ratio.
    if (options.width !== undefined && options.height !== undefined) {
      desiredAspectRatio = options.width / options.height
    }
  
    // If the user selected a height and width (so cropping) and fit option
    // is not set, we'll set our defaults
    if (options.width !== undefined && options.height !== undefined) {
      if (!options.resizingBehavior) {
        options.resizingBehavior = `fill`
      }
    }
  
    // Create sizes (in width) for the image. If the width of the
    // image is 800px, the sizes would then be: 800, 1200, 1600,
    // 2400.
    //
    // This is enough sizes to provide close to the optimal image size for every
    // device size / screen resolution
    let fixedSizes = []
    fixedSizes.push(options.width)
    fixedSizes.push(options.width * 1.5)
    fixedSizes.push(options.width * 2)
    fixedSizes.push(options.width * 3)
    fixedSizes = fixedSizes.map(Math.round)
  
    // Filter out sizes larger than the image's width and the contentful image's max size.
    const filteredSizes = fixedSizes.filter(size => {
      const calculatedHeight = Math.round(size / desiredAspectRatio)
      return (
        size <= CONTENTFUL_IMAGE_MAX_SIZE &&
        calculatedHeight <= CONTENTFUL_IMAGE_MAX_SIZE &&
        size <= width
      )
    })
  
    // Sort sizes for prettiness.
    const sortedSizes = _.sortBy(filteredSizes)
  
    // Create the srcSet.
    const srcSet = sortedSizes
      .map((size, i) => {
        let resolution
        switch (i) {
          case 0:
            resolution = `1x`
            break
          case 1:
            resolution = `1.5x`
            break
          case 2:
            resolution = `2x`
            break
          case 3:
            resolution = `3x`
            break
          default:
        }
        const h = Math.round(size / desiredAspectRatio)
        return `${createUrl(baseUrl, {
          ...options,
          width: size,
          height: h,
        })} ${resolution}`
      })
      .join(`,\n`)
  
    let pickedHeight, pickedWidth
    if (options.height) {
      pickedHeight = options.height
      pickedWidth = options.height * desiredAspectRatio
    } else {
      pickedHeight = options.width / desiredAspectRatio
      pickedWidth = options.width
    }
  
    return {
      aspectRatio: desiredAspectRatio,
      baseUrl,
      width: Math.round(pickedWidth),
      height: Math.round(pickedHeight),
      src: createUrl(baseUrl, {
        ...options,
        width: options.width,
      }),
      srcSet,
    }
  }
  exports.resolveFixed = resolveFixed
  
  const resolveFluid = (image, options) => {
    if (!isImage(image)) return null
  
    const { baseUrl, width, aspectRatio } = getBasicImageProps(image, options)
  
    let desiredAspectRatio = aspectRatio
  
    // If no dimension is given, set a default maxWidth
    if (options.maxWidth === undefined && options.maxHeight === undefined) {
      options.maxWidth = 800
    }
  
    // If only a maxHeight is given, calculate the maxWidth based on the height and the aspect ratio
    if (options.maxHeight !== undefined && options.maxWidth === undefined) {
      options.maxWidth = Math.round(options.maxHeight * desiredAspectRatio)
    }
  
    // If we're cropping, calculate the specified aspect ratio.
    if (options.maxHeight !== undefined && options.maxWidth !== undefined) {
      desiredAspectRatio = options.maxWidth / options.maxHeight
    }
  
    // If the users didn't set a default sizes, we'll make one.
    if (!options.sizes) {
      options.sizes = `(max-width: ${options.maxWidth}px) 100vw, ${
        options.maxWidth
      }px`
    }
  
    // Create sizes (in width) for the image. If the max width of the container
    // for the rendered markdown file is 800px, the sizes would then be: 200,
    // 400, 800, 1200, 1600, 2400.
    //
    // This is enough sizes to provide close to the optimal image size for every
    // device size / screen resolution
    let fluidSizes = []
    fluidSizes.push(options.maxWidth / 4)
    fluidSizes.push(options.maxWidth / 2)
    fluidSizes.push(options.maxWidth)
    fluidSizes.push(options.maxWidth * 1.5)
    fluidSizes.push(options.maxWidth * 2)
    fluidSizes.push(options.maxWidth * 3)
    fluidSizes = fluidSizes.map(Math.round)
  
    // Filter out sizes larger than the image's maxWidth and the contentful image's max size.
    const filteredSizes = fluidSizes.filter(size => {
      const calculatedHeight = Math.round(size / desiredAspectRatio)
      return (
        size <= CONTENTFUL_IMAGE_MAX_SIZE &&
        calculatedHeight <= CONTENTFUL_IMAGE_MAX_SIZE &&
        size <= width
      )
    })
  
    // Add the original image (if it isn't already in there) to ensure the largest image possible
    // is available for small images.
    if (
      !filteredSizes.includes(parseInt(width)) &&
      parseInt(width) < CONTENTFUL_IMAGE_MAX_SIZE &&
      Math.round(width / desiredAspectRatio) < CONTENTFUL_IMAGE_MAX_SIZE
    ) {
      filteredSizes.push(width)
    }
  
    // Sort sizes for prettiness.
    const sortedSizes = _.sortBy(filteredSizes)
  
    // Create the srcSet.
    const srcSet = sortedSizes
      .map(width => {
        const h = Math.round(width / desiredAspectRatio)
        return `${createUrl(image.file.url, {
          ...options,
          width,
          height: h,
        })} ${Math.round(width)}w`
      })
      .join(`,\n`)
  
    return {
      aspectRatio: desiredAspectRatio,
      baseUrl,
      src: createUrl(baseUrl, {
        ...options,
        width: options.maxWidth,
        height: options.maxHeight,
      }),
      srcSet,
      sizes: options.sizes,
    }
  }
  exports.resolveFluid = resolveFluid
  
  const resolveResize = (image, options) => {
    if (!isImage(image)) return null
  
    const { baseUrl, aspectRatio } = getBasicImageProps(image, options)
  
    // If no dimension is given, set a default width
    if (options.width === undefined && options.height === undefined) {
      options.width = 400
    }
  
    // If the user selected a height and width (so cropping) and fit option
    // is not set, we'll set our defaults
    if (options.width !== undefined && options.height !== undefined) {
      if (!options.resizingBehavior) {
        options.resizingBehavior = `fill`
      }
    }
  
    let pickedHeight = options.height,
      pickedWidth = options.width
  
    if (pickedWidth === undefined) {
      pickedWidth = pickedHeight * aspectRatio
    }
  
    if (pickedHeight === undefined) {
      pickedHeight = pickedWidth / aspectRatio
    }
  
    return {
      src: createUrl(image.file.url, options),
      width: Math.round(pickedWidth),
      height: Math.round(pickedHeight),
      aspectRatio,
      baseUrl,
    }
  }
  
  exports.resolveResize = resolveResize
  
  const fixedNodeType = ({ name, getTracedSVG }) => {
    return {
      type: new GraphQLObjectType({
        name: name,
        fields: {
          base64: {
            type: GraphQLString,
            resolve(imageProps) {
              return getBase64Image(imageProps)
            },
          },
          tracedSVG: {
            type: GraphQLString,
            resolve: getTracedSVG,
          },
          aspectRatio: { type: GraphQLFloat },
          width: { type: GraphQLFloat },
          height: { type: GraphQLFloat },
          src: { type: GraphQLString },
          srcSet: { type: GraphQLString },
          srcWebp: {
            type: GraphQLString,
            resolve({ image, options, context }) {
              if (
                _.get(image, `file.contentType`) === `image/webp` ||
                options.toFormat === `webp`
              ) {
                return null
              }
  
              const fixed = resolveFixed(image, {
                ...options,
                toFormat: `webp`,
              })
              return _.get(fixed, `src`)
            },
          },
          srcSetWebp: {
            type: GraphQLString,
            resolve({ image, options, context }) {
              if (
                _.get(image, `file.contentType`) === `image/webp` ||
                options.toFormat === `webp`
              ) {
                return null
              }
  
              const fixed = resolveFixed(image, {
                ...options,
                toFormat: `webp`,
              })
              return _.get(fixed, `srcSet`)
            },
          },
        },
      }),
      args: {
        width: {
          type: GraphQLInt,
        },
        height: {
          type: GraphQLInt,
        },
        quality: {
          type: GraphQLInt,
          defaultValue: 50,
        },
        toFormat: {
          type: ImageFormatType,
          defaultValue: ``,
        },
        resizingBehavior: {
          type: ImageResizingBehavior,
        },
        cropFocus: {
          type: ImageCropFocusType,
          defaultValue: null,
        },
        background: {
          type: GraphQLString,
          defaultValue: null,
        },
      },
      resolve: (image, options, context) =>
        Promise.resolve(resolveFixed(image, options)).then(node => {
          return {
            ...node,
            image,
            options,
            context,
          }
        }),
    }
  }
  
  const fluidNodeType = ({ name, getTracedSVG }) => {
    return {
      type: new GraphQLObjectType({
        name: name,
        fields: {
          base64: {
            type: GraphQLString,
            resolve(imageProps) {
              return getBase64Image(imageProps)
            },
          },
          tracedSVG: {
            type: GraphQLString,
            resolve: getTracedSVG,
          },
          aspectRatio: { type: GraphQLFloat },
          src: { type: GraphQLString },
          srcSet: { type: GraphQLString },
          srcWebp: {
            type: GraphQLString,
            resolve({ image, options, context }) {
              if (
                _.get(image, `file.contentType`) === `image/webp` ||
                options.toFormat === `webp`
              ) {
                return null
              }
  
              const fluid = resolveFluid(image, {
                ...options,
                toFormat: `webp`,
              })
              return _.get(fluid, `src`)
            },
          },
          srcSetWebp: {
            type: GraphQLString,
            resolve({ image, options, context }) {
              if (
                _.get(image, `file.contentType`) === `image/webp` ||
                options.toFormat === `webp`
              ) {
                return null
              }
  
              const fluid = resolveFluid(image, {
                ...options,
                toFormat: `webp`,
              })
              return _.get(fluid, `srcSet`)
            },
          },
          sizes: { type: GraphQLString },
        },
      }),
      args: {
        maxWidth: {
          type: GraphQLInt,
        },
        maxHeight: {
          type: GraphQLInt,
        },
        quality: {
          type: GraphQLInt,
          defaultValue: 50,
        },
        toFormat: {
          type: ImageFormatType,
          defaultValue: ``,
        },
        resizingBehavior: {
          type: ImageResizingBehavior,
        },
        cropFocus: {
          type: ImageCropFocusType,
          defaultValue: null,
        },
        background: {
          type: GraphQLString,
          defaultValue: null,
        },
        sizes: {
          type: GraphQLString,
        },
      },
      resolve: (image, options, context) =>
        Promise.resolve(resolveFluid(image, options)).then(node => {
          return {
            ...node,
            image,
            options,
            context,
          }
        }),
    }
  }
  
  exports.extendNodeType = ({ type, store }) => {
    if (type.name.match(/contentful.*RichTextNode/)) {
      return {
        nodeType: {
          type: GraphQLString,
          deprecationReason: `This field is deprecated, please use 'json' instead.`,
        },
        json: {
          type: GraphQLJSON,
          resolve: (source, fieldArgs) => {
            const contentJSON = JSON.parse(source.internal.content)
            return contentJSON
          },
        },
      }
    }
  
    if (type.name !== `ContentfulAsset`) {
      return {}
    }
  
    const getTracedSVG = async args => {
      const { traceSVG } = require(`gatsby-plugin-sharp`)
  
      const { image, options } = args
      const {
        file: { contentType },
      } = image
  
      if (contentType.indexOf(`image/`) !== 0) {
        return null
      }
  
      const absolutePath = await cacheImage(store, image, options)
      const extension = path.extname(absolutePath)
  
      return traceSVG({
        file: {
          internal: image.internal,
          name: image.file.fileName,
          extension,
          absolutePath,
        },
        args: { toFormat: `` },
        fileArgs: options,
      })
    }
  
    // TODO: Remove resolutionsNode and sizesNode for Gatsby v3
    const fixedNode = fixedNodeType({ name: `ContentfulFixed`, getTracedSVG })
    const resolutionsNode = fixedNodeType({
      name: `ContentfulResolutions`,
      getTracedSVG,
    })
    resolutionsNode.deprecationReason = `Resolutions was deprecated in Gatsby v2. It's been renamed to "fixed" https://example.com/write-docs-and-fix-this-example-link`
  
    const fluidNode = fluidNodeType({ name: `ContentfulFluid`, getTracedSVG })
    const sizesNode = fluidNodeType({ name: `ContentfulSizes`, getTracedSVG })
    sizesNode.deprecationReason = `Sizes was deprecated in Gatsby v2. It's been renamed to "fluid" https://example.com/write-docs-and-fix-this-example-link`
  
    return {
      fixed: fixedNode,
      resolutions: resolutionsNode,
      fluid: fluidNode,
      sizes: sizesNode,
      resize: {
        type: new GraphQLObjectType({
          name: `ContentfulResize`,
          fields: {
            base64: {
              type: GraphQLString,
              resolve(imageProps) {
                return getBase64Image(imageProps)
              },
            },
            tracedSVG: {
              type: GraphQLString,
              resolve: getTracedSVG,
            },
            src: { type: GraphQLString },
            width: { type: GraphQLInt },
            height: { type: GraphQLInt },
            aspectRatio: { type: GraphQLFloat },
          },
        }),
        args: {
          width: {
            type: GraphQLInt,
          },
          height: {
            type: GraphQLInt,
          },
          quality: {
            type: GraphQLInt,
            defaultValue: 50,
          },
          jpegProgressive: {
            type: GraphQLBoolean,
            defaultValue: true,
          },
          resizingBehavior: {
            type: ImageResizingBehavior,
          },
          toFormat: {
            type: ImageFormatType,
            defaultValue: ``,
          },
          cropFocus: {
            type: ImageCropFocusType,
            defaultValue: null,
          },
          background: {
            type: GraphQLString,
            defaultValue: null,
          },
        },
        resolve(image, options, context) {
          return resolveResize(image, options)
        },
      },
    }
  }
  