text: /**
   * @module ol/layer/MapboxVector
   */
  import BaseEvent from '../events/Event.js';
  import EventType from '../events/EventType.js';
  import MVT from '../format/MVT.js';
  import SourceState from '../source/State.js';
  import VectorTileLayer from '../layer/VectorTile.js';
  import VectorTileSource from '../source/VectorTile.js';
  import {applyStyle} from 'ol-mapbox-style';
  
  const mapboxBaseUrl = 'https://api.mapbox.com';
  
  /**
   * Gets the path from a mapbox:// URL.
   * @param {string} url The Mapbox URL.
   * @return {string} The path.
   * @private
   */
  export function getMapboxPath(url) {
    const startsWith = 'mapbox://';
    if (url.indexOf(startsWith) !== 0) {
      return '';
    }
    return url.slice(startsWith.length);
  }
  
  /**
   * Turns mapbox:// sprite URLs into resolvable URLs.
   * @param {string} url The sprite URL.
   * @param {string} token The access token.
   * @return {string} A resolvable URL.
   * @private
   */
  export function normalizeSpriteUrl(url, token) {
    const mapboxPath = getMapboxPath(url);
    if (!mapboxPath) {
      return url;
    }
    const startsWith = 'sprites/';
    if (mapboxPath.indexOf(startsWith) !== 0) {
      throw new Error(`unexpected sprites url: ${url}`);
    }
    const sprite = mapboxPath.slice(startsWith.length);
  
    return `${mapboxBaseUrl}/styles/v1/${sprite}/sprite?access_token=${token}`;
  }
  
  /**
   * Turns mapbox:// glyphs URLs into resolvable URLs.
   * @param {string} url The glyphs URL.
   * @param {string} token The access token.
   * @return {string} A resolvable URL.
   * @private
   */
  export function normalizeGlyphsUrl(url, token) {
    const mapboxPath = getMapboxPath(url);
    if (!mapboxPath) {
      return url;
    }
    const startsWith = 'fonts/';
    if (mapboxPath.indexOf(startsWith) !== 0) {
      throw new Error(`unexpected fonts url: ${url}`);
    }
    const font = mapboxPath.slice(startsWith.length);
  
    return `${mapboxBaseUrl}/fonts/v1/${font}/0-255.pbf?access_token=${token}`;
  }
  
  /**
   * Turns mapbox:// style URLs into resolvable URLs.
   * @param {string} url The style URL.
   * @param {string} token The access token.
   * @return {string} A resolvable URL.
   * @private
   */
  export function normalizeStyleUrl(url, token) {
    const mapboxPath = getMapboxPath(url);
    if (!mapboxPath) {
      return url;
    }
    const startsWith = 'styles/';
    if (mapboxPath.indexOf(startsWith) !== 0) {
      throw new Error(`unexpected style url: ${url}`);
    }
    const style = mapboxPath.slice(startsWith.length);
  
    return `${mapboxBaseUrl}/styles/v1/${style}?&access_token=${token}`;
  }
  
  /**
   * Turns mapbox:// source URLs into vector tile URL templates.
   * @param {string} url The source URL.
   * @param {string} token The access token.
   * @return {string} A vector tile template.
   * @private
   */
  export function normalizeSourceUrl(url, token) {
    const mapboxPath = getMapboxPath(url);
    if (!mapboxPath) {
      return url;
    }
    return `https://{a-d}.tiles.mapbox.com/v4/${mapboxPath}/{z}/{x}/{y}.vector.pbf?access_token=${token}`;
  }
  
  /**
   * @classdesc
   * Event emitted on configuration or loading error.
   */
  class ErrorEvent extends BaseEvent {
    /**
     * @param {Error} error error object.
     */
    constructor(error) {
      super(EventType.ERROR);
  
      /**
       * @type {Error}
       */
      this.error = error;
    }
  }
  
  /**
   * @typedef {Object} StyleObject
   * @property {Object<string, SourceObject>} sources The style sources.
   * @property {string} sprite The sprite URL.
   * @property {string} glyphs The glyphs URL.
   * @property {Array<LayerObject>} layers The style layers.
   */
  
  /**
   * @typedef {Object} SourceObject
   * @property {string} url The source URL.
   * @property {SourceType} type The source type.
   */
  
  /**
   * The Mapbox source type.
   * @enum {string}
   */
  const SourceType = {
    VECTOR: 'vector',
  };
  
  /**
   * @typedef {Object} LayerObject
   * @property {string} id The layer id.
   * @property {string} source The source id.
   */
  
  /**
   * @typedef {Object} Options
   * @property {string} styleUrl The URL of the Mapbox style object to use for this layer.  For a
   * style created with Mapbox Studio and hosted on Mapbox, this will look like
   * 'mapbox://styles/you/your-style'.
   * @property {string} accessToken The access token for your Mapbox style.
   * @property {string} [source] If your style uses more than one source, you need to use either the
   * `source` property or the `layers` property to limit rendering to a single vector source.  The
   * `source` property corresponds to the id of a vector source in your Mapbox style.
   * @property {Array<string>} [layers] Limit rendering to the list of included layers.  All layers
   * must share the same vector source.  If your style uses more than one source, you need to use
   * either the `source` property or the `layers` property to limit rendering to a single vector
   * source.
   * @property {boolean} [declutter=true] Declutter images and text. Decluttering is applied to all
   * image and text styles of all Vector and VectorTile layers that have set this to `true`. The priority
   * is defined by the z-index of the layer, the `zIndex` of the style and the render order of features.
   * Higher z-index means higher priority. Within the same z-index, a feature rendered before another has
   * higher priority.
   * @property {string} [className='ol-layer'] A CSS class name to set to the layer element.
   * @property {number} [opacity=1] Opacity (0, 1).
   * @property {boolean} [visible=true] Visibility.
   * @property {import("../extent.js").Extent} [extent] The bounding extent for layer rendering.  The layer will not be
   * rendered outside of this extent.
   * @property {number} [zIndex] The z-index for layer rendering.  At rendering time, the layers
   * will be ordered, first by Z-index and then by position. When `undefined`, a `zIndex` of 0 is assumed
   * for layers that are added to the map's `layers` collection, or `Infinity` when the layer's `setMap()`
   * method was used.
   * @property {number} [minResolution] The minimum resolution (inclusive) at which this layer will be
   * visible.
   * @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
   * be visible.
   * @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be
   * visible.
   * @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will
   * be visible.
   * @property {import("../render.js").OrderFunction} [renderOrder] Render order. Function to be used when sorting
   * features before rendering. By default features are drawn in the order that they are created. Use
   * `null` to avoid the sort, but get an undefined draw order.
   * @property {number} [renderBuffer=100] The buffer in pixels around the tile extent used by the
   * renderer when getting features from the vector tile for the rendering or hit-detection.
   * Recommended value: Vector tiles are usually generated with a buffer, so this value should match
   * the largest possible buffer of the used tiles. It should be at least the size of the largest
   * point symbol or line width.
   * @property {import("./VectorTileRenderType.js").default|string} [renderMode='hybrid'] Render mode for vector tiles:
   *  * `'hybrid'`: Polygon and line elements are rendered as images, so pixels are scaled during zoom
   *    animations. Point symbols and texts are accurately rendered as vectors and can stay upright on
   *    rotated views.
   *  * `'vector'`: Everything is rendered as vectors. Use this mode for improved performance on vector
   *    tile layers with only a few rendered features (e.g. for highlighting a subset of features of
   *    another layer with the same source).
   * @property {import("../PluggableMap.js").default} [map] Sets the layer as overlay on a map. The map will not manage
   * this layer in its layers collection, and the layer will be rendered on top. This is useful for
   * temporary layers. The standard way to add a layer to a map and have it managed by the map is to
   * use {@link module:ol/Map#addLayer}.
   * @property {boolean} [updateWhileAnimating=false] When set to `true`, feature batches will be
   * recreated during animations. This means that no vectors will be shown clipped, but the setting
   * will have a performance impact for large amounts of vector data. When set to `false`, batches
   * will be recreated when no animation is active.
   * @property {boolean} [updateWhileInteracting=false] When set to `true`, feature batches will be
   * recreated during interactions. See also `updateWhileAnimating`.
   * @property {number} [preload=0] Preload. Load low-resolution tiles up to `preload` levels. `0`
   * means no preloading.
   * @property {boolean} [useInterimTilesOnError=true] Use interim tiles on error.
   */
  
  /**
   * @classdesc
   * A vector tile layer based on a Mapbox style that uses a single vector source.  Configure
   * the layer with the `styleUrl` and `accessToken` shown in Mapbox Studio's share panel.
   * If the style uses more than one source, use the `source` property to choose a single
   * vector source.  If you want to render a subset of the layers in the style, use the `layers`
   * property (all layers must share the same vector source).  See the constructor options for
   * more detail.
   *
   *     var map = new Map({
   *       view: new View({
   *         center: [0, 0],
   *         zoom: 1
   *       }),
   *       layers: [
   *         new MapboxVector({
   *           styleUrl: 'mapbox://styles/mapbox/bright-v9',
   *           accessToken: 'your-mapbox-access-token-here'
   *         })
   *       ],
   *       target: 'map'
   *     });
   *
   * On configuration or loading error, the layer will trigger an `'error'` event.  Listeners
   * will receive an object with an `error` property that can be used to diagnose the problem.
   *
   * @param {Options} options Options.
   * @extends {VectorTileLayer}
   * @fires module:ol/events/Event~BaseEvent#event:error
   * @api
   */
  class MapboxVectorLayer extends VectorTileLayer {
    /**
     * @param {Options} options Layer options.  At a minimum, `styleUrl` and `accessToken`
     * must be provided.
     */
    constructor(options) {
      const declutter = 'declutter' in options ? options.declutter : true;
      const source = new VectorTileSource({
        state: SourceState.LOADING,
        format: new MVT(),
      });
  
      super({
        source: source,
        declutter: declutter,
        className: options.className,
        opacity: options.opacity,
        visible: options.visible,
        zIndex: options.zIndex,
        minResolution: options.minResolution,
        maxResolution: options.maxResolution,
        minZoom: options.minZoom,
        maxZoom: options.maxZoom,
        renderOrder: options.renderOrder,
        renderBuffer: options.renderBuffer,
        renderMode: options.renderMode,
        map: options.map,
        updateWhileAnimating: options.updateWhileAnimating,
        updateWhileInteracting: options.updateWhileInteracting,
        preload: options.preload,
        useInterimTilesOnError: options.useInterimTilesOnError,
      });
  
      this.sourceId = options.source;
      this.layers = options.layers;
      this.accessToken = options.accessToken;
      this.fetchStyle(options.styleUrl);
    }
  
    /**
     * Fetch the style object.
     * @param {string} styleUrl The URL of the style to load.
     * @protected
     */
    fetchStyle(styleUrl) {
      const url = normalizeStyleUrl(styleUrl, this.accessToken);
      fetch(url)
        .then((response) => {
          if (!response.ok) {
            throw new Error(
              `unexpected response when fetching style: ${response.status}`
            );
          }
          return response.json();
        })
        .then((style) => {
          this.onStyleLoad(style);
        })
        .catch((error) => {
          this.handleError(error);
        });
    }
  
    /**
     * Handle the loaded style object.
     * @param {StyleObject} style The loaded style.
     * @protected
     */
    onStyleLoad(style) {
      let sourceId;
      let sourceIdOrLayersList;
      if (this.layers) {
        // confirm all layers share the same source
        const lookup = {};
        for (let i = 0; i < style.layers.length; ++i) {
          const layer = style.layers[i];
          if (layer.source) {
            lookup[layer.id] = layer.source;
          }
        }
        let firstSource;
        for (let i = 0; i < this.layers.length; ++i) {
          const candidate = lookup[this.layers[i]];
          if (!candidate) {
            this.handleError(
              new Error(`could not find source for ${this.layers[i]}`)
            );
            return;
          }
          if (!firstSource) {
            firstSource = candidate;
          } else if (firstSource !== candidate) {
            this.handleError(
              new Error(
                `layers can only use a single source, found ${firstSource} and ${candidate}`
              )
            );
            return;
          }
        }
        sourceId = firstSource;
        sourceIdOrLayersList = this.layers;
      } else {
        sourceId = this.sourceId;
        sourceIdOrLayersList = sourceId;
      }
  
      if (!sourceIdOrLayersList) {
        // default to the first source in the style
        sourceId = Object.keys(style.sources)[0];
        sourceIdOrLayersList = sourceId;
      }
  
      if (style.sprite) {
        style.sprite = normalizeSpriteUrl(style.sprite, this.accessToken);
      }
  
      if (style.glyphs) {
        style.glyphs = normalizeGlyphsUrl(style.glyphs, this.accessToken);
      }
  
      const styleSource = style.sources[sourceId];
      if (styleSource.type !== SourceType.VECTOR) {
        this.handleError(
          new Error(`only works for vector sources, found ${styleSource.type}`)
        );
        return;
      }
  
      const source = this.getSource();
      source.setUrl(normalizeSourceUrl(styleSource.url, this.accessToken));
  
      applyStyle(this, style, sourceIdOrLayersList)
        .then(() => {
          source.setState(SourceState.READY);
        })
        .catch((error) => {
          this.handleError(error);
        });
    }
  
    /**
     * Handle configuration or loading error.
     * @param {Error} error The error.
     * @protected
     */
    handleError(error) {
      this.dispatchEvent(new ErrorEvent(error));
      const source = this.getSource();
      source.setState(SourceState.ERROR);
    }
  }
  
  export default MapboxVectorLayer;
  