// App.jsx — сборка сайта + переключение разделов (клик-тру)

// Плавная анимированная прокрутка с мягким easing (вместо резкого нативного smooth).
function animateScrollTo(el, targetTop, opts = {}) {
  if (!el) return;
  if (el.__scrollRAF) cancelAnimationFrame(el.__scrollRAF);
  const start = el.scrollTop;
  const dist = Math.max(0, targetTop) - start;
  if (Math.abs(dist) < 2) { el.scrollTop = Math.max(0, targetTop); return; }
  // длительность зависит от расстояния: спокойно, но не затянуто
  const dur = opts.duration != null
    ? opts.duration
    : Math.min(1150, Math.max(620, Math.abs(dist) * 0.6));
  // easeInOutCubic — мягкий разгон и плавное замедление в конце
  const ease = (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2);
  const t0 = performance.now();
  const step = (now) => {
    const p = Math.min(1, (now - t0) / dur);
    el.scrollTop = start + dist * ease(p);
    if (p < 1) { el.__scrollRAF = requestAnimationFrame(step); }
    else { el.__scrollRAF = null; }
  };
  // отменять анимацию, если пользователь сам начал прокручивать
  const cancel = () => { if (el.__scrollRAF) { cancelAnimationFrame(el.__scrollRAF); el.__scrollRAF = null; } cleanup(); };
  const cleanup = () => { el.removeEventListener("wheel", cancel); el.removeEventListener("touchstart", cancel); };
  el.addEventListener("wheel", cancel, { passive: true });
  el.addEventListener("touchstart", cancel, { passive: true });
  el.__scrollRAF = requestAnimationFrame(step);
}

function setScrollTopInstant(el, top) {
  if (!el) return;
  if (el.__scrollRAF) {
    cancelAnimationFrame(el.__scrollRAF);
    el.__scrollRAF = null;
  }
  const previousBehavior = el.style.scrollBehavior;
  el.style.scrollBehavior = "auto";
  el.scrollTop = Math.max(0, top);
  requestAnimationFrame(() => {
    el.style.scrollBehavior = previousBehavior;
  });
}

function getAnchorOffset(scroller) {
  const header = scroller ? scroller.querySelector("header") : null;
  const headerHeight = header ? header.getBoundingClientRect().height : 72;
  const compact = window.matchMedia && window.matchMedia("(max-width: 720px)").matches;
  return Math.round(headerHeight + (compact ? 18 : 24));
}

