// Web Mercator проекция + слой растровых тайлов (slippy-map) + кэш загрузки.
// Подложки: тёмная (CARTO, OSM-данные), классическая OSM, без тайлов (схема).
(function () {
  const TILE = 256;
  const DEG = Math.PI / 180, RAD = 180 / Math.PI;
  const MAX_LAT = 85.0511;

  // lon/lat -> мировые пиксели при размере мира ws (= TILE * 2^z).
  function lonToWorldX(lon, ws) { return (lon + 180) / 360 * ws; }
  function latToWorldY(lat, ws) {
    const l = Math.max(-MAX_LAT, Math.min(MAX_LAT, lat));
    const s = Math.sin(l * DEG);
    const y = 0.5 - Math.log((1 + s) / (1 - s)) / (4 * Math.PI);
    return y * ws;
  }
  function worldXToLon(wx, ws) { return wx / ws * 360 - 180; }
  function worldYToLat(wy, ws) {
    const n = Math.PI - 2 * Math.PI * (wy / ws);
    return RAD * Math.atan(Math.sinh(n));
  }

  // Метры на пиксель в точке широты (для перевода км -> px).
  function metersPerPixel(lat, ws) {
    return Math.cos(lat * DEG) * 2 * Math.PI * 6378137 / ws;
  }

  const PROVIDERS = {
    dark: {
      id: "dark", name: "Тёмная",
      url: (z, x, y) => `https://a.basemaps.cartocdn.com/dark_all/${z}/${x}/${y}.png`,
      scrim: 0.0, label: "© OpenStreetMap · © CARTO", maxZoom: 18, dark: true,
    },
    osm: {
      id: "osm", name: "Светлая",
      url: (z, x, y) => `https://a.basemaps.cartocdn.com/rastertiles/voyager/${z}/${x}/${y}.png`,
      scrim: 0.34, label: "© OpenStreetMap · © CARTO", maxZoom: 19, dark: false,
    },
    schema: {
      id: "schema", name: "Схема", url: null, scrim: 0, label: "Схематичная основа (оффлайн)", dark: true,
    },
  };

  // Кэш тайлов: key -> {img, loaded, error}.
  const cache = new Map();
  function getTile(provider, z, x, y, onLoad) {
    const key = provider.id + "/" + z + "/" + x + "/" + y;
    let rec = cache.get(key);
    if (rec) return rec;
    rec = { img: new Image(), loaded: false, error: false };
    rec.img.crossOrigin = "anonymous";
    rec.img.onload = () => { rec.loaded = true; onLoad && onLoad(); };
    rec.img.onerror = () => { rec.error = true; };
    rec.img.src = provider.url(z, x, y);
    cache.set(key, rec);
    return rec;
  }

  // Рисует слой тайлов на ctx для текущего вида.
  // view: { centerLon, centerLat, z }, size: {w,h}. onLoad — колбэк перерисовки.
  function drawTiles(ctx, provider, view, size, onLoad) {
    if (!provider || !provider.url) return;
    const { w, h } = size;
    const z = view.z;
    const tz = Math.max(1, Math.min(provider.maxZoom || 18, Math.round(z)));
    const ws = TILE * Math.pow(2, z);        // непрерывный размер мира
    const k = Math.pow(2, z - tz);           // масштаб тайла tz -> экран
    const tileScreen = TILE * k;
    const n = Math.pow(2, tz);

    const cwx = lonToWorldX(view.centerLon, ws);
    const cwy = latToWorldY(view.centerLat, ws);
    const originX = cwx - w / 2;              // мировой px (непрерывный) в левом верхнем углу
    const originY = cwy - h / 2;

    // Видимый диапазон тайлов в tz-пространстве.
    const leftTz = (originX / k) / TILE;
    const topTz = (originY / k) / TILE;
    const rightTz = ((originX + w) / k) / TILE;
    const botTz = ((originY + h) / k) / TILE;
    const x0 = Math.floor(leftTz), x1 = Math.floor(rightTz);
    const y0 = Math.max(0, Math.floor(topTz)), y1 = Math.min(n - 1, Math.floor(botTz));

    ctx.imageSmoothingEnabled = true;
    for (let tx = x0; tx <= x1; tx++) {
      const wrapX = ((tx % n) + n) % n;       // горизонтальный повтор мира
      for (let ty = y0; ty <= y1; ty++) {
        const rec = getTile(provider, tz, wrapX, ty, onLoad);
        const sx = tx * tileScreen - originX;
        const sy = ty * tileScreen - originY;
        if (rec.loaded) {
          ctx.drawImage(rec.img, Math.floor(sx), Math.floor(sy), Math.ceil(tileScreen) + 1, Math.ceil(tileScreen) + 1);
        }
      }
    }
    // затемняющий scrim (для светлой OSM, чтобы overlay читался)
    if (provider.scrim > 0) {
      ctx.fillStyle = `rgba(8,16,12,${provider.scrim})`;
      ctx.fillRect(0, 0, w, h);
    }
  }

  window.Basemap = {
    TILE, MAX_LAT, PROVIDERS,
    lonToWorldX, latToWorldY, worldXToLon, worldYToLat,
    metersPerPixel, drawTiles, getTile,
  };
})();
