function _arrayLikeToArray(r, a) {
  (null == a || a > r.length) && (a = r.length);
  for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
  return n;
}
function _createForOfIteratorHelperLoose(r, e) {
  var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
  if (t) return (t = t.call(r)).next.bind(t);
  if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) {
    t && (r = t);
    var o = 0;
    return function () {
      return o >= r.length ? {
        done: !0
      } : {
        done: !1,
        value: r[o++]
      };
    };
  }
  throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _extends() {
  return _extends = Object.assign ? Object.assign.bind() : function (n) {
    for (var e = 1; e < arguments.length; e++) {
      var t = arguments[e];
      for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
    }
    return n;
  }, _extends.apply(null, arguments);
}
function _unsupportedIterableToArray(r, a) {
  if (r) {
    if ("string" == typeof r) return _arrayLikeToArray(r, a);
    var t = {}.toString.call(r).slice(8, -1);
    return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
  }
}

var example = 'image-Tb9Ew8CXIwaY6R1kjMvI0uRR-2000x3000-jpg';
function parseAssetId(ref) {
  var _ref$split = ref.split('-'),
    id = _ref$split[1],
    dimensionString = _ref$split[2],
    format = _ref$split[3];
  if (!id || !dimensionString || !format) {
    throw new Error("Malformed asset _ref '" + ref + "'. Expected an id like \"" + example + "\".");
  }
  var _dimensionString$spli = dimensionString.split('x'),
    imgWidthStr = _dimensionString$spli[0],
    imgHeightStr = _dimensionString$spli[1];
  var width = +imgWidthStr;
  var height = +imgHeightStr;
  var isValidAssetId = isFinite(width) && isFinite(height);
  if (!isValidAssetId) {
    throw new Error("Malformed asset _ref '" + ref + "'. Expected an id like \"" + example + "\".");
  }
  return {
    id: id,
    width: width,
    height: height,
    format: format
  };
}

var isRef = function isRef(src) {
  var source = src;
  return source ? typeof source._ref === 'string' : false;
};
var isAsset = function isAsset(src) {
  var source = src;
  return source ? typeof source._id === 'string' : false;
};
var isAssetStub = function isAssetStub(src) {
  var source = src;
  return source && source.asset ? typeof source.asset.url === 'string' : false;
};
// Detect in-progress uploads (has upload key but no complete asset reference)
var isInProgressUpload = function isInProgressUpload(src) {
  if (typeof src === 'object' && src !== null) {
    var obj = src;
    // Check if it has an upload key (indicating in-progress upload)
    return obj._upload && (!obj.asset || !obj.asset._ref);
  }
  return false;
};
// Convert an asset-id, asset or image to an image record suitable for processing
// eslint-disable-next-line complexity
function parseSource(source) {
  if (!source) {
    return null;
  }
  var image;
  if (typeof source === 'string' && isUrl(source)) {
    // Someone passed an existing image url?
    image = {
      asset: {
        _ref: urlToId(source)
      }
    };
  } else if (typeof source === 'string') {
    // Just an asset id
    image = {
      asset: {
        _ref: source
      }
    };
  } else if (isRef(source)) {
    // We just got passed an asset directly
    image = {
      asset: source
    };
  } else if (isAsset(source)) {
    // If we were passed an image asset document
    image = {
      asset: {
        _ref: source._id || ''
      }
    };
  } else if (isAssetStub(source)) {
    // If we were passed a partial asset (`url`, but no `_id`)
    image = {
      asset: {
        _ref: urlToId(source.asset.url)
      }
    };
  } else if (typeof source.asset === 'object') {
    // Probably an actual image with materialized asset
    image = _extends({}, source);
  } else {
    // We got something that does not look like an image, or it is an image
    // that currently isn't sporting an asset.
    return null;
  }
  var img = source;
  if (img.crop) {
    image.crop = img.crop;
  }
  if (img.hotspot) {
    image.hotspot = img.hotspot;
  }
  return applyDefaults(image);
}
function isUrl(url) {
  return /^https?:\/\//.test("" + url);
}
function urlToId(url) {
  var parts = url.split('/').slice(-1);
  return ("image-" + parts[0]).replace(/\.([a-z]+)$/, '-$1');
}
// Mock crop and hotspot if image lacks it
function applyDefaults(image) {
  if (image.crop && image.hotspot) {
    return image;
  }
  // We need to pad in default values for crop or hotspot
  var result = _extends({}, image);
  if (!result.crop) {
    result.crop = {
      left: 0,
      top: 0,
      bottom: 0,
      right: 0
    };
  }
  if (!result.hotspot) {
    result.hotspot = {
      x: 0.5,
      y: 0.5,
      height: 1.0,
      width: 1.0
    };
  }
  return result;
}

