import type {LngLat} from '@yandex/ymaps3-types';
import type {Feature} from '@yandex/ymaps3-clusterer';

window.map = null;

main();


/** @var Object ymaps3 */
async function main() {
  // Waiting for all api elements to be loaded
  await ymaps3.ready;

  const {
    YMapControls,
    YMap,
    YMapDefaultSchemeLayer,
    YMapFeatureDataSource,
    YMapLayer,
    YMapMarker,
    YMapListener
  } = ymaps3;

  // Load the package with the cluster, extract the classes for creating clusterer objects and the clustering method
  const {YMapClusterer, clusterByGrid} = await ymaps3.import('@yandex/ymaps3-clusterer@0.0.1');

  const {YMapZoomControl} = await ymaps3.import('@yandex/ymaps3-controls@0.0.1');

  let jsonData = await fetch('/map-customization.json')
    .then(response => response.json())
    .then(jsonData => {
      return jsonData
    });

  const mapContainer = document.getElementById('app');

  if (mapContainer) {
    let options = JSON.parse(document.querySelector('[data-options]').textContent);

    let center = [
      options.center.split(', ')[0],
      options.center.split(', ')[1]
    ];

    if (window.innerWidth <= 991) {
      center = [
        options.mobileCenter.split(', ')[0],
        options.mobileCenter.split(', ')[1]
      ];
    }

    const map = new YMap(mapContainer, {
      location: {
        center,
        zoom: (window.innerWidth <= 991 ? options.mobileZoom : options.zoom)
      }
    });
    // Create and add to the map a layer with a map schema, data sources, a layer of markers

    window.YMap = map;

    map.addChild(new YMapDefaultSchemeLayer({
      customization: jsonData
    }))
      .addChild(new YMapFeatureDataSource({id: 'clusterer-source'}))
      .addChild(new YMapLayer({source: 'clusterer-source', type: 'markers', zIndex: 1800}));


    if (window.innerWidth > 991) {
      map.setBehaviors(['dblClick', 'drag']);
    } else {
      map.setBehaviors(['dblClick']);
    }
    // map.behaviors.disable('drag', 'dblClick'/*['drag', 'dblClick', '']*/);

    map.addChild(
      new YMapControls({position: 'right'})
        .addChild(new YMapZoomControl({}))
    );

    // You can set any markup for the marker and for the cluster
    const contentPin = document.createElement('div');
    contentPin.classList.add('point');
    contentPin.style.position = 'relative';
    contentPin.style.left = '-15px';
    contentPin.style.top = '-35px';
    contentPin.innerHTML = `<img src="${options.imagesPath}/map-pin-normal.svg" class="pin">`;


    /* We declare the function for rendering ordinary markers, we will submit it to the clusterer settings.
    Note that the function must return any Entity element. In the example, this is ymaps3.YMapMarker. */
    const marker = (feature: Feature) => {
      let contentPinNew = contentPin.cloneNode(true);
      contentPinNew.dataset.id = feature.id;
      contentPinNew.dataset.coords = feature.properties.coords;


      contentPinNew.insertAdjacentHTML('beforeend', ``);

      contentPinNew.addEventListener('click', () => {

        if (window.innerWidth <= 991) {
          const map = document.querySelector('.geography__map');
          const button = document.querySelector('.geography__button');

          if (map) {
            map.classList.add('show-points');
          }

          if (button) {
            button.style.display = 'none';
          }
        }

        let geoBlock = document.querySelectorAll('.geography__block');
        let geo = document.querySelectorAll('.geography__info');

        geoBlock.forEach(geoInfo => {
          geoInfo.classList.add('geography__block__hidden');
        })

        geo.forEach(geoInfo => {
          geoInfo.classList.remove('geography__info__show');
        })

        if (document.querySelector('.geography__block')) {
          //   document.querySelector('.geography__block').classList.add('geography__block__hidden');
          document.querySelector('.geography__info[data-id="' + contentPinNew.dataset.id + '"]').classList.add('geography__info__show');


          const coords = contentPinNew.dataset.coords.split(',')

          window.YMap.update({
            location: {
              center: [coords[1], coords[0]],
              zoom: 16,
              duration: 1000,
            }
          });

          setTimeout(() => {
            const img = document.querySelector(`.point[data-id="${contentPinNew.dataset.id}"] img`);

            const points = document.querySelectorAll('.point img');

            points.forEach(point => point.setAttribute('src', `${options.imagesPath}/map-pin-normal.svg`));

            img.setAttribute('src', `${options.imagesPath}/map-pin-active.svg`);

          }, 10)
        } else {
          const coords = contentPinNew.dataset.coords.split(',')

          window.YMap.update({
            location: {
              center: [coords[1], coords[0]],
              zoom: 16,
              duration: 1000,
            }
          });
        }
      });

      return new YMapMarker(
        {
          coordinates: feature.geometry.coordinates,
          source: 'clusterer-source'
        },
        contentPinNew
      );


    }

    interface CustomMarkerWithPopupProps {
      coordinates: LngLat; // marker position [lng, lat]
      popupContent: string;
      zIndex?: number;
      blockBehaviors?: boolean;
    }


    // As for ordinary markers, we declare a cluster rendering function that also returns an Entity element.


    function circle(count: number, features) {
      const circle = document.createElement('div');
      let ids = '';
      let names = '';
      let urls = '';
      let previews = '';
      let populars = '';

      features.forEach(e => {
        ids += e.id.toString() + ',';
        names += e.properties.name + ' #+# '
        urls += e.properties.url + ','
        previews += e.properties.preview + ' #+# ';
        populars += e.properties.popular !== null ? '+' + ' #=# ' : '-' + ' #=# ';
      });


      circle.classList.add('circle');
      circle.innerHTML = `
            <div class="circle-content" data-ids="${ids}" data-populars="${populars}" data-names="${names}" data-previews="${previews}" data-urls="${urls}" style="position: relative; width: 50px; height: 48px; left: -15px; top: -45px;">
            <img src="${options.imagesPath}/map-pin.svg" alt="" style="width: 100%;">
                <span class="circle-text" style="position: absolute;
    font-weight: bold;
    display: flex;
    color: #fff;
    justify-content: center;
    align-items: center;
    width: 100%;
    height: 100%;
    top: 2px;">${count}</span>
            </div>
        `;


      return circle;
    }

    const cluster = (coordinates: LngLat, features: Feature[]) => {
      let currentCircle = circle(features.length, features).cloneNode(true) as HTMLElement;

      let marker = new YMapMarker(
        {
          coordinates,
          source: 'clusterer-source',
          features
        },
        currentCircle
      );

      currentCircle.addEventListener('click', event => {

        var x1 = Infinity,
          x2 = 0,
          y1 = Infinity,
          y2 = 0;

        for (var i = 0; i < marker._props.features.length; i++) {
          var prop = marker._props.features[i];
          x1 = Math.min(prop.geometry.coordinates[0], x1);
          x2 = Math.max(prop.geometry.coordinates[0], x2);
          y1 = Math.min(prop.geometry.coordinates[1], y1);
          y2 = Math.max(prop.geometry.coordinates[1], y2);
        }

        var bounds = [[x1, y1], [x2, y2]];
        var x = bounds[0][0] + (bounds[1][0] - bounds[0][0]) / 2;
        var y = bounds[0][1] + (bounds[1][1] - bounds[0][1]) / 2;

        var center = [x, y];

        map.update({
          location: {
            center: center,
            zoom: getScale(bounds, map, true, false) - 1,
            duration: 1000,
          }
        });
      });

      return marker;
    }


    /* We create a clusterer object and add it to the map object.
    As parameters, we pass the clustering method, an array of features, the functions for rendering markers and clusters.
    For the clustering method, we will pass the size of the grid division in pixels. */

    const points = JSON.parse(document.querySelector('[data-points]').textContent).points

    window.mapPoints = points;

    if (!window.logicloud) {
      window.logicloud = {};  
    }


    window.logicloud.clusterer = new YMapClusterer({
      method: clusterByGrid({gridSize: 64}),
      features: points,
      marker,
      cluster
    });

    map.addChild(window.logicloud.clusterer);

    map.addChild(
      new YMapListener({
        onActionStart: e => {
        },
        onActionEnd: e => {
        },
      })
    );
  }
}