function getAnchorTarget(scroller, anchor) {
  if (!scroller || !anchor) return null;
  const safeAnchor = window.CSS && CSS.escape ? CSS.escape(anchor) : anchor.replace(/"/g, '\\"');
  const node = scroller.querySelector("#" + safeAnchor);
  if (!node) return null;
  return node.matches("section") ? (node.querySelector("[data-anchor-content]") || node) : node;
}

function scrollToAnchor(scroller, anchor, opts = {}) {
  if (!scroller || !anchor) return;

  let tries = 0, lastTop = -1, stable = 0;
  const targetTop = () => {
    const target = getAnchorTarget(scroller, anchor);
    if (!target) return null;

    const style = window.getComputedStyle(target);
    const targetOffset = parseFloat(style.getPropertyValue("--anchor-offset"));
    const offset = Number.isFinite(targetOffset) && targetOffset > 0 ? targetOffset : getAnchorOffset(scroller);
    const raw = target.getBoundingClientRect().top - scroller.getBoundingClientRect().top + scroller.scrollTop - offset;
    const maxScroll = scroller.scrollHeight - scroller.clientHeight;
    return Math.max(0, Math.min(raw, maxScroll));
  };

  const go = () => {
    if (opts.isCurrent && !opts.isCurrent()) return;
    const top = targetTop();
    if (top !== null) {
      if (Math.abs(top - lastTop) <= 1) stable++;
      else { stable = 0; lastTop = top; }
      if (stable >= 2 || tries > 50) {
        if (opts.instant) setScrollTopInstant(scroller, top);
        else {
          const duration = opts.duration == null ? undefined : opts.duration;
          animateScrollTo(scroller, top, { duration });
          const correctionDelay = (duration || Math.min(1150, Math.max(620, Math.abs(top - scroller.scrollTop) * 0.6))) + 90;
          setTimeout(() => {
            if (opts.isCurrent && !opts.isCurrent()) return;
            const correctedTop = targetTop();
            if (correctedTop !== null && Math.abs(correctedTop - scroller.scrollTop) > 3) {
              setScrollTopInstant(scroller, correctedTop);
            }
          }, correctionDelay);
        }
        return;
      }
    }
    if (tries++ < 60) setTimeout(go, 30);
  };

  setTimeout(go, opts.delay == null ? 50 : opts.delay);
}

function HomePage({ onNav, newsHidden, radio }) {
  return (
    <div>
      <Hero onNav={onNav} radio={radio} />
      <Ministries onNav={onNav} />
      <WhereWeServe />
      <News onNav={onNav} hidden={newsHidden} />
      <SupportCTA onNav={onNav} />
      <HomeContact onNav={onNav} />
    </div>
  );
}

function MobileFloatingRadio({ radio, visible }) {
  if (!radio) return null;
  const active = radio.playing || radio.loading;
  const label = radio.loading ? "Подключаем" : active ? "Эфир" : "Радио";

  return (
    <div className={`mobile-floating-radio${visible ? " is-visible" : ""}`}>
      <button type="button" onClick={radio.toggle}
        aria-label={active ? "Остановить радио" : "Слушать радио"}
        className={`mobile-floating-radio-button${active ? " is-active" : ""}`}>
        <span className="mobile-floating-radio-icon">
          <Icon name={active ? "pause" : "radio"} size={18} color="var(--cream)" strokeWidth={2} />
        </span>
        <span className="mobile-floating-radio-text">
          <span>{label}</span>
          <span className="mobile-floating-radio-status">
            <span aria-hidden="true" />
            {radio.playing ? "Прямой эфир" : radio.loading ? "Идет запуск" : "Слушать"}
          </span>
        </span>
      </button>
    </div>
  );
}

const RADIO_STREAM_SRC = "/api/radio-stream";

function useRadioController() {
  const audioRef = React.useRef(null);
  const [state, setState] = React.useState("idle");
  const [volume, setVolume] = React.useState(1);
  const [muted, setMuted] = React.useState(false);
  const playing = state === "playing";
  const loading = state === "loading";
  const errored = state === "error";
  const expanded = loading || playing;
  const effectiveVolume = muted ? 0 : volume;

  const toggle = React.useCallback(async () => {
    const audio = audioRef.current;
    if (!audio) return;

    if (playing || loading) {
      audio.pause();
      setState("idle");
      return;
    }

    try {
      setState("loading");
      if (errored) audio.load();
      await audio.play();
      setState("playing");
    } catch (e) {
      setState("error");
    }
  }, [playing, loading, errored]);

  const changeVolume = React.useCallback((event) => {
    const next = Number(event.target.value) / 100;
    setVolume(next);
    setMuted(next === 0);
  }, []);

  const toggleMute = React.useCallback(() => {
    setMuted((current) => {
      const next = !current;
      if (next && volume === 0) setVolume(1);
      return next;
    });
  }, [volume]);

  React.useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;
    const onPlaying = () => setState("playing");
    const onWaiting = () => setState((current) => current === "idle" ? "idle" : "loading");
    const onPause = () => setState((current) => current === "error" ? "error" : "idle");
    const onError = () => setState("error");
    audio.addEventListener("playing", onPlaying);
    audio.addEventListener("waiting", onWaiting);
    audio.addEventListener("stalled", onWaiting);
    audio.addEventListener("pause", onPause);
    audio.addEventListener("error", onError);
    return () => {
      audio.removeEventListener("playing", onPlaying);
      audio.removeEventListener("waiting", onWaiting);
      audio.removeEventListener("stalled", onWaiting);
      audio.removeEventListener("pause", onPause);
      audio.removeEventListener("error", onError);
    };
  }, []);

  React.useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;
    audio.volume = effectiveVolume;
    audio.muted = muted;
  }, [effectiveVolume, muted]);

  const audio = <audio ref={audioRef} src={RADIO_STREAM_SRC} preload="none" playsInline style={{ display: "none" }} />;

  return {
    audio,
    changeVolume,
    effectiveVolume,
    errored,
    expanded,
    loading,
    muted,
    playing,
    state,
    toggle,
    toggleMute,
    volume,
  };
}

async function fetchNewsSettings() {
  const res = await fetch("/api/globals/news-settings", {
    headers: { Accept: "application/json" },
  });
  if (!res.ok) throw new Error("Не удалось загрузить настройки новостей");
  const data = await res.json();
  return {
    hideNews: data.hideNews !== false,
  };
}