var SPEC_NAME_TO_URL_NAME_MAPPINGS = [['width', 'w'], ['height', 'h'], ['format', 'fm'], ['download', 'dl'], ['blur', 'blur'], ['sharpen', 'sharp'], ['invert', 'invert'], ['orientation', 'or'], ['minHeight', 'min-h'], ['maxHeight', 'max-h'], ['minWidth', 'min-w'], ['maxWidth', 'max-w'], ['quality', 'q'], ['fit', 'fit'], ['crop', 'crop'], ['saturation', 'sat'], ['auto', 'auto'], ['dpr', 'dpr'], ['pad', 'pad'], ['frame', 'frame']];
function urlForImage(options) {
  var spec = _extends({}, options || {});
  var source = spec.source;
  delete spec.source;
  var image = parseSource(source);
  if (!image) {
    if (source && isInProgressUpload(source)) {
      // This is a placeholder image that will be replaced with the actual image when the upload is complete
      // This is a 0x0 transparent PNG image
      return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8HwQACfsD/QNViZkAAAAASUVORK5CYII=';
    }
    throw new Error("Unable to resolve image URL from source (" + JSON.stringify(source) + ")");
  }
  var id = image.asset._ref || image.asset._id || '';
  var asset = parseAssetId(id);
  // Compute crop rect in terms of pixel coordinates in the raw source image
  var cropLeft = Math.round(image.crop.left * asset.width);
  var cropTop = Math.round(image.crop.top * asset.height);
  var crop = {
    left: cropLeft,
    top: cropTop,
    width: Math.round(asset.width - image.crop.right * asset.width - cropLeft),
    height: Math.round(asset.height - image.crop.bottom * asset.height - cropTop)
  };
  // Compute hot spot rect in terms of pixel coordinates
  var hotSpotVerticalRadius = image.hotspot.height * asset.height / 2;
  var hotSpotHorizontalRadius = image.hotspot.width * asset.width / 2;
  var hotSpotCenterX = image.hotspot.x * asset.width;
  var hotSpotCenterY = image.hotspot.y * asset.height;
  var hotspot = {
    left: hotSpotCenterX - hotSpotHorizontalRadius,
    top: hotSpotCenterY - hotSpotVerticalRadius,
    right: hotSpotCenterX + hotSpotHorizontalRadius,
    bottom: hotSpotCenterY + hotSpotVerticalRadius
  };
  // If irrelevant, or if we are requested to: don't perform crop/fit based on
  // the crop/hotspot.
  if (!(spec.rect || spec.focalPoint || spec.ignoreImageParams || spec.crop)) {
    spec = _extends({}, spec, fit({
      crop: crop,
      hotspot: hotspot
    }, spec));
  }
  return specToImageUrl(_extends({}, spec, {
    asset: asset
  }));
}
// eslint-disable-next-line complexity
function specToImageUrl(spec) {
  var cdnUrl = (spec.baseUrl || 'https://cdn.sanity.io').replace(/\/+$/, '');
  var vanityStub = spec.vanityName ? "/" + spec.vanityName : '';
  var filename = spec.asset.id + "-" + spec.asset.width + "x" + spec.asset.height + "." + spec.asset.format + vanityStub;
  var baseUrl = cdnUrl + "/images/" + spec.projectId + "/" + spec.dataset + "/" + filename;
  var params = [];
  if (spec.rect) {
    // Only bother url with a crop if it actually crops anything
    var _spec$rect = spec.rect,
      left = _spec$rect.left,
      top = _spec$rect.top,
      width = _spec$rect.width,
      height = _spec$rect.height;
    var isEffectiveCrop = left !== 0 || top !== 0 || height !== spec.asset.height || width !== spec.asset.width;
    if (isEffectiveCrop) {
      params.push("rect=" + left + "," + top + "," + width + "," + height);
    }
  }
  if (spec.bg) {
    params.push("bg=" + spec.bg);
  }
  if (spec.focalPoint) {
    params.push("fp-x=" + spec.focalPoint.x);
    params.push("fp-y=" + spec.focalPoint.y);
  }
  var flip = [spec.flipHorizontal && 'h', spec.flipVertical && 'v'].filter(Boolean).join('');
  if (flip) {
    params.push("flip=" + flip);
  }
  // Map from spec name to url param name, and allow using the actual param name as an alternative
  SPEC_NAME_TO_URL_NAME_MAPPINGS.forEach(function (mapping) {
    var specName = mapping[0],
      param = mapping[1];
    if (typeof spec[specName] !== 'undefined') {
      params.push(param + "=" + encodeURIComponent(spec[specName]));
    } else if (typeof spec[param] !== 'undefined') {
      params.push(param + "=" + encodeURIComponent(spec[param]));
    }
  });
  if (params.length === 0) {
    return baseUrl;
  }
  return baseUrl + "?" + params.join('&');
}
function fit(source, spec) {
  var cropRect;
  var imgWidth = spec.width;
  var imgHeight = spec.height;
  // If we are not constraining the aspect ratio, we'll just use the whole crop
  if (!(imgWidth && imgHeight)) {
    return {
      width: imgWidth,
      height: imgHeight,
      rect: source.crop
    };
  }
  var crop = source.crop;
  var hotspot = source.hotspot;
  // If we are here, that means aspect ratio is locked and fitting will be a bit harder
  var desiredAspectRatio = imgWidth / imgHeight;
  var cropAspectRatio = crop.width / crop.height;
  if (cropAspectRatio > desiredAspectRatio) {
    // The crop is wider than the desired aspect ratio. That means we are cutting from the sides
    var height = Math.round(crop.height);
    var width = Math.round(height * desiredAspectRatio);
    var top = Math.max(0, Math.round(crop.top));
    // Center output horizontally over hotspot
    var hotspotXCenter = Math.round((hotspot.right - hotspot.left) / 2 + hotspot.left);
    var left = Math.max(0, Math.round(hotspotXCenter - width / 2));
    // Keep output within crop
    if (left < crop.left) {
      left = crop.left;
    } else if (left + width > crop.left + crop.width) {
      left = crop.left + crop.width - width;
    }
    cropRect = {
      left: left,
      top: top,
      width: width,
      height: height
    };
  } else {
    // The crop is taller than the desired ratio, we are cutting from top and bottom
    var _width = crop.width;
    var _height = Math.round(_width / desiredAspectRatio);
    var _left = Math.max(0, Math.round(crop.left));
    // Center output vertically over hotspot
    var hotspotYCenter = Math.round((hotspot.bottom - hotspot.top) / 2 + hotspot.top);
    var _top = Math.max(0, Math.round(hotspotYCenter - _height / 2));
    // Keep output rect within crop
    if (_top < crop.top) {
      _top = crop.top;
    } else if (_top + _height > crop.top + crop.height) {
      _top = crop.top + crop.height - _height;
    }
    cropRect = {
      left: _left,
      top: _top,
      width: _width,
      height: _height
    };
  }
  return {
    width: imgWidth,
    height: imgHeight,
    rect: cropRect
  };
}