/**
 * Получение масштаба
 * @param {[Array, Array]} bounds
 * @param {YMap} map
 * @param {Boolean} inscribe вписывать область в карту
 * @param {Boolean} floor округляем результат в меньшую сторону
 * @returns {Number}
 */
function getScale(bounds, map, inscribe, floor) {
  if (typeof inscribe === "undefined") {
    inscribe = true;
  }
  if (typeof floor === "undefined") {
    floor = false;
  }
  var pixelBounds = toGlobalPixelBounds(bounds, 0);
  // 1e-10 чтобы не было деления на 0
  var deltaX = Math.max(Math.abs(pixelBounds[1][0] - pixelBounds[0][0]), 1e-10);
  var deltaY = Math.max(Math.abs(pixelBounds[1][1] - pixelBounds[0][1]), 1e-10);
  var logX = Math.log(map.size.x / deltaX) * Math.LOG2E;
  var logY = Math.log(map.size.y / deltaY) * Math.LOG2E;
  var result = Math.min(Math.max(0, inscribe ? Math.min(logX, logY) : Math.max(logX, logY)), map.zoomRange.max);
  return floor ? Math.floor(result + 1e-10) : result;
}

function toGlobalPixelBounds(geoBounds, zoom) {
  if (typeof zoom === "undefined") {
    zoom = 0;
  }

  var lowerCorner = toGlobalPixels(geoBounds[0], zoom);
  var upperCorner = toGlobalPixels(geoBounds[1], zoom);
  var projectionCycled = [true, false];
  var worldSize = calculateWorldSize(zoom);
  var result = [lowerCorner.slice(), upperCorner.slice()];
  if (lowerCorner[0] > upperCorner[0]) {
    if (projectionCycled[0]) {
      result[0][0] = lowerCorner[0];
      result[1][0] = upperCorner[0] + worldSize;
    } else {
      result[0][0] = upperCorner[0];
      result[1][0] = lowerCorner[0];
    }
  }
  if (lowerCorner[1] > upperCorner[1]) {
    if (projectionCycled[1]) {
      result[0][1] = lowerCorner[1];
      result[1][1] = upperCorner[1] + worldSize;
    } else {
      result[0][1] = upperCorner[1];
      result[1][1] = lowerCorner[1];
    }
  }
  return result;
}

