import { getAbsoluteUrl } from "../utils";
import maplibregl from "maplibre-gl";
import MapboxDraw from "@mapbox/mapbox-gl-draw";
// We do the following as a hack to get compatibility with Maplibre-gl 3.3.0 and mapbox-draw-gl
// TODO: When compatibility for maplibre 3.x is figured out, remove this and update: https://github.com/mapbox/mapbox-gl-draw/pull/1184
// @ts-ignore
MapboxDraw.constants.classes.CONTROL_BASE = "maplibregl-ctrl";
// @ts-ignore
MapboxDraw.constants.classes.CONTROL_PREFIX = "maplibregl-ctrl-";
// @ts-ignore
MapboxDraw.constants.classes.CONTROL_GROUP = "maplibregl-ctrl-group";
// @ts-ignore
MapboxDraw.constants.classes.ATTRIBUTION = "maplibregl-ctrl-attrib";
export class Map {
  constructor() {
    this._features = [];
    this.draw = undefined;
    this.drawOptions = undefined;
    this.drawing = false;
    this.glyphs = undefined;
    this.hash = false;
    this.latitude = undefined;
    this.longitude = undefined;
    this.zoom = 10;
    this.minzoom = 0;
    this.maxzoom = 22;
    this.sprite = undefined;
    this.map = undefined;
    this.clickMode = "top-layer";
    this.clickEventDelay = 100;
  }
  componentWillLoad() {
    this._style = this.loadStyle();
    this.map = new maplibregl.Map({
      container: this.el,
      center: [this.longitude, this.latitude],
      hash: this.hash === "true" ? true : this.hash,
      zoom: this.zoom,
      minZoom: this.minzoom,
      maxZoom: this.maxzoom,
      style: this._style,
    });
    window.addEventListener("resize", this.resizeMap.bind(this));
    this.map.on("draw.create", (e) => this.glDrawCreate.emit({
      mapId: this.el.id,
      features: e.features,
    }));
    this.map.on("draw.delete", (e) => this.glDrawDelete.emit({
      mapId: this.el.id,
      features: e.features,
    }));
  }
  componentDidUpdate() {
    this.resizeMap();
  }
  async updateStyle() {
    if (this._updateStyleTimeout == undefined) {
      this._updateStyleTimeout = window.setTimeout(async () => {
        this._updateStyleTimeout = null;
        this._style = await this.loadStyle();
        this.map.setStyle(this._style);
        this.glStyleUpdated.emit(this._style);
      }, 66);
    }
  }
  handleFeatureClick(e) {
    let features = e.detail.features;
    if (features.length === 0)
      return;
    Array.prototype.push.apply(this._features, features);
    if (this.clickEventDelay != undefined && this.clickEventDelay !== 0) {
      if (this._clickEventTimeout == undefined) {
        this._clickEventTimeout = window.setTimeout(async () => {
          this._clickEventTimeout = null;
          if (this._features.length !== 0) {
            this.glClickedFeaturesGathered.emit({
              features: this.filterFeatureClicks(this._features),
            });
          }
          this._features = [];
        }, this.clickEventDelay);
      }
    }
    else {
      this.glClickedFeaturesGathered.emit({
        features: this.filterFeatureClicks(this._features),
      });
    }
  }
  async handleStyleAdded() {
    this.updateStyle();
  }
  async handleStyleModified() {
    this.updateStyle();
  }
  async handleStyleRemoved() {
    this.updateStyle();
  }
  async updateDrawing() {
    if (this.drawing) {
      this.draw = new MapboxDraw(this.drawOptions);
      // @ts-ignore TODO: Find a better alternative to ignoring the type conversion.
      this.map.addControl(this.draw);
      this.glDrawEnter.emit({
        mapId: this.el.id,
      });
    }
    else {
      // @ts-ignore
      this.map.removeControl(this.draw);
      this.draw = null;
      this.glDrawExit.emit({
        mapId: this.el.id,
      });
    }
  }
  /**
   * Informs Mapbox GL that the map canvas has been resized. It uses a
   * timeout and can be called repeatedly (e.g., in an event handler) without
   * a performance penalty. For a synchronous resize, use `.map.resize()`.
   */
  async resizeMap() {
    if (this._resizeMapTimeout == undefined) {
      this._resizeMapTimeout = window.setTimeout(async () => {
        this._resizeMapTimeout = null;
        if (this.map != undefined)
          this.map.resize();
      }, 66);
    }
  }
  getStyleLayers(styleId, json) {
    if (json.layers == undefined)
      return [];
    let results = [];
    for (let layer of json.layers) {
      let result = Object.assign(Object.assign({}, layer), { id: this.prefix(styleId, layer.id) });
      if ("source" in layer &&
        layer.source != undefined &&
        typeof layer.source === "string")
        result["source"] = this.prefix(styleId, layer.source);
      results.push(result);
    }
    return results;
  }
  getStyleSources(styleId, json) {
    if (json.sources == undefined)
      return [];
    let sources = {};
    for (let sourceName in json.sources)
      sources[this.prefix(styleId, sourceName)] = json.sources[sourceName];
    return sources;
  }
  loadStyle() {
    let style = {
      version: 8,
      metadata: {},
      sources: {},
      layers: [],
    };
    if (this.glyphs != undefined)
      style.glyphs = this.glyphs;
    if (this.sprite != undefined)
      style.sprite = this.sprite;
    Array.from(this.el.querySelectorAll("gl-style")).forEach((styleEl) => {
      if (!styleEl.enabled)
        return;
      let id = styleEl.id;
      let json = styleEl.json;
      if (json == undefined)
        return;
      let styleLayers = this.getStyleLayers(id, json);
      if (styleEl.insertIntoStyle != undefined &&
        styleEl.insertBeneathLayer != undefined) {
        const insertIntoIdx = style.layers
          .map((sl) => sl.id)
          .indexOf(`${styleEl.insertIntoStyle}:${styleEl.insertBeneathLayer}`);
        style.layers.splice(insertIntoIdx, 0, ...styleLayers);
      }
      else {
        style.layers = styleLayers.concat(style.layers);
      }
      style.sources = Object.assign(Object.assign({}, style.sources), this.getStyleSources(id, json));
      // TODO: Deal with multiple styles that each have their own glyphs
      // or sprites. For now, we use the explicitly set URL on gl-map,
      // or fall back to the first URL found in the child styles. See:
      // https://github.com/mapbox/mapbox-gl-js/issues/4086
      // https://github.com/mapbox/mapbox-gl-js/issues/4000
      if (json.glyphs && style.glyphs == undefined)
        style.glyphs = json.glyphs;
      if (json.sprite && style.sprite == undefined)
        style.sprite = json.sprite;
    });
    if (style.glyphs != undefined)
      style.glyphs = getAbsoluteUrl(style.glyphs)
        .replace("%7Bfontstack%7D", "{fontstack}")
        .replace("%7Brange%7D", "{range}");
    if (style.sprite != undefined) {
      if (typeof style.sprite === "string") {
        style.sprite = getAbsoluteUrl(style.sprite);
      }
      else if (style.sprite.length !== 0) {
        style.sprite.forEach((sprite) => {
          sprite.url = getAbsoluteUrl(sprite.url);
        });
      }
    }
    return style;
  }
  prefix(styleId, value) {
    return `${styleId}:${value}`;
  }
  filterTopLayer(features) {
    let layerName = features[features.length - 1].layer.id;
    return features.filter((feature) => feature.layer.id === layerName);
  }
  filterTopFeature(features) {
    return [features[features.length - 1]];
  }
  filterFeatureClicks(features) {
    switch (this.clickMode) {
      case "all":
        return features;
      case "top-feature":
        return this.filterTopFeature(features);
      case "top-layer":
        return this.filterTopLayer(features);
      default:
        throw new Error("Popup filtering error");
    }
  }
  static get is() { return "gl-map"; }
  static get originalStyleUrls() {
    return {
      "$": ["map.css"]
    };
  }
  static get styleUrls() {
    return {
      "$": ["map.css"]
    };
  }
  static get properties() {
    return {
      "draw": {
        "type": "unknown",
        "mutable": true,
        "complexType": {
          "original": "MapboxDraw",
          "resolved": "MapboxDraw",
          "references": {
            "MapboxDraw": {
              "location": "global",
              "id": "global::MapboxDraw"
            }
          }
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Webmapgl uses the mapbox-gl-draw plugin for drawing features.\nThis property is a reference to the `MapboxDraw` instance when a draw session is active."
        }
      },
      "drawOptions": {
        "type": "unknown",
        "mutable": false,
        "complexType": {
          "original": "MapboxDraw.MapboxDrawOptions",
          "resolved": "MapboxDrawOptions",
          "references": {
            "MapboxDraw": {
              "location": "global",
              "id": "global::MapboxDraw"
            }
          }
        },
        "required": false,
        "optional": true,
        "docs": {
          "tags": [],
          "text": "Options object used to initialize `MapboxDraw`."
        }
      },
      "drawing": {
        "type": "boolean",
        "mutable": false,
        "complexType": {
          "original": "boolean",
          "resolved": "boolean",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "There is an active draw session. It is usually set by `gl-draw-controller`."
        },
        "attribute": "drawing",
        "reflect": false,
        "defaultValue": "false"
      },
      "glyphs": {
        "type": "string",
        "mutable": false,
        "complexType": {
          "original": "string",
          "resolved": "string",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Default glyphs URL. It can be overridden by child `gl-style` components."
        },
        "attribute": "glyphs",
        "reflect": false
      },
      "hash": {
        "type": "any",
        "mutable": false,
        "complexType": {
          "original": "string | boolean",
          "resolved": "boolean | string",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Store the map location and zoom level in the URL hash. If the value is\na string, that value will be used as the key in the URL hash."
        },
        "attribute": "hash",
        "reflect": false,
        "defaultValue": "false"
      },
      "latitude": {
        "type": "number",
        "mutable": false,
        "complexType": {
          "original": "number",
          "resolved": "number",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Initial center latitude of the map."
        },
        "attribute": "latitude",
        "reflect": false
      },
      "longitude": {
        "type": "number",
        "mutable": false,
        "complexType": {
          "original": "number",
          "resolved": "number",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Initial center longitude of the map."
        },
        "attribute": "longitude",
        "reflect": false
      },
      "zoom": {
        "type": "number",
        "mutable": false,
        "complexType": {
          "original": "number",
          "resolved": "number",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Initial zoom level of the map."
        },
        "attribute": "zoom",
        "reflect": false,
        "defaultValue": "10"
      },
      "minzoom": {
        "type": "number",
        "mutable": false,
        "complexType": {
          "original": "number",
          "resolved": "number",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Minimum zoom level."
        },
        "attribute": "minzoom",
        "reflect": false,
        "defaultValue": "0"
      },
      "maxzoom": {
        "type": "number",
        "mutable": false,
        "complexType": {
          "original": "number",
          "resolved": "number",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Maximum zoom level."
        },
        "attribute": "maxzoom",
        "reflect": false,
        "defaultValue": "22"
      },
      "sprite": {
        "type": "string",
        "mutable": false,
        "complexType": {
          "original": "string",
          "resolved": "string",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Default sprite URL. It can be overridden by child `gl-style` components."
        },
        "attribute": "sprite",
        "reflect": false
      },
      "map": {
        "type": "unknown",
        "mutable": true,
        "complexType": {
          "original": "maplibregl.Map",
          "resolved": "Map",
          "references": {
            "maplibregl": {
              "location": "global",
              "id": "global::maplibregl"
            }
          }
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Reference to the `maplibregl.Map` object. It can be used to customize\nmap properties and behaviors beyond those exposed by `gl-map`."
        }
      },
      "clickMode": {
        "type": "string",
        "mutable": false,
        "complexType": {
          "original": "\"all\" | \"top-layer\" | \"top-feature\"",
          "resolved": "\"all\" | \"top-feature\" | \"top-layer\"",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "How many, and what, feature to return should the user click on the map"
        },
        "attribute": "click-mode",
        "reflect": false,
        "defaultValue": "\"top-layer\""
      },
      "clickEventDelay": {
        "type": "number",
        "mutable": false,
        "complexType": {
          "original": "number",
          "resolved": "number",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Milliseconds to wait before opening the popup. This delay allows for\nhandling multiple feature click events in a single popup. Multiple events\ncan be fired in quick succession when the user clicks on multiple\noverlapping features."
        },
        "attribute": "click-event-delay",
        "reflect": false,
        "defaultValue": "100"
      }
    };
  }
  static get events() {
    return [{
        "method": "glDrawCreate",
        "name": "glDrawCreate",
        "bubbles": true,
        "cancelable": true,
        "composed": true,
        "docs": {
          "tags": [],
          "text": "Emitted when a new feature is created in a drawing session."
        },
        "complexType": {
          "original": "any",
          "resolved": "any",
          "references": {}
        }
      }, {
        "method": "glDrawDelete",
        "name": "glDrawDelete",
        "bubbles": true,
        "cancelable": true,
        "composed": true,
        "docs": {
          "tags": [],
          "text": "Emitted when a feature is deleted in a drawing session."
        },
        "complexType": {
          "original": "any",
          "resolved": "any",
          "references": {}
        }
      }, {
        "method": "glDrawEnter",
        "name": "glDrawEnter",
        "bubbles": true,
        "cancelable": true,
        "composed": true,
        "docs": {
          "tags": [],
          "text": "Emitted when a draw session starts."
        },
        "complexType": {
          "original": "any",
          "resolved": "any",
          "references": {}
        }
      }, {
        "method": "glDrawExit",
        "name": "glDrawExit",
        "bubbles": true,
        "cancelable": true,
        "composed": true,
        "docs": {
          "tags": [],
          "text": "Emitted when a draw session ends."
        },
        "complexType": {
          "original": "any",
          "resolved": "any",
          "references": {}
        }
      }, {
        "method": "glStyleUpdated",
        "name": "glStyleUpdated",
        "bubbles": true,
        "cancelable": true,
        "composed": true,
        "docs": {
          "tags": [],
          "text": "Emitted after the map style has been updated."
        },
        "complexType": {
          "original": "any",
          "resolved": "any",
          "references": {}
        }
      }, {
        "method": "glClickedFeaturesGathered",
        "name": "glClickedFeaturesGathered",
        "bubbles": true,
        "cancelable": true,
        "composed": true,
        "docs": {
          "tags": [],
          "text": "Emitted after one or more features has been clicked"
        },
        "complexType": {
          "original": "any",
          "resolved": "any",
          "references": {}
        }
      }];
  }
  static get methods() {
    return {
      "resizeMap": {
        "complexType": {
          "signature": "() => Promise<void>",
          "parameters": [],
          "references": {
            "Promise": {
              "location": "global",
              "id": "global::Promise"
            }
          },
          "return": "Promise<void>"
        },
        "docs": {
          "text": "Informs Mapbox GL that the map canvas has been resized. It uses a\ntimeout and can be called repeatedly (e.g., in an event handler) without\na performance penalty. For a synchronous resize, use `.map.resize()`.",
          "tags": []
        }
      }
    };
  }
  static get elementRef() { return "el"; }
  static get watchers() {
    return [{
        "propName": "drawing",
        "methodName": "updateDrawing"
      }];
  }
  static get listeners() {
    return [{
        "name": "glFeatureClick",
        "method": "handleFeatureClick",
        "target": undefined,
        "capture": false,
        "passive": false
      }, {
        "name": "glStyleElementAdded",
        "method": "handleStyleAdded",
        "target": undefined,
        "capture": false,
        "passive": false
      }, {
        "name": "glStyleElementModified",
        "method": "handleStyleModified",
        "target": undefined,
        "capture": false,
        "passive": false
      }, {
        "name": "glStyleElementRemoved",
        "method": "handleStyleRemoved",
        "target": undefined,
        "capture": false,
        "passive": false
      }];
  }
}
//# sourceMappingURL=map.js.map