var validFits = ['clip', 'crop', 'fill', 'fillmax', 'max', 'scale', 'min'];
var validCrops = ['top', 'bottom', 'left', 'right', 'center', 'focalpoint', 'entropy'];
var validAutoModes = ['format'];
function isSanityModernClientLike(client) {
  return client && 'config' in client ? typeof client.config === 'function' : false;
}
function isSanityClientLike(client) {
  return client && 'clientConfig' in client ? typeof client.clientConfig === 'object' : false;
}
function rewriteSpecName(key) {
  var specs = SPEC_NAME_TO_URL_NAME_MAPPINGS;
  for (var _iterator = _createForOfIteratorHelperLoose(specs), _step; !(_step = _iterator()).done;) {
    var entry = _step.value;
    var specName = entry[0],
      param = entry[1];
    if (key === specName || key === param) {
      return specName;
    }
  }
  return key;
}
function urlBuilder(options) {
  // Did we get a modernish client?
  if (isSanityModernClientLike(options)) {
    // Inherit config from client
    var _options$config = options.config(),
      apiUrl = _options$config.apiHost,
      projectId = _options$config.projectId,
      dataset = _options$config.dataset;
    var apiHost = apiUrl || 'https://api.sanity.io';
    return new ImageUrlBuilder(null, {
      baseUrl: apiHost.replace(/^https:\/\/api\./, 'https://cdn.'),
      projectId: projectId,
      dataset: dataset
    });
  }
  // Did we get a SanityClient?
  if (isSanityClientLike(options)) {
    // Inherit config from client
    var _options$clientConfig = options.clientConfig,
      _apiUrl = _options$clientConfig.apiHost,
      _projectId = _options$clientConfig.projectId,
      _dataset = _options$clientConfig.dataset;
    var _apiHost = _apiUrl || 'https://api.sanity.io';
    return new ImageUrlBuilder(null, {
      baseUrl: _apiHost.replace(/^https:\/\/api\./, 'https://cdn.'),
      projectId: _projectId,
      dataset: _dataset
    });
  }
  // Or just accept the options as given
  return new ImageUrlBuilder(null, options || {});
}
var ImageUrlBuilder = /*#__PURE__*/function () {
  function ImageUrlBuilder(parent, options) {
    this.options = void 0;
    this.options = parent ? _extends({}, parent.options || {}, options || {}) // Merge parent options
    : _extends({}, options || {}); // Copy options
  }
  var _proto = ImageUrlBuilder.prototype;
  _proto.withOptions = function withOptions(options) {
    var baseUrl = options.baseUrl || this.options.baseUrl;
    var newOptions = {
      baseUrl: baseUrl
    };
    for (var key in options) {
      if (options.hasOwnProperty(key)) {
        var specKey = rewriteSpecName(key);
        newOptions[specKey] = options[key];
      }
    }
    return new ImageUrlBuilder(this, _extends({
      baseUrl: baseUrl
    }, newOptions));
  }
  // The image to be represented. Accepts a Sanity 'image'-document, 'asset'-document or
  // _id of asset. To get the benefit of automatic hot-spot/crop integration with the content
  // studio, the 'image'-document must be provided.
  ;
  _proto.image = function image(source) {
    return this.withOptions({
      source: source
    });
  }
  // Specify the dataset
  ;
  _proto.dataset = function dataset(_dataset2) {
    return this.withOptions({
      dataset: _dataset2
    });
  }
  // Specify the projectId
  ;
  _proto.projectId = function projectId(_projectId2) {
    return this.withOptions({
      projectId: _projectId2
    });
  }
  // Specify background color
  ;
  _proto.bg = function bg(_bg) {
    return this.withOptions({
      bg: _bg
    });
  }
  // Set DPR scaling factor
  ;
  _proto.dpr = function dpr(_dpr) {
    // A DPR of 1 is the default - so only include it if we have a different value
    return this.withOptions(_dpr && _dpr !== 1 ? {
      dpr: _dpr
    } : {});
  }
  // Specify the width of the image in pixels
  ;
  _proto.width = function width(_width) {
    return this.withOptions({
      width: _width
    });
  }
  // Specify the height of the image in pixels
  ;
  _proto.height = function height(_height) {
    return this.withOptions({
      height: _height
    });
  }
  // Specify focal point in fraction of image dimensions. Each component 0.0-1.0
  ;
  _proto.focalPoint = function focalPoint(x, y) {
    return this.withOptions({
      focalPoint: {
        x: x,
        y: y
      }
    });
  };
  _proto.maxWidth = function maxWidth(_maxWidth) {
    return this.withOptions({
      maxWidth: _maxWidth
    });
  };
  _proto.minWidth = function minWidth(_minWidth) {
    return this.withOptions({
      minWidth: _minWidth
    });
  };
  _proto.maxHeight = function maxHeight(_maxHeight) {
    return this.withOptions({
      maxHeight: _maxHeight
    });
  };
  _proto.minHeight = function minHeight(_minHeight) {
    return this.withOptions({
      minHeight: _minHeight
    });
  }
  // Specify width and height in pixels
  ;
  _proto.size = function size(width, height) {
    return this.withOptions({
      width: width,
      height: height
    });
  }
  // Specify blur between 0 and 100
  ;
  _proto.blur = function blur(_blur) {
    return this.withOptions({
      blur: _blur
    });
  };
  _proto.sharpen = function sharpen(_sharpen) {
    return this.withOptions({
      sharpen: _sharpen
    });
  }
  // Specify the desired rectangle of the image
  ;
  _proto.rect = function rect(left, top, width, height) {
    return this.withOptions({
      rect: {
        left: left,
        top: top,
        width: width,
        height: height
      }
    });
  }
  // Specify the image format of the image. 'jpg', 'pjpg', 'png', 'webp'
  ;
  _proto.format = function format(_format) {
    return this.withOptions({
      format: _format
    });
  };
  _proto.invert = function invert(_invert) {
    return this.withOptions({
      invert: _invert
    });
  }
  // Rotation in degrees 0, 90, 180, 270
  ;
  _proto.orientation = function orientation(_orientation) {
    return this.withOptions({
      orientation: _orientation
    });
  }
  // Compression quality 0-100
  ;
  _proto.quality = function quality(_quality) {
    return this.withOptions({
      quality: _quality
    });
  }
  // Make it a download link. Parameter is default filename.
  ;
  _proto.forceDownload = function forceDownload(download) {
    return this.withOptions({
      download: download
    });
  }
  // Flip image horizontally
  ;
  _proto.flipHorizontal = function flipHorizontal() {
    return this.withOptions({
      flipHorizontal: true
    });
  }
  // Flip image vertically
  ;
  _proto.flipVertical = function flipVertical() {
    return this.withOptions({
      flipVertical: true
    });
  }
  // Ignore crop/hotspot from image record, even when present
  ;
  _proto.ignoreImageParams = function ignoreImageParams() {
    return this.withOptions({
      ignoreImageParams: true
    });
  };
  _proto.fit = function fit(value) {
    if (validFits.indexOf(value) === -1) {
      throw new Error("Invalid fit mode \"" + value + "\"");
    }
    return this.withOptions({
      fit: value
    });
  };
  _proto.crop = function crop(value) {
    if (validCrops.indexOf(value) === -1) {
      throw new Error("Invalid crop mode \"" + value + "\"");
    }
    return this.withOptions({
      crop: value
    });
  }
  // Saturation
  ;
  _proto.saturation = function saturation(_saturation) {
    return this.withOptions({
      saturation: _saturation
    });
  };
  _proto.auto = function auto(value) {
    if (validAutoModes.indexOf(value) === -1) {
      throw new Error("Invalid auto mode \"" + value + "\"");
    }
    return this.withOptions({
      auto: value
    });
  }
  // Specify the number of pixels to pad the image
  ;
  _proto.pad = function pad(_pad) {
    return this.withOptions({
      pad: _pad
    });
  }
  // Vanity URL for more SEO friendly URLs
  ;
  _proto.vanityName = function vanityName(value) {
    return this.withOptions({
      vanityName: value
    });
  };
  _proto.frame = function frame(_frame) {
    if (_frame !== 1) {
      throw new Error("Invalid frame value \"" + _frame + "\"");
    }
    return this.withOptions({
      frame: _frame
    });
  }
  // Gets the url based on the submitted parameters
  ;
  _proto.url = function url() {
    return urlForImage(this.options);
  }
  // Alias for url()
  ;
  _proto.toString = function toString() {
    return this.url();
  };
  return ImageUrlBuilder;
}();

export { urlBuilder as default };
//# sourceMappingURL=image-url.esm.mjs.map