function toGlobalPixels(point, zoom) {
  var radius = 6378137;
  var equator = 2 * Math.PI * radius;
  var subequator = 1 / equator;
  var pixelsPerMeter = 256 * subequator;
  var halfEquator = equator / 2;
  var currentZoom = 0;

  if (zoom != currentZoom) {
    pixelsPerMeter = Math.pow(2, zoom + 8) * subequator;
    currentZoom = zoom;
  }

  var mercatorCoords = geoToMercator(point);
  return [
    (halfEquator + mercatorCoords[0]) * pixelsPerMeter,
    (halfEquator - mercatorCoords[1]) * pixelsPerMeter
  ];
}

function geoToMercator(geo) {
  return [
    longitudeToX(geo[0]),
    latitudeToY(geo[1])
  ];
}
;

function longitudeToX(lng) {
  var radius = 6378137;
  var c_pi180 = Math.PI / 180;
  var longitude = cycleRestrict(lng * c_pi180, -Math.PI, Math.PI);
  return radius * longitude;
}

function latitudeToY(lat) {
  var radius = 6378137;
  var e = 0.0818191908426;
  var c_pi180 = Math.PI / 180;
  var c_180pi = 180 / Math.PI;
  var epsilon = 1e-10;
  // epsilon чтобы не получить (-)Infinity
  var latitude = restrict(lat, -90 + epsilon, 90 - epsilon) * c_pi180;
  var esinLat = e * Math.sin(latitude);

  // Для широты -90 получается 0, и в результате по широте выходит -Infinity
  var tan_temp = Math.tan(Math.PI * 0.25 + latitude * 0.5);
  var pow_temp = Math.pow(Math.tan(Math.PI * 0.25 + Math.asin(esinLat) * 0.5), e);
  var U = tan_temp / pow_temp;

  return radius * Math.log(U);
}

function restrict(value, min, max) {
  return Math.max(Math.min(value, max), min);
}

function cycleRestrict(value, min, max) {
  if (value == Number.POSITIVE_INFINITY) {
    return max;
  } else if (value == Number.NEGATIVE_INFINITY) {
    return min;
  }
  return value - Math.floor((value - min) / (max - min)) * (max - min);
}

function calculateWorldSize(zoom) {
  return Math.pow(2, zoom + 8);
}

function getBounds() {
  var x1 = Infinity,
    x2 = 0,
    y1 = Infinity,
    y2 = 0;

  for (var i = 0; i < this._props.length; i++) {
    var prop = this._props[i];
    x1 = Math.min(prop.geometry.coordinates[0], x1);
    x2 = Math.max(prop.geometry.coordinates[0], x2);
    y1 = Math.min(prop.geometry.coordinates[1], y1);
    y2 = Math.max(prop.geometry.coordinates[1], y2);
  }
  return [[x1, y1], [x2, y2]];
}

//Получение центра
function getCenter() {
  var bounds = getBounds();
  var x = bounds[0][0] + (bounds[1][0] - bounds[0][0]) / 2;
  var y = bounds[0][1] + (bounds[1][1] - bounds[0][1]) / 2;
  return [x, y];
}