const DEFAULT_ROUTE = { active: "Главная", anchor: null, newsId: null };

function routeWindow() {
  try {
    if (window.parent && window.parent !== window && window.parent.location.origin === window.location.origin) {
      return window.parent;
    }
  } catch (e) {}
  return window;
}

function isEmbeddedRouteWindow(rw) {
  return rw && rw !== window;
}

function normalizeRoute(route) {
  return {
    active: route && route.active ? route.active : "Главная",
    anchor: route && route.anchor ? route.anchor : null,
    newsId: route && route.newsId ? route.newsId : null,
  };
}

function routeFromHash() {
  try {
    const rw = routeWindow();
    const hash = (isEmbeddedRouteWindow(rw) ? rw.location.hash : window.location.hash).replace(/^#/, "");
    if (!hash) return DEFAULT_ROUTE;

    const params = new URLSearchParams(hash);
    return {
      active: params.get("tab") || "Главная",
      anchor: params.get("anchor") || null,
      newsId: params.get("news") || null,
    };
  } catch (e) {
    return DEFAULT_ROUTE;
  }
}

function hashFromRoute(route) {
  const rw = routeWindow();
  if (!route.newsId && !route.anchor && (!route.active || route.active === "Главная")) return rw.location.pathname + rw.location.search;

  const params = new URLSearchParams();
  params.set("tab", route.active || "Главная");
  if (route.anchor) params.set("anchor", route.anchor);
  if (route.newsId) params.set("news", route.newsId);
  return "#" + params.toString();
}

function writeRouteHistory(method, state, route) {
  const rw = routeWindow();
  try {
    rw.history[method](state, "", hashFromRoute(route));
  } catch (e) {}
}

function RouteLoader({ visible }) {
  return (
    <div className={`route-loader${visible ? " is-visible" : ""}`} aria-hidden={!visible}>
      <div className="route-loader-mark">
        <span />
      </div>
    </div>
  );
}

function App() {
  const initialRoute = React.useMemo(() => routeFromHash(), []);
  const [route, setRoute] = React.useState(normalizeRoute(initialRoute));
  const active = route.active;
  const anchor = route.anchor;
  const newsId = route.newsId;
  const [navNonce, setNavNonce] = React.useState(0);
  const [scrolled, setScrolled] = React.useState(false);
  const [pastHero, setPastHero] = React.useState(false);
  const [newsHidden, setNewsHidden] = React.useState(true);
  const [routeLoading, setRouteLoading] = React.useState(false);
  const scrollerRef = React.useRef(null);
  const pendingRestoreRef = React.useRef(null);
  const routeRef = React.useRef(normalizeRoute(initialRoute));
  const navTokenRef = React.useRef(0);
  const navLoadingTimerRef = React.useRef(null);
  const radio = useRadioController();

  const finishRouteLoading = (token, delay = 0) => {
    window.setTimeout(() => {
      if (navTokenRef.current !== token) return;
      if (navLoadingTimerRef.current) {
        clearTimeout(navLoadingTimerRef.current);
        navLoadingTimerRef.current = null;
      }
      setRouteLoading(false);
    }, delay);
  };

  const onNav = (item, target = null) => {
    if (newsHidden && item === "Новости") {
      applyRoute({ active: "Главная", anchor: null, newsId: null });
      return;
    }
    if (item === "Где мы служим") {
      applyRoute({ active: "Главная", anchor: "where-we-serve", newsId: null });
      return;
    }
    applyRoute({ active: item, anchor: target, newsId: null });
  };

  // открыть детальную страницу новости (или вернуться к списку при null)
  const openNews = (id) => {
    if (newsHidden) {
      applyRoute({ active: "Главная", anchor: null, newsId: null });
      return;
    }
    applyRoute({ active: "Новости", anchor: null, newsId: id });
  };

  // применить маршрут к состоянию + (опц.) записать в историю браузера
  const applyRoute = (route, push = true) => {
    const nextRoute = normalizeRoute(route);
    const token = ++navTokenRef.current;
    const scroller = scrollerRef.current;
    const currentRoute = routeRef.current;
    const sameRoute = currentRoute.active === nextRoute.active &&
      (currentRoute.anchor || null) === (nextRoute.anchor || null) &&
      (currentRoute.newsId || null) === (nextRoute.newsId || null);

    if (scroller && scroller.__scrollRAF) {
      cancelAnimationFrame(scroller.__scrollRAF);
      scroller.__scrollRAF = null;
    }
    if (navLoadingTimerRef.current) clearTimeout(navLoadingTimerRef.current);
    navLoadingTimerRef.current = setTimeout(() => {
      if (navTokenRef.current === token) setRouteLoading(true);
    }, 140);
    if (push) {
      // сохраняем текущую позицию прокрутки в активной записи истории
      writeRouteHistory("replaceState", { ...currentRoute, scroll: scroller ? scroller.scrollTop : 0 }, currentRoute);
    }

    if (scroller && !nextRoute.anchor && !sameRoute) {
      setScrollTopInstant(scroller, 0);
    }

    routeRef.current = nextRoute;
    setRoute(nextRoute);
    setNavNonce((n) => n + 1);
    if (push) {
      writeRouteHistory("pushState", { ...nextRoute, scroll: 0 }, nextRoute);
    }
    if (sameRoute && !nextRoute.anchor) finishRouteLoading(token, 0);
  };

  // системные кнопки браузера «Назад/Вперёд»
  React.useEffect(() => {
    const rw = routeWindow();
    if (!rw.history.state) {
      writeRouteHistory("replaceState", { ...initialRoute, scroll: 0 }, initialRoute);
    }
    const onPop = (e) => {
      const s = normalizeRoute(e.state || routeFromHash());
      // восстановить сохранённую позицию прокрутки этой записи
      pendingRestoreRef.current = (e.state && typeof e.state.scroll === "number") ? e.state.scroll : 0;
      applyRoute(s, false);
    };
    rw.addEventListener("popstate", onPop);
    return () => rw.removeEventListener("popstate", onPop);
  }, []);

  const goBack = () => {
    // системная история браузера
    try { routeWindow().history.back(); } catch (e) { applyRoute({ active: "Главная", anchor: null, newsId: null }, false); }
  };

  // навигация доступна для PageHero без проброса через все страницы
  React.useEffect(() => {
    window.__navBack = goBack;
    window.__navHome = () => onNav("Главная");
    window.__navTo = onNav;
    window.__openNews = openNews;
  });

  React.useEffect(() => {
    let cancelled = false;

    fetchNewsSettings()
      .then((settings) => {
        if (cancelled) return;
        setNewsHidden(settings.hideNews);
      })
      .catch(() => {
        if (!cancelled) setNewsHidden(true);
      });

    return () => {
      cancelled = true;
    };
  }, []);

  React.useEffect(() => {
    if (newsHidden && routeRef.current.active === "Новости") {
      applyRoute({ active: "Главная", anchor: null, newsId: null }, false);
    }
  }, [newsHidden]);

  React.useEffect(() => {
    const syncRouteFromHash = () => {
      const route = routeFromHash();
      const current = { active, anchor: anchor || null, newsId: newsId || null };
      const next = { active: route.active, anchor: route.anchor || null, newsId: route.newsId || null };

      if (current.active !== next.active || current.anchor !== next.anchor || current.newsId !== next.newsId) {
        applyRoute(next, false);
        return;
      }

      if (next.anchor) {
        setNavNonce((n) => n + 1);
      }
    };

    const rw = routeWindow();
    rw.addEventListener("hashchange", syncRouteFromHash);
    return () => {
      rw.removeEventListener("hashchange", syncRouteFromHash);
    };
  }, [active, anchor, newsId]);

  // прокрутка при смене раздела: к якорю / к восстановленной позиции / наверх
  React.useLayoutEffect(() => {
    const el = scrollerRef.current;
    if (!el) return;
    // восстановление позиции при «Назад/Вперёд» — приоритетнее
    if (pendingRestoreRef.current != null) {
      const target = pendingRestoreRef.current;
      pendingRestoreRef.current = null;
      let tries = 0;
      const restore = () => {
        const maxScroll = el.scrollHeight - el.clientHeight;
        if (maxScroll >= target - 2 || tries > 40) { setScrollTopInstant(el, Math.min(target, maxScroll)); }
        else { tries++; setTimeout(restore, 25); }
      };
      restore();
      return;
    }
    if (anchor) {
      // ждём, пока макет «устаканится» (цель перестанет смещаться), затем плавно скроллим один раз
      const token = navTokenRef.current;
      scrollToAnchor(el, anchor, {
        delay: 20,
        duration: 420,
        isCurrent: () => navTokenRef.current === token,
      });
      finishRouteLoading(token, 620);
    } else {
      setScrollTopInstant(el, 0);
      finishRouteLoading(navTokenRef.current, 80);
    }
  }, [active, anchor, navNonce]);

  React.useEffect(() => {
    const el = scrollerRef.current;
    if (!el) return;
    let saveRAF = null;
    const onScroll = () => {
      setScrolled(el.scrollTop > 40);
      setPastHero(el.scrollTop > el.clientHeight * 0.72);
      if (saveRAF) return;
      saveRAF = requestAnimationFrame(() => {
        saveRAF = null;
        const currentRoute = routeRef.current;
        writeRouteHistory("replaceState", { ...currentRoute, scroll: el.scrollTop }, currentRoute);
      });
    };
    el.addEventListener("scroll", onScroll);
    onScroll();
    return () => {
      el.removeEventListener("scroll", onScroll);
      if (saveRAF) cancelAnimationFrame(saveRAF);
    };
  }, [active, anchor, newsId]);

  // главная — шапка поверх фото; на внутренних — обычная кремовая
  const onPhoto = active === "Главная";
  const activeNav = active === "Главная" && anchor === "where-we-serve" ? "Где мы служим" : active;
  const showMobileFloatingRadio = active !== "Главная" || pastHero;

  // плавное появление секций при скролле (один общий наблюдатель на весь сайт)
  React.useEffect(() => {
    const el = scrollerRef.current;
    if (!el) return;
    const reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    let io;
    const t = setTimeout(() => {
      if (!reduce) {
        const targets = el.querySelectorAll("section, footer");
        io = new IntersectionObserver((entries) => {
          entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); } });
        }, { root: el, threshold: 0.12, rootMargin: "0px 0px -8% 0px" });
        targets.forEach((s) => {
          const r = s.getBoundingClientRect();
          if (r.top < el.clientHeight * 0.92) s.classList.add("in");
          else { s.classList.add("reveal"); io.observe(s); }
        });
      }
      // пошаговое появление нумерованных этапов — на scroll-листенере (надёжно)
      const steps = [...el.querySelectorAll(".creed-row")];
      steps.forEach((s) => s.classList.add("creed-step"));
      const revealSteps = () => {
        const er = el.getBoundingClientRect();
        steps.forEach((s) => {
          if (s.classList.contains("in")) return;
          const r = s.getBoundingClientRect();
          if (r.top < er.top + el.clientHeight * 0.88) s.classList.add("in");
        });
      };
      el.addEventListener("scroll", revealSteps, { passive: true });
      revealSteps();
      el.__revealSteps = revealSteps;
    }, 80);
    return () => {
      clearTimeout(t);
      if (io) io.disconnect();
      if (el.__revealSteps) { el.removeEventListener("scroll", el.__revealSteps); el.__revealSteps = null; }
    };
  }, [active, newsId, navNonce]);

  let page;
  if (active === "Главная") page = <HomePage onNav={onNav} newsHidden={newsHidden} radio={radio} />;
  else if (active === "О миссии") page = <AboutPage />;
  else if (active === "Контакты") page = <ContactsPage />;
  else if (active === "Служение") page = <MinistryPage onNav={onNav} />;
  else if (active === "Медиа") page = <MediaPage />;
  else if (active === "Подписка") page = <SubscriptionPage />;
  else if (active === "Поддержать") page = <SupportPage onNav={onNav} />;
  else if (active === "Вероучение") page = <AboutPage />;
  else if (active === "Новости" && !newsHidden) page = newsId
    ? <NewsArticle id={newsId} onOpen={openNews} onBack={() => openNews(null)} />
    : <NewsPage onOpen={openNews} />;
  else page = <Placeholder title={active} />;

  return (
    <div ref={scrollerRef} data-scroller style={{ height: "100vh", overflowY: "auto", overflowX: "hidden",
      background: "var(--cream)", scrollBehavior: "auto" }}>
      {radio.audio}
      <Header active={activeNav} onNav={onNav} scrolled={scrolled} onPhoto={onPhoto} forceSolid={Boolean(newsId)} newsHidden={newsHidden} radio={radio} />
      {page}
      <RouteLoader visible={routeLoading} />
      <MobileFloatingRadio radio={radio} visible={showMobileFloatingRadio} />
      <Footer onNav={onNav} newsHidden={newsHidden} />
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
Object.assign(window, { scrollToAnchor, fetchNewsSettings, MobileFloatingRadio, __anchorVersion: "20260612-nav3" });
