<template>
  <!-- map container -->
  <div
    id="map"
    style="height: 100vh; background-color: transparent; position: relative"
  ></div>

  <!-- fullscreen video shown after 10 s of inactivity -->
  <div
    id="video-overlay"
    v-show="videoOverlayVisible"
    style="
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh;
      z-index: 10000;
      background: #000;
      pointer-events: none;
    "
  ></div>
</template>

<script>
import * as PIXI from "pixi.js";
import { Viewport } from "pixi-viewport";
import { Text, TextStyle, FillGradient } from "pixi.js";
import { API_URL, GEO_URL } from "../config";

export default {
  name: "MainMap",
  data() {
    return {
      routeId: null,
      pixiApp: null,
      viewport: null,
      routeGraphics: null,
      videoOverlayVisible: false,
      stationContainer: null,
      toCanvasPoint: null,
      routePollTimer: null,
      attemptCount: 0,
      routeGroup: null,
      routeRotate: 0,
      routeCentered: false,
      viewportAdjusted: false, // авто‑фит сделан?
      clampApplied: false, // границы перемещения выставлены?
      stationZoomed: false, // приблизили ли к станции?
      centerLatitude: null,
      centerLongitude: null,
    };
  },
  async mounted() {
    await this.createPixiApp();
    await this.fetchContext();
    this.startRoutePolling();
  },

  methods: {
    async createPixiApp() {
      const container = document.getElementById("map");
      if (!container || this.pixiApp) return;

      // PixiJS v8: create then init
      this.pixiApp = new PIXI.Application();
      await this.pixiApp.init({
        resizeTo: container,
        antialias: true,
        backgroundAlpha: 0,
      });

      // add canvas to DOM (missing after last refactor)
      container.appendChild(this.pixiApp.canvas);

      // гарантируем, что канвас всегда принимает pointer‑/touch‑события
      this.pixiApp.canvas.style.pointerEvents = "auto";
      this.pixiApp.canvas.style.touchAction = "none";
      this.pixiApp.canvas.style.zIndex = "1";

      // убедимся, что video‑overlay действительно выше канваса
      const overlay = document.getElementById("video-overlay");
      if (overlay) overlay.style.zIndex = "10000";

      // create interactive viewport (pixi v8: use "events")
      this.viewport = new Viewport({
        screenWidth: container.clientWidth,
        screenHeight: container.clientHeight,
        events: this.pixiApp.renderer.events, // EventSystem
      });

      this.routeGroup = new PIXI.Container();
      // this.routeGroup.scale.set(2);
      this.viewport.addChild(this.routeGroup);

      // enable typical interactions (restored .decelerate())
      this.viewport.drag().pinch().wheel().decelerate();
      // если к этому моменту уже известны ограничения – сразу применим
      this.updateViewportClampZoom();

      // add viewport to stage
      this.pixiApp.stage.addChild(this.viewport);

      this.routeGraphics = new PIXI.Graphics();
      this.routeGroup.addChild(this.routeGraphics);

      this.stationContainer = new PIXI.Container();
      this.routeGroup.addChild(this.stationContainer);
    },
    // Применяем ограничения масштабирования к viewport
    updateViewportClampZoom() {
      if (
        this.viewport &&
        this.routeScaleMin !== null &&
        this.routeScaleMax !== null
      ) {
        const minScale = this.routeScaleMin / 20;
        const maxScale = this.routeScaleMax / 20;
        // убедимся, что min < max
        if (minScale < maxScale) {
          this.viewport.clampZoom({ minScale, maxScale });
          console.log(
            `%cViewport clampZoom → min=${minScale}, max=${maxScale}`,
            "color: violet;"
          );
        }
      }
    },

    // Рендерим линию маршрута по массиву координат [lat, lng]
    drawRoute(latlngs) {
      if (!Array.isArray(latlngs) || latlngs.length < 2) return;
      // если graphics ещё не готов – запомним и перерисуем, когда он появится
      if (!this.routeGraphics) {
        // пробуем снова через animation frame
        requestAnimationFrame(() => this.drawRoute(latlngs));
        return;
      }

      // очищаем графику и задаём стиль обводки
      this.routeGraphics.clear();
      this.routeGraphics.setStrokeStyle({
        width: 10,
        color: "#ED1C24",
        alpha: 1,
      });
      this.routeGraphics.beginPath();

      // Bounding box для нормализации
      const lats = latlngs.map((p) => p[0]);
      const lngs = latlngs.map((p) => p[1]);
      const minLat = Math.min(...lats);
      const maxLat = Math.max(...lats);
      const minLng = Math.min(...lngs);
      const maxLng = Math.max(...lngs);

      const canvas = this.pixiApp.canvas;
      const width = canvas.width;
      const height = canvas.height;
      const padding = 40;

      const scaleX = (width - padding * 2) / (maxLng - minLng || 1);
      const scaleY = (height - padding * 2) / (maxLat - minLat || 1);
      let scale = 30000;
      console.log(scaleX, scaleY, scale);

      const toPoint = ([lat, lng]) => {
        const x = (lng - minLng) * scale + padding;
        const y = (maxLat - lat) * scale + padding;
        return { x, y };
      };

      // сохраняем функцию трансформации для станций
      this.toCanvasPoint = toPoint;

      const first = toPoint(latlngs[0]);
      this.routeGraphics.moveTo(first.x, first.y);
      for (let i = 1; i < latlngs.length; i++) {
        const p = toPoint(latlngs[i]);
        this.routeGraphics.lineTo(p.x, p.y);
      }
      this.routeGraphics.stroke();
    },

    // Отрисовываем станции: PIXI-кружок + подпись (RU+EN)
    drawStations(stationsRu, stationsEn) {
      console.groupCollapsed(
        "%cdrawStations()",
        "color: dodgerblue; font-weight: bold;"
      );

      // Базовые проверки
      if (!Array.isArray(stationsRu) || !stationsRu.length) {
        console.warn("stationsRu пуст или не массив → ничего не рисуем");
        console.groupEnd();
        return;
      }
      if (!this.toCanvasPoint) {
        console.warn("toCanvasPoint ещё не готов → ждём drawRoute()");
        console.groupEnd();
        return;
      }

      // RU->EN словарь
      const enById = {};
      if (Array.isArray(stationsEn)) {
        stationsEn.forEach((st) => (enById[st.id] = st.name));
      } else {
        console.warn("stationsEn не массив → EN названия пропущены");
      }

      // Очищаем слой
      if (this.stationContainer) this.stationContainer.removeChildren();
      else {
        console.warn("stationContainer ещё не создан");
        console.groupEnd();
        return;
      }

      let drawn = 0;
      stationsRu.forEach((st, idx) => {
        // Проверяем координаты
        if (
          typeof st.latitude !== "number" ||
          typeof st.longitude !== "number"
        ) {
          console.warn(`station[${idx}] (${st.id}) без координат → пропущен`);
          return;
        }

        const { x, y } = this.toCanvasPoint([st.latitude, st.longitude]);
        const ox = (st.offset_x || 0) / 1.25; // смещение по X (½)
        const oy = (st.offset_y || 0) * 1; // смещение по Y (½)
        if (isNaN(x) || isNaN(y)) {
          console.warn(`station[${idx}] (${st.id}) координаты NaN → пропущен`);
          return;
        }

        /* 1. Кружок PIXI */
        const node = new PIXI.Container();
        node.x = x;
        node.y = y;
        this.stationContainer.addChild(node);

        const g = new PIXI.Graphics();
        g.fill("#ED1C24")
          .stroke({ width: 5, color: 0x000000 })
          .circle(0, 0, 11)
          .fill()
          .stroke();
        node.addChild(g);

        // const g = new PIXI.Graphics();
        // g.fill(0xffffff).stroke({ width: 2, color: 0x000000 });
        // g.circle(0, 0, 60);
        // node.addChild(g);

        /* 2. Двухстрочная подпись (RU + EN) */
        const solidWhite = new FillGradient({
          type: "linear",
          colorStops: [
            { offset: 0, color: 0xffffff },
            { offset: 1, color: 0xffffff },
          ],
        });

        const labelGroup = new PIXI.Container();
        labelGroup.x = ox;
        labelGroup.y = oy;
        // сохраняем исходные оффсеты, чтобы пересчитывать при повороте
        labelGroup._ox = ox;
        labelGroup._oy = oy;
        node.addChild(labelGroup);

        // RU
        const ruStyle = new TextStyle({
          fill: solidWhite,
          fontSize: 20,
          fontFamily: "Arial",
          align: "right",
          fontWeight: "bold",
        });
        const ruText = new Text({ text: st.name, style: ruStyle });
        ruText.anchor.set(1, 0.5); // bottom-right
        ruText.x = 24;
        ruText.y = -20;
        labelGroup.addChild(ruText);

        // EN
        const enName = enById[st.id];
        if (enName) {
          const enStyle = new TextStyle({
            fill: solidWhite,
            fontSize: 16,
            fontFamily: "Arial",
            align: "right",
            fontWeight: "bold",
          });
          const enText = new Text({ text: enName, style: enStyle });
          enText.anchor.set(1, -1); // top-right
          enText.x = 24;
          enText.y = ruText.y + 2;
          labelGroup.addChild(enText);
        }

        drawn++;
      });

      console.log(`✅ Отрисовано станций: ${drawn}/${stationsRu.length}`);
      console.groupEnd();

      // гарантируем, что подписи горизонтальны
      this.updateLabelOrientation();

      /* ─── Center the view on the first station (once, keep current zoom) ─── */
      if (!this.stationZoomed && this.stationContainer.children.length) {
        const firstNode = this.stationContainer.children[0];
        if (firstNode) {
          // мировые координаты станции (учитывают поворот/масштаб routeGroup)
          const b = firstNode.getBounds();
          const worldPos = { x: b.x + b.width / 2, y: b.y + b.height / 2 };

          // плавно перемещаем камеру к станции, масштаб НЕ меняем
          this.viewport.animate({
            time: 600,
            position: { x: worldPos.x, y: worldPos.y },
            easing: "easeInOutSine",
          });

          this.viewport.once("moved-end", () => {
            this.applyPanClamp();
          });
          this.stationZoomed = true;
          console.log(
            `%c[centerToStation] world=(${worldPos.x.toFixed(
              1
            )},${worldPos.y.toFixed(1)})`,
            "color: plum"
          );
        }
      }
    },

    // Поддерживаем горизонтальную ориентацию подписей
    updateLabelOrientation() {
      if (!this.stationContainer || !this.routeGroup) return;

      // угол для компенсации
      const angle = -this.routeGroup.rotation;

      this.stationContainer.children.forEach((node) => {
        node.children.forEach((child) => {
          // Graphics‑объект (кружок) оставляем без изменений
          if (child instanceof PIXI.Text) {
            // просто выравниваем текст
            child.rotation = angle;
          } else if (child instanceof PIXI.Container) {
            child.rotation = angle;

            if (child._ox !== undefined && child._oy !== undefined) {
              const cosA = Math.cos(angle);
              const sinA = Math.sin(angle);
              child.x = child._ox * cosA + child._oy * sinA;
              child.y = -child._ox * sinA + child._oy * cosA;
            }
          }
        });
      });
    },

    autoFitViewport() {
      if (this.viewportAdjusted || !this.viewport || !this.routeGroup) return;

      // Глобальные габариты с учётом поворота/скейла
      const b = this.routeGroup.getBounds();

      const sw = this.viewport.screenWidth;
      const sh = this.viewport.screenHeight;

      // Подбираем масштаб так, чтобы занять ~90% экрана
      const fill = 0.9;
      let scale = Math.min(sw / b.width, sh / b.height) * fill;

      // Учитываем scale_min / scale_max, если есть
      if (this.routeScaleMin !== null)
        scale = Math.max(scale, this.routeScaleMin / 10);
      if (this.routeScaleMax !== null)
        scale = Math.min(scale, this.routeScaleMax / 10);

      const cx = b.x + b.width / 2;
      const cy = b.y + b.height / 2;

      // setZoom(scale, worldX, worldY) — Pixi‑Viewport v4/v8 API
      this.viewport.setZoom(scale, cx, cy);
      this.viewportAdjusted = true;

      // выставляем границы пэннинга
      this.applyPanClamp();

      console.log(
        `%c[autoFitViewport] scale=${scale.toFixed(2)}, center=(${cx.toFixed(
          1
        )},${cy.toFixed(1)})`,
        "color: mediumspringgreen"
      );
    },
    // Ограничиваем панорамирование так, чтобы пользователь
    // не мог «уехать» далеко от маршрута, динамически распределяя «воздух»
    // в зависимости от угла поворота (чтобы слева/сверху не оставалось пустоты).
    applyPanClamp() {
      if (!this.viewport || !this.routeGroup) return;

      // Bounding‑box маршрута (учитывает rotation/scale)
      const b = this.routeGroup.getBounds();

      // Базовый «воздух» 10% от большей стороны
      const pad = Math.max(b.width, b.height) * 0.1;

      // Направление маршрута (радианы) — используем, чтобы перераспределить паддинги
      const ang = this.routeGroup.rotation; // радианы
      const cos = Math.cos(ang);
      const sin = Math.sin(ang);

      // Распределяем паддинги: если cos > 0 → маршрут «смотрит» вправо,
      // поэтому больше воздуха добавляем справа, меньше слева и т.д.
      const padLeft = (pad * (1 - cos)) / 2;
      const padRight = (pad * (1 + cos)) / 2;
      const padTop = (pad * (1 - sin)) / 2;
      const padBottom = (pad * (1 + sin)) / 2;

      this.viewport.clamp({
        left: b.x - padLeft,
        right: b.x + b.width + padRight,
        top: b.y - padTop,
        bottom: b.y + b.height + padBottom,
        underflow: "center",
      });

      console.log(
        `%c[applyPanClamp] angle=${((ang * 180) / Math.PI).toFixed(
          1
        )}°, pads L${padLeft.toFixed(1)} R${padRight.toFixed(
          1
        )} T${padTop.toFixed(1)} B${padBottom.toFixed(1)}`,
        "color: orange"
      );
    },
    // ───────────────────────── polling helpers ─────────────────────────
    startRoutePolling() {
      if (this.routePollTimer) return; // already polling
      this.routePollTimer = setInterval(() => {
        console.debug("route poll → fetchContext() + fetchRoute()");
        this.fetchContext().finally(() => this.fetchRoute());
      }, 3000);
    },
    stopRoutePolling() {
      if (this.routePollTimer) {
        clearInterval(this.routePollTimer);
        this.routePollTimer = null;
      }
    },

    // Получаем контекст геолокации, чтобы определить актуальный routeId
    async fetchContext() {
      try {
        const res = await fetch(`${GEO_URL}/v1/geolocation/context`);
        const data = await res.json();

        const ctxRouteId =
          data?.routeId ?? data?.routeProgress?.routeId ?? null;

        if (ctxRouteId) {
          // если routeId изменился – обновим, иначе оставим прежний
          const changed = ctxRouteId !== this.routeId;
          this.routeId = ctxRouteId;

          if (changed) {
            this.fetchRoute();
          } else if (!changed && !this.routeGraphics) {
            // если маршрут ещё не нарисован (первый рендер), всё равно подтянем данные
            this.fetchRoute();
          }
        } else {
          console.warn("fetchContext: routeId не получен из контекста");
        }
      } catch (err) {
        console.error("Ошибка при получении контекста геолокации:", err);
      }
    },

    // Получаем все данные маршрута и выводим их в консоль
    async fetchRoute() {
      if (!this.routeId) return;
      if (this.routePollTimer) this.attemptCount += 1;
      else this.attemptCount = 0;
      this.attemptCount += 1;
      console.log(
        `%cFetchRoute attempt #${this.attemptCount} (routeId=${this.routeId})`,
        "color: lightgreen;font-weight:bold;"
      );
      if (!this.routeId) {
        console.warn("fetchRoute: routeId ещё не известен. Ждём контекст…");
        return;
      }

      try {
        let gotPath = false;
        const [route, stationsRu, stationsEn, sights] = await Promise.all([
          fetch(`${API_URL}/route/${this.routeId}`).then((r) => r.json()),
          fetch(`${API_URL}/route/${this.routeId}/station`).then((r) =>
            r.json()
          ),
          fetch(`${API_URL}/route/${this.routeId}/station?lang=en`).then((r) =>
            r.json()
          ),
          fetch(`${API_URL}/route/${this.routeId}/sight`).then((r) => r.json()),
        ]);

        console.log(
          `%cFetchRoute attempt #${this.attemptCount}`,
          "color: lightgreen; font-weight: bold;"
        );
        console.log("  Route →", route);
        console.log("  Stations (ru) →", stationsRu.length);
        console.log("  Stations (en) →", stationsEn.length);
        console.log("  Sights →", sights.length);
        if (typeof route.scale_min === "number")
          this.routeScaleMin = route.scale_min;
        if (typeof route.scale_max === "number")
          this.routeScaleMax = route.scale_max;
        if (typeof route.center_latitude === "number")
          this.centerLatitude = route.center_latitude;
        if (typeof route.center_longitude === "number")
          this.centerLongitude = route.center_longitude;
        // обновим ограничения масштабирования в viewport
        this.updateViewportClampZoom();

        if (
          route?.path &&
          Array.isArray(route.path) &&
          route.path.length >= 2
        ) {
          this.drawRoute(route.path);
          this.drawStations(stationsRu, stationsEn);
          if (typeof route.rotate === "number") {
            // this.routeRotate = route.rotate;
            this.routeGroup.rotation = (route.rotate * Math.PI) / 180;
            this.updateLabelOrientation(); // подписи остаются горизонтальными
          }
          // после линии, станций и (возможного) поворота автоматически подгоняем viewport
          this.autoFitViewport();
          gotPath = true;
        } else {
          console.warn(
            "fetchRoute: path отсутствует или слишком короткий. Повторяем запрос…"
          );
        }
        if (gotPath) {
          this.stopRoutePolling();
          this.attemptCount = 0;
        } else {
          this.startRoutePolling();
        }
      } catch (err) {
        console.error("Ошибка при получении данных маршрута:", err);
        this.startRoutePolling();
        if (this.attemptCount > 1e6) this.attemptCount = 0;
      }
    },
  },
  beforeUnmount() {
    this.stopRoutePolling();
  },
};
</script>
