/* Variant B — «Кинохроника»
   Видеофон Читы + цветовая засветка маршрута, фото-узлы.
   Точки распределены по полному внешнему кругу.
   Данные — window.ORBIT_ROUTES (из БД), подписи — window.ORBIT_I18N. */

// Seamless loop with ONE active video at a time.
// We snapshot the last frame onto a canvas right before the seek-to-0,
// then crossfade the canvas out as the video resumes from frame 0.
// Only one decoder is ever running → far cheaper than dual-video crossfade.
const SeamlessVideo = ({ src, poster, className }) => {
  const { useEffect, useRef } = React;
  const videoRef = useRef(null);
  const canvasRef = useRef(null);
  const fade = 0.7;

  useEffect(() => {
    const v = videoRef.current;
    const c = canvasRef.current;
    if (!v || !c) return;
    let handing = false;
    let timer = null;

    const onTime = () => {
      const dur = v.duration;
      if (!isFinite(dur) || dur <= 0 || handing) return;
      if (dur - v.currentTime <= 0.25) {
        handing = true;
        // 1. Snapshot the current frame.
        const w = v.videoWidth || 1280;
        const h = v.videoHeight || 720;
        if (c.width !== w) c.width = w;
        if (c.height !== h) c.height = h;
        const ctx = c.getContext("2d");
        try { ctx.drawImage(v, 0, 0, w, h); } catch (_) {}
        // 2. Show the frozen frame instantly (cover the seek gap).
        c.style.transition = "none";
        c.style.opacity = "1";
        // 3. Seek + resume.
        try { v.currentTime = 0; } catch (_) {}
        v.play().catch(() => {});
        // 4. After the video reports a new frame, fade the canvas out.
        const startFade = () => {
          requestAnimationFrame(() => {
            c.style.transition = `opacity ${fade}s linear`;
            c.style.opacity = "0";
          });
        };
        if ("requestVideoFrameCallback" in v) {
          v.requestVideoFrameCallback(startFade);
        } else {
          v.addEventListener("seeked", startFade, { once: true });
        }
        timer = setTimeout(() => { handing = false; }, fade * 1000 + 200);
      }
    };
    v.addEventListener("timeupdate", onTime);
    return () => {
      v.removeEventListener("timeupdate", onTime);
      if (timer) clearTimeout(timer);
    };
  }, []);

  return (
    <>
      <video
        ref={videoRef}
        className={className}
        src={src}
        poster={poster}
        autoPlay
        muted
        playsInline
        preload="auto"
        style={{ position: "absolute", inset: 0 }}
      />
      <canvas
        ref={canvasRef}
        aria-hidden="true"
        style={{
          position: "absolute", inset: 0,
          width: "100%", height: "100%",
          objectFit: "cover",
          opacity: 0,
          pointerEvents: "none",
          willChange: "opacity",
        }}
      />
    </>
  );
};

const OrbitCinema = () => {
  const { useMemo, useState } = React;
  const routes = window.ORBIT_ROUTES || [];
  const I = window.ORBIT_I18N || {};
  const t = (k, d) => (I[k] != null && I[k] !== "" ? I[k] : d);
  const { angle, activeId, setActiveId, hoverId, setHoverId, setTarget } = window.useOrbit({ speed: 0.15 });
  // Выбранный объект (точка маршрута). Показывает справа карточку с аудиогидом.
  const [stop, setStop] = useState(null);
  // Клик по линии: орбита НЕ замирает — она продолжает непрерывно вращаться, а
  // веер точек активной линии вращается вместе с её узлом. Повторный клик — сброс.
  const selectLine = (id) => {
    setStop(null);
    setActiveId(id);
    setTarget(null); // держим орбиту в постоянном движении
  };
  const reset = () => { setStop(null); setActiveId(null); setTarget(null); };

  // Внешнее управление: интро-чипы линий вызывают window.orbitSelect(routeId),
  // чтобы открыть линию на орбите (с «перестройкой»). Держим selectLine в ref,
  // чтобы window.orbitSelect всегда вызывал актуальную версию и работал при
  // КАЖДОМ клике (а не только при первом), и НЕ удаляем его при размонтировании.
  const selectRef = React.useRef(null);
  selectRef.current = selectLine;
  React.useEffect(() => {
    window.orbitSelect = (rid) => {
      if (selectRef.current && routes.some((r) => r.id === rid)) selectRef.current(rid);
    };
    // подхватываем «отложенный выбор», если чип кликнули до монтирования орбиты
    if (window.__pendingOrbit) {
      const p = window.__pendingOrbit;
      window.__pendingOrbit = null;
      window.orbitSelect(p);
    }
  }, []);

  // На мобиле карточку описания выносим ВНИЗ через портал в #orbit-card-slot
  // (вне scale-холста орбиты): орбита занимает всю ширину, а текст читаемый.
  const [isMobile, setIsMobile] = useState(
    typeof window !== "undefined" && window.matchMedia
      ? window.matchMedia("(max-width: 760px)").matches
      : false
  );
  const [cardSlot, setCardSlot] = useState(null);
  React.useEffect(() => {
    setCardSlot(document.getElementById("orbit-card-slot"));
    if (!window.matchMedia) return;
    const mq = window.matchMedia("(max-width: 760px)");
    const onChange = () => setIsMobile(mq.matches);
    onChange();
    mq.addEventListener ? mq.addEventListener("change", onChange) : mq.addListener(onChange);
    return () => {
      mq.removeEventListener ? mq.removeEventListener("change", onChange) : mq.removeListener(onChange);
    };
  }, []);

  const CX = 360;
  const CY = 372;
  const R_INNER = 170;
  const R_OUTER = 280;
  const LABEL_R = 44;       // label offset OUTWARD from node center
  const N = routes.length;
  const total = String(N).padStart(2, "0");

  const active = routes.find((r) => r.id === activeId);
  const activeIdx = routes.findIndex((r) => r.id === activeId);

  // Inner route nodes — always orbiting smoothly (no snap).
  const innerPositions = useMemo(() => {
    return routes.map((r, i) => {
      const a = (i / N) * 360 + angle - 90;
      const p = window.polar(a, R_INNER);
      const norm = Math.hypot(p.x, p.y) || 1;
      // Radial outward unit vector — used to anchor the label so it never flips
      return { ...r, angle: a, ...p, dirX: p.x / norm, dirY: p.y / norm };
    });
  }, [angle]);

  // Stops orbit WITH the active node — anchored to its live angle.
  const stops = useMemo(() => {
    if (!active) return [];
    const anchor = (activeIdx / N) * 360 + angle - 90;
    const count = active.stops.length;
    const arc = 220;
    const start = anchor - arc / 2;
    return active.stops.map((s, i) => {
      const tt = count === 1 ? 0.5 : i / (count - 1);
      const a = start + arc * tt;
      return { ...s, angle: a, ...window.polar(a, R_OUTER) };
    });
  }, [active, activeIdx, angle]);

  // Навигация стрелками: ◀▶ по маршрутам (когда открыта карточка линии) и по
  // объектам (точкам активного маршрута, когда открыта карточка объекта). С зацикливанием.
  const goRoute = (dir) => {
    if (!N) return;
    const cur = activeIdx < 0 ? 0 : activeIdx;
    selectLine(routes[(cur + dir + N) % N].id);
  };
  const goStop = (dir) => {
    if (!active || !active.stops || !active.stops.length) return;
    const list = active.stops;
    let idx = stop ? list.findIndex((s) => s.id === stop.id) : 0;
    if (idx < 0) idx = 0;
    setStop(list[(idx + dir + list.length) % list.length]);
  };
  // Те же переходы с клавиатуры ←/→ (если открыта карточка).
  React.useEffect(() => {
    const onKey = (e) => {
      if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
      const el = document.activeElement;
      if (el && /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName)) return;
      const dir = e.key === "ArrowRight" ? 1 : -1;
      if (stop) goStop(dir);
      else if (active) goRoute(dir);
      else return;
      e.preventDefault();
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [stop, active, activeIdx]);

  // Пара кнопок-стрелок поверх фото карточки.
  const arrows = (onPrev, onNext, lbl) => (
    <div className="orb2__card-nav">
      <button type="button" className="orb2__card-arrow orb2__card-arrow--prev" aria-label={lbl + " ←"} onClick={(e) => { e.stopPropagation(); onPrev(); }}>‹</button>
      <button type="button" className="orb2__card-arrow orb2__card-arrow--next" aria-label={lbl + " →"} onClick={(e) => { e.stopPropagation(); onNext(); }}>›</button>
    </div>
  );

  const cardEl = (
      <div className={`orb2__card${(active || stop) ? " is-on" : ""}`} onClick={(e) => e.stopPropagation()}>
        {stop && (
          <div className="orb2__card-in" style={{ "--c": active ? active.color : "#7a6a5e" }}>
            <div className="orb2__card-photo">
              <img src={stop.img} alt="" />
              <div className="orb2__card-grad" />
              {active && active.stops && active.stops.length > 1 ? arrows(() => goStop(-1), () => goStop(1), t("obj", "объект")) : null}
              <div className="orb2__card-eyebrow">
                <i />
                <span>{t("obj", "объект")}</span>
              </div>
              <h3 className="orb2__card-title">{stop.name}</h3>
            </div>
            <div className="orb2__card-body">
              {stop.desc ? <p className="orb2__card-text">{stop.desc}</p> : null}
              {stop.audio ? (
                <div style={{ marginTop: "2px", marginBottom: "4px" }}>
                  <div className="orb2__card-meta-k" style={{ marginBottom: "8px" }}>{t("listen", "Аудиогид")}</div>
                  <audio src={stop.audio} controls preload="none" style={{ width: "100%", height: "34px" }} />
                </div>
              ) : (
                <div className="orb2__card-meta-k">{t("no_audio", "Аудиогид скоро")}</div>
              )}
            </div>
          </div>
        )}
        {!stop && active && (
          <div className="orb2__card-in" style={{ "--c": active.color }}>
            <div className="orb2__card-photo">
              <img src={active.hero} alt="" />
              <div className="orb2__card-grad" />
              {N > 1 ? arrows(() => goRoute(-1), () => goRoute(1), t("line", "линия")) : null}
              <div className="orb2__card-eyebrow">
                <i />
                <span>{`${t("line", "линия")} ${active.idx}`}</span>
              </div>
              <div className="orb2__card-kicker">{active.kind}</div>
              <h3 className="orb2__card-title">{active.name}</h3>
            </div>
            <div className="orb2__card-body">
              <p className="orb2__card-text">{active.description}</p>
              <div className="orb2__card-meta">
                <div className="orb2__card-meta-cell">
                  <span className="orb2__card-meta-k">{t("stops", "точек")}</span>
                  <span className="orb2__card-meta-v">{active.stops.length}</span>
                </div>
                <div className="orb2__card-meta-cell">
                  <span className="orb2__card-meta-k">{t("walk", "прогулка")}</span>
                  <span className="orb2__card-meta-v">{active.duration}</span>
                </div>
              </div>
            </div>
          </div>
        )}
        {!stop && !active && (
          <div className="orb2__hint">
            <div className="orb2__hint-cap">{t("map", "Карта линий")}</div>
            <div className="orb2__hint-h" dangerouslySetInnerHTML={{ __html: t("hint_h", "Коснитесь<br>одной из линий") }} />
            <div className="orb2__hint-sub">{t("hint_sub", "Раскроет точки на внешней орбите")}</div>
          </div>
        )}
      </div>
  );

  return (
    <div className="orb2" onClick={reset} style={{ "--accent": active ? active.color : "#7a6a5e" }}>
      {/* Video atmosphere */}
      <div className="orb2__bg" aria-hidden="true">
        <SeamlessVideo
          className="orb2__video"
          src="/static/video/chita-bg.mp4"
          poster="/static/images/Chita_start.jpg"
        />
        <div className="orb2__vignette" />
        <div className="orb2__wash" style={{ background: active ? `radial-gradient(120% 80% at 50% 55%, ${active.color}22 0%, transparent 60%)` : "transparent" }} />
        <div className="orb2__noise" />
      </div>

      {/* Orbit rings */}
      <svg className="orb2__rings" width="980" height="744" aria-hidden="true">
        <defs>
          <radialGradient id="orb2RingFade" cx="50%" cy="50%" r="50%">
            <stop offset="0%" stopColor="rgba(244,237,225,0.0)" />
            <stop offset="60%" stopColor="rgba(244,237,225,0.16)" />
            <stop offset="100%" stopColor="rgba(244,237,225,0.0)" />
          </radialGradient>
        </defs>
        <circle cx={CX} cy={CY} r={R_INNER} fill="none" stroke="rgba(244,237,225,0.12)" strokeWidth="1.2" />
        <circle
          cx={CX}
          cy={CY}
          r={R_OUTER}
          fill="none"
          stroke={active ? active.color : "rgba(244,237,225,0.05)"}
          strokeOpacity={active ? 0.45 : 0.5}
          strokeWidth="1.2"
          strokeDasharray="3 8"
          style={{ transition: "stroke .6s ease" }}
        />
        {/* Subtle orbit tick marks */}
        {[...Array(48)].map((_, i) => {
          const a = (i / 48) * 360;
          const p1 = window.polar(a, R_INNER - 8);
          const p2 = window.polar(a, R_INNER - 2);
          return (
            <line
              key={i}
              x1={CX + p1.x}
              y1={CY + p1.y}
              x2={CX + p2.x}
              y2={CY + p2.y}
              stroke="rgba(244,237,225,0.10)"
              strokeWidth="1"
            />
          );
        })}
        {active &&
          stops.map((s, i) => {
            const inner = innerPositions[activeIdx];
            return (
              <line
                key={i}
                x1={CX + inner.x}
                y1={CY + inner.y}
                x2={CX + s.x}
                y2={CY + s.y}
                stroke={active.color}
                strokeOpacity="0.55"
                strokeWidth="1.2"
                strokeDasharray="2 5"
                style={{ animation: "orb2Thread 8s linear infinite" }}
              />
            );
          })}
      </svg>

      {/* Center */}
      <div className="orb2__core" style={{ left: CX, top: CY }} onClick={(e) => e.stopPropagation()}>
        <div className="orb2__core-pulse orb2__core-pulse--1" />
        <div className="orb2__core-pulse orb2__core-pulse--2" />
        <div className="orb2__core-pulse orb2__core-pulse--3" />
        <div className="orb2__core-disc">
          <img src="/static/images/logo_circle.png" alt="" />
        </div>
        <div className="orb2__core-name">TOURLINE</div>
        <div className="orb2__core-sub">{`${t("city", "Чита")} · ${N} ${t("lines", "линии")}`}</div>
      </div>

      {/* Inner route photo nodes */}
      {innerPositions.map((r) => {
        const isActive = r.id === activeId;
        const isDim = activeId && !isActive;
        return (
          <button
            key={r.id}
            className={`orb2__node${isActive ? " is-active" : ""}${isDim ? " is-dim" : ""}`}
            style={{ left: CX + r.x, top: CY + r.y, "--c": r.color, "--lx": `${r.dirX * 44}px`, "--ly": `${r.dirY * 44}px` }}
            onMouseEnter={() => setHoverId(r.id)}
            onMouseLeave={() => setHoverId(null)}
            onClick={(e) => {
              e.stopPropagation();
              selectLine(isActive ? null : r.id);
            }}
          >
            <span className="orb2__node-glow" />
            <span className="orb2__node-disc">
              <img src={r.hero} alt="" />
              <span className="orb2__node-overlay" />
            </span>
            <span className="orb2__node-idx">{r.idx}</span>
            <span className="orb2__node-tag">
              {r.name}
            </span>
          </button>
        );
      })}

      {/* Stop photo nodes (full outer ring) — кликабельны: открывают объект */}
      {stops.map((s, i) => {
        const isSel = stop && stop.id === s.id;
        return (
          <div
            key={`${activeId}-${i}`}
            className={`orb2__stop${isSel ? " is-sel" : ""}`}
            style={{ left: CX + s.x, top: CY + s.y, "--c": active.color, animationDelay: `${i * 90}ms`, cursor: "pointer" }}
            onClick={(e) => { e.stopPropagation(); setStop(s); }}
          >
            <span className="orb2__stop-disc" style={isSel ? { transform: "scale(1.28)", boxShadow: `0 0 0 3px rgba(10,9,7,.85),0 0 26px ${active.color}` } : null}>
              <img src={s.img} alt="" />
            </span>
            <span className="orb2__stop-cap">
              <span className="orb2__stop-num">{String(i + 1).padStart(2, "0")}</span>
              <span className="orb2__stop-name">{s.name}</span>
            </span>
          </div>
        );
      })}

      {/* Card: десктоп — справа в холсте; мобайл — портал вниз (#orbit-card-slot) */}
      {!isMobile && cardEl}
      {isMobile && cardSlot ? ReactDOM.createPortal(cardEl, cardSlot) : null}

      {/* Chrome */}
      <div className="orb2__chrome orb2__chrome--tl">
        <span className="orb2__chrome-dot" />
        {t("chrome_tl", "TourLine · карта линий")}
      </div>
      <div className="orb2__chrome orb2__chrome--tr">
        {active ? `${active.idx} / ${total}` : t("live", "live")}
      </div>
      <div className="orb2__chrome orb2__chrome--br">52°02′ N · 113°30′ E</div>
    </div>
  );
};

window.OrbitCinema = OrbitCinema;
