// MediaPage.jsx — раздел «Медиа»: журналы (обложки выпусков) + аудио детского хора

function getConfirmedMagazineUrl(value) {
  if (!value || typeof value !== "string") return "";

  try {
    const url = new URL(value);
    if (url.hostname.endsWith("fliphtml5.com")) return "";
    return url.href;
  } catch {
    return "";
  }
}

function getUploadUrl(value) {
  return value && typeof value === "object" && typeof value.url === "string" ? value.url : "";
}

function getPayloadMagazines(category, page = 1, limit = 4) {
  const params = new URLSearchParams({
    depth: "1",
    limit: String(limit),
    page: String(page),
    sort: "sortOrder,-publishedAt",
  });
  params.set("where[status][equals]", "published");
  params.set("where[category][equals]", category);

  return fetch(`/api/magazines?${params.toString()}`, {
    headers: { Accept: "application/json" },
  })
    .then((res) => {
      if (!res.ok) throw new Error("Не удалось загрузить журналы");
      return res.json();
    })
    .then((data) => {
      const docs = Array.isArray(data.docs) ? data.docs : [];
      const items = docs
        .map((item) => ({
          category: item.category,
          href: getConfirmedMagazineUrl(item.externalUrl),
          issue: item.year && item.issueLabel ? `${item.year} · ${item.issueLabel}` : item.issueLabel || "",
          pdfUrl: getUploadUrl(item.pdfFile),
          src: getUploadUrl(item.cover),
          title: item.title,
        }))
        .filter((item) => item.category && item.title && (item.src || item.pdfUrl));
      return {
        hasNextPage: Boolean(data.hasNextPage),
        items,
        nextPage: data.nextPage || page + 1,
        totalDocs: data.totalDocs || items.length,
      };
    });
}

function MagazineReader({ magazine, onClose }) {
  const [pdf, setPdf] = React.useState(null);
  const [page, setPage] = React.useState(1);
  const [pageCount, setPageCount] = React.useState(0);
  const [status, setStatus] = React.useState("loading");
  const [direction, setDirection] = React.useState("next");
  const [singlePage, setSinglePage] = React.useState(false);

  React.useEffect(() => {
    const update = () => setSinglePage(window.innerWidth < 820);
    update();
    window.addEventListener("resize", update);
    return () => window.removeEventListener("resize", update);
  }, []);

  React.useEffect(() => {
    let cancelled = false;
    const pdfjs = window.pdfjsLib;

    setPdf(null);
    setPage(1);
    setPageCount(0);
    setStatus("loading");

    if (!magazine?.pdfUrl || !pdfjs) {
      setStatus("fallback");
      return () => { cancelled = true; };
    }

    if (pdfjs.GlobalWorkerOptions) {
      pdfjs.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js";
    }

    pdfjs.getDocument(magazine.pdfUrl).promise
      .then((doc) => {
        if (cancelled) return;
        setPdf(doc);
        setPageCount(doc.numPages || 0);
        setStatus("ready");
      })
      .catch(() => {
        if (!cancelled) setStatus("fallback");
      });

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

  React.useEffect(() => {
    const previous = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => { document.body.style.overflow = previous; };
  }, []);

  if (!magazine) return null;

  const step = singlePage ? 1 : 2;
  const maxStartPage = singlePage ? pageCount : pageCount % 2 === 0 ? Math.max(1, pageCount - 1) : pageCount;
  const visiblePages = singlePage ? [page] : [page, page + 1].filter((n) => n <= pageCount);
  const canPrev = page > 1;
  const canNext = pageCount ? page < maxStartPage : false;
  const spreadLabel = pageCount
    ? singlePage
      ? `Страница ${page} из ${pageCount}`
      : `Страницы ${page}${visiblePages[1] ? `–${visiblePages[1]}` : ""} из ${pageCount}`
    : "Открываем выпуск";
  const goPrev = () => {
    setDirection("prev");
    setPage((current) => Math.max(1, current - step));
  };
  const goNext = () => {
    setDirection("next");
    setPage((current) => Math.min(maxStartPage, current + step));
  };

  React.useEffect(() => {
    const onKey = (event) => {
      if (event.key === "Escape") onClose();
      if (event.key === "ArrowRight" && page < maxStartPage) {
        setDirection("next");
        setPage((current) => Math.min(maxStartPage, current + step));
      }
      if (event.key === "ArrowLeft" && page > 1) {
        setDirection("prev");
        setPage((current) => Math.max(1, current - step));
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onClose, page, maxStartPage, step]);

  return (
    <div role="dialog" aria-modal="true" aria-label={`Просмотр журнала ${magazine.title}`}
      className="mag-reader-overlay" onClick={onClose}
      style={{ position: "fixed", inset: 0, zIndex: 3000, background: "rgba(10,20,17,.72)",
        display: "flex", flexDirection: "column", padding: "12px clamp(8px, 1.8vw, 22px)",
        backdropFilter: "blur(10px)", WebkitBackdropFilter: "blur(10px)" }}>
      <style>{`
        @keyframes magReaderIn { from { opacity: 0; transform: translateY(14px) scale(.985); } to { opacity: 1; transform: none; } }
        @keyframes magPageNext { from { opacity: .55; transform: perspective(1200px) rotateY(-10deg) translateX(16px); } to { opacity: 1; transform: perspective(1200px) rotateY(0) translateX(0); } }
        @keyframes magPagePrev { from { opacity: .55; transform: perspective(1200px) rotateY(10deg) translateX(-16px); } to { opacity: 1; transform: perspective(1200px) rotateY(0) translateX(0); } }
        .mag-reader-stage { animation: magReaderIn .34s cubic-bezier(.22,1,.36,1) both; }
        .mag-reader-page { animation: ${direction === "prev" ? "magPagePrev" : "magPageNext"} .34s cubic-bezier(.22,1,.36,1) both; }
        .mag-reader-canvas { max-height: calc(100svh - 142px); }
        @media (max-width: 820px) {
          .mag-reader-overlay { padding: 8px !important; }
          .mag-reader-head { align-items: flex-start !important; gap: 12px !important; }
          .mag-reader-title { font-size: 16px !important; }
          .mag-reader-stage { padding: 6px !important; }
          .mag-reader-canvas { max-height: calc(100svh - 136px); }
          .mag-reader-controls { grid-template-columns: 44px 1fr 44px !important; }
          .mag-reader-control-label { display: none !important; }
        }
        @media (prefers-reduced-motion: reduce) {
          .mag-reader-stage, .mag-reader-page { animation: none !important; }
        }
      `}</style>
      <div onClick={(event) => event.stopPropagation()} style={{ height: "100%", display: "flex",
        flexDirection: "column", gap: 8 }}>
        <div className="mag-reader-head" style={{ display: "flex", alignItems: "center", justifyContent: "space-between",
          gap: 18, color: "var(--cream)" }}>
          <div style={{ minWidth: 0 }}>
            <div style={{ fontFamily: "var(--font-ui)", fontSize: 13, color: "rgba(243,238,218,.7)",
              marginBottom: 4 }}>{magazine.issue}</div>
            <h3 className="mag-reader-title" style={{ fontFamily: "var(--font-display)", fontWeight: 600,
              fontSize: 21, lineHeight: 1.18, margin: 0, color: "var(--cream)" }}>{magazine.title}</h3>
          </div>
          <div style={{ display: "flex", gap: 8, flex: "none" }}>
            <button onClick={onClose} aria-label="Закрыть просмотр журнала"
              style={{ width: 44, height: 44, borderRadius: 999, border: "1px solid rgba(243,238,218,.26)",
                background: "rgba(243,238,218,.1)", color: "var(--cream)", cursor: "pointer",
                display: "inline-flex", alignItems: "center", justifyContent: "center" }}>
              <Icon name="x" size={20} color="var(--cream)" />
            </button>
          </div>
        </div>

        <div className="mag-reader-stage" style={{ flex: 1, minHeight: 0, borderRadius: 24,
          background: "linear-gradient(135deg, rgba(243,238,218,.96), rgba(255,255,255,.9))",
          border: "1px solid rgba(243,238,218,.28)", boxShadow: "0 30px 90px rgba(0,0,0,.28)",
          padding: 8, display: "flex", alignItems: "center", justifyContent: "center", overflow: "hidden" }}>
          {status === "ready" ? (
            <div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: singlePage ? 0 : 10,
              width: "100%", height: "100%", perspective: 1200 }}>
              {visiblePages.map((n) => (
                <MagazinePdfPage key={`${magazine.pdfUrl}-${n}-${singlePage}`} pdf={pdf} pageNumber={n}
                  singlePage={singlePage} />
              ))}
            </div>
          ) : status === "fallback" ? (
            <iframe title={magazine.title} src={magazine.pdfUrl}
              style={{ width: "100%", height: "100%", border: "none", borderRadius: 16, background: "#fff" }} />
          ) : (
            <div style={{ fontFamily: "var(--font-ui)", color: "var(--graphite)" }}>Открываем PDF...</div>
          )}
        </div>

        <div className="mag-reader-controls" style={{ display: "grid", gridTemplateColumns: "150px 1fr 150px",
          alignItems: "center", gap: 10 }}>
          <button onClick={goPrev} disabled={!canPrev} aria-label="Предыдущие страницы"
            style={{ height: 46, borderRadius: 999, border: "1px solid rgba(243,238,218,.3)",
              background: canPrev ? "rgba(243,238,218,.12)" : "rgba(243,238,218,.05)",
              color: "var(--cream)", opacity: canPrev ? 1 : .45, cursor: canPrev ? "pointer" : "not-allowed",
              display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8,
              fontFamily: "var(--font-ui)", fontWeight: 600 }}>
            <Icon name="chevron-left" size={18} color="var(--cream)" />
            <span className="mag-reader-control-label">Назад</span>
          </button>
          <div style={{ textAlign: "center", color: "rgba(243,238,218,.82)", fontFamily: "var(--font-ui)",
            fontSize: 14 }}>{spreadLabel}</div>
          <button onClick={goNext} disabled={!canNext} aria-label="Следующие страницы"
            style={{ height: 46, borderRadius: 999, border: "1px solid rgba(243,238,218,.3)",
              background: canNext ? "rgba(243,238,218,.12)" : "rgba(243,238,218,.05)",
              color: "var(--cream)", opacity: canNext ? 1 : .45, cursor: canNext ? "pointer" : "not-allowed",
              display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8,
              fontFamily: "var(--font-ui)", fontWeight: 600 }}>
            <span className="mag-reader-control-label">Вперед</span>
            <Icon name="chevron-right" size={18} color="var(--cream)" />
          </button>
        </div>
      </div>
    </div>
  );
}

function MagazinePdfPage({ pdf, pageNumber, singlePage }) {
  const canvasRef = React.useRef(null);
  const [rendered, setRendered] = React.useState(false);

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

    setRendered(false);

    pdf.getPage(pageNumber).then((page) => {
      if (cancelled) return;
      const canvas = canvasRef.current;
      if (!canvas) return;
      const viewportBase = page.getViewport({ scale: 1 });
      const maxWidth = singlePage ? Math.min(window.innerWidth - 26, 980) : Math.min((window.innerWidth - 62) / 2, 690);
      const maxHeight = window.innerHeight - 146;
      const scale = Math.min(maxWidth / viewportBase.width, maxHeight / viewportBase.height, 2.4);
      const viewport = page.getViewport({ scale: Math.max(.35, scale) });
      const context = canvas.getContext("2d");

      canvas.width = Math.floor(viewport.width);
      canvas.height = Math.floor(viewport.height);
      canvas.style.width = `${Math.floor(viewport.width)}px`;
      canvas.style.height = `${Math.floor(viewport.height)}px`;

      activeRender = page.render({ canvasContext: context, viewport });
      return activeRender.promise;
    }).then(() => {
      if (!cancelled) setRendered(true);
    }).catch(() => {
      if (!cancelled) setRendered(true);
    });

    return () => {
      cancelled = true;
      if (activeRender && activeRender.cancel) activeRender.cancel();
    };
  }, [pdf, pageNumber, singlePage]);

  return (
    <div className="mag-reader-page" style={{ position: "relative", background: "#fff",
      boxShadow: "0 18px 48px rgba(34,27,22,.24)", borderRadius: 8, overflow: "hidden",
      opacity: rendered ? 1 : .62, transition: "opacity .2s ease" }}>
      <canvas ref={canvasRef} className="mag-reader-canvas" style={{ display: "block", maxWidth: "100%",
        background: "#fff" }} />
      <div style={{ position: "absolute", left: 10, bottom: 8, fontFamily: "var(--font-ui)",
        fontSize: 11, color: "rgba(43,38,34,.44)", fontVariantNumeric: "tabular-nums" }}>{pageNumber}</div>
    </div>
  );
}

function MagazineCoverPlaceholder({ title }) {
  return (
    <div style={{ width: "100%", height: "100%", background: "var(--cream)",
      display: "flex", alignItems: "center", justifyContent: "center", position: "relative" }}>
      <div style={{ padding: 18, textAlign: "center", color: "var(--stone)", fontFamily: "var(--font-ui)",
        fontSize: 13.5, lineHeight: 1.4 }}>
        <Icon name="file-text" size={34} color="var(--terracotta)" style={{ marginBottom: 12 }} />
        <div>{title}</div>
      </div>
    </div>
  );
}

// Сетка журналов: показывает ограниченное число обложек, остальные раскрываются
// по кнопке с плавной анимацией (чтобы страница не была бесконечной).
function MagazineSubscriptionNote({ children }) {
  return (
    <p style={{ fontFamily: "var(--font-ui)", fontSize: 14.5, lineHeight: 1.55,
      color: "var(--stone)", margin: "8px 0 0", maxWidth: 680 }}>
      {children}{" "}
      <button type="button" onClick={() => window.__navTo ? window.__navTo("Подписка") : null}
        style={{ color: "var(--terracotta)", fontWeight: 700, textDecoration: "none",
          background: "transparent", border: "none", padding: 0, cursor: "pointer",
          font: "inherit" }}>
        Оформить годовую подписку.
      </button>
    </p>
  );
}

function MagazineGrid({ category, initial = 4 }) {
  const [items, setItems] = React.useState([]);
  const [page, setPage] = React.useState(1);
  const [hasNextPage, setHasNextPage] = React.useState(false);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState(false);
  const [readerMagazine, setReaderMagazine] = React.useState(null);
  const wrapRef = React.useRef(null);

  const loadPage = React.useCallback((nextPage = 1) => {
    setLoading(true);
    setError(false);
    getPayloadMagazines(category, nextPage, initial)
      .then((data) => {
        setItems((current) => nextPage === 1 ? data.items : [...current, ...data.items]);
        setPage(nextPage);
        setHasNextPage(data.hasNextPage);
      })
      .catch(() => {
        setError(true);
      })
      .finally(() => {
        setLoading(false);
      });
  }, [category, initial]);

  React.useEffect(() => {
    setItems([]);
    setPage(1);
    setHasNextPage(false);
    loadPage(1);
  }, [loadPage]);

  const showMore = () => {
    if (!loading && hasNextPage) loadPage(page + 1);
  };

  return (
    <div ref={wrapRef}>
      <style>{`@keyframes magIn { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }`}</style>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 26, marginTop: 24 }} className="kit-grid-4">
        {items.map((m, i) => (
          <div key={`${m.src}-${m.issue}`} style={i >= initial
            ? { animation: "magIn .5s cubic-bezier(.22,1,.36,1) backwards", animationDelay: ((i - initial) * 0.06) + "s" }
            : undefined}>
            <MagazineCard {...m} onOpenReader={() => setReaderMagazine(m)} />
          </div>
        ))}
      </div>
      {loading && items.length === 0 && (
        <div style={{ fontFamily: "var(--font-ui)", color: "var(--stone)", marginTop: 24 }}>
          Загружаем выпуски...
        </div>
      )}
      {error && items.length === 0 && (
        <div style={{ fontFamily: "var(--font-ui)", color: "var(--stone)", marginTop: 24 }}>
          Не удалось загрузить выпуски. Попробуйте обновить страницу.
        </div>
      )}
      {hasNextPage && (
        <div style={{ display: "flex", justifyContent: "center", marginTop: 32 }}>
          <Button variant="secondary" icon="chevron-down" onClick={showMore}>
            {loading ? "Загружаем..." : "Показать ещё"}
          </Button>
        </div>
      )}
      {readerMagazine && <MagazineReader magazine={readerMagazine} onClose={() => setReaderMagazine(null)} />}
    </div>
  );
}

function MagazineCard({ src, issue, title, topics, href, pdfUrl, onOpenReader }) {
  const [hover, setHover] = React.useState(false);
  const canRead = Boolean(pdfUrl);
  const content = (
    <>
      <div style={{ width: "100%", aspectRatio: "3 / 4", borderRadius: 14, overflow: "hidden",
          border: "1px solid var(--line)", background: "#fff",
          boxShadow: hover ? "var(--shadow-lg)" : "var(--shadow-md)",
          transform: hover ? "translateY(-4px)" : "none", transition: "all .26s cubic-bezier(.22,1,.36,1)",
          position: "relative" }}>
        {src ? (
          <img src={src} alt={title} loading="lazy" decoding="async"
            style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
        ) : (
          <MagazineCoverPlaceholder title={title} />
        )}
        {canRead && (
          <span style={{ position: "absolute", right: 10, bottom: 10, width: 38, height: 38,
            borderRadius: 999, background: "rgba(1,65,56,.9)", color: "var(--cream)",
            display: "inline-flex", alignItems: "center", justifyContent: "center",
            boxShadow: "0 10px 24px rgba(6,14,10,.25)" }}>
            <Icon name="book-open" size={18} color="var(--cream)" />
          </span>
        )}
      </div>
      <div>
        <Badge tone="soft">{issue}</Badge>
        <h4 style={{ fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 16,
          letterSpacing: "-.005em", lineHeight: 1.25, color: (canRead || href) && hover ? "var(--terracotta)" : "var(--ink)",
          margin: "10px 0 0", textWrap: "pretty", transition: "color .2s ease" }}>{title}</h4>
        {topics && topics.length > 0 && (
          <ul className="mag-topics" style={{ listStyle: "none", padding: 0, margin: "9px 0 0",
            display: "flex", flexDirection: "column", gap: 4 }}>
            {topics.map((t) => (
              <li key={t} style={{ fontFamily: "var(--font-ui)", fontSize: 12.5, lineHeight: 1.35,
                color: "var(--stone)", display: "flex", gap: 7 }}>
                <span style={{ color: "var(--terracotta)", flex: "none" }}>·</span>{t}
              </li>
            ))}
          </ul>
        )}
      </div>
    </>
  );

  if (canRead) {
    return (
      <button type="button" onClick={onOpenReader}
        onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
        style={{ display: "flex", flexDirection: "column", gap: 13, textAlign: "left",
          background: "transparent", border: "none", padding: 0, cursor: "pointer", width: "100%" }}>
        {content}
      </button>
    );
  }

  if (href) {
    return (
      <a href={href} target="_blank" rel="noopener noreferrer"
        onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
        style={{ display: "flex", flexDirection: "column", gap: 13, textDecoration: "none", cursor: "pointer" }}>
        {content}
      </a>
    );
  }

  return (
    <article
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{ display: "flex", flexDirection: "column", gap: 13 }}>
      {content}
    </article>
  );
}

const ALBUMS = [
  { title: "Песни надежды", year: "2025", tracks: [
    { title: "Свет Христов", dur: "2:54" },
    { title: "Радуйтесь, друзья", dur: "3:12" },
    { title: "Песенка о доброте", dur: "2:38" },
    { title: "Небесный Отец", dur: "3:45" },
    { title: "Колыбельная веры", dur: "2:20" },
    { title: "Идём за Светом", dur: "3:05" },
    { title: "Благодарю", dur: "2:48" },
    { title: "Горные вершины", dur: "3:30" },
  ] },
  { title: "Свет в горах", year: "2024", tracks: [
    { title: "Утренняя молитва", dur: "2:42" },
    { title: "Дорога домой", dur: "3:18" },
    { title: "Тихий вечер", dur: "2:55" },
    { title: "Сердце нараспашку", dur: "3:01" },
    { title: "Над облаками", dur: "2:34" },
    { title: "Доброе слово", dur: "2:49" },
  ] },
  { title: "Голос радости", year: "2023", tracks: [
    { title: "Радость моя", dur: "2:27" },
    { title: "Поём вместе", dur: "3:08" },
    { title: "Маленький свет", dur: "2:16" },
    { title: "Под крылом", dur: "3:22" },
    { title: "Спасибо, Боже", dur: "2:51" },
  ] },
];

function getPayloadAudioAlbums() {
  return fetch("/api/choir-albums?depth=2&limit=50&sort=-createdAt", {
    headers: { Accept: "application/json" },
  })
    .then((res) => {
      if (!res.ok) throw new Error("Не удалось загрузить альбомы хора");
      return res.json();
    })
    .then((data) => {
      const docs = Array.isArray(data.docs) ? data.docs : [];
      return docs
        .filter((album) => album.status === "published")
        .map((album) => {
          const tracks = Array.isArray(album.tracks) ? album.tracks : [];
          const yearSource = album.albumDate || album.createdAt;
          const year = yearSource ? String(new Date(yearSource).getFullYear()) : "";
          return {
            title: album.title,
            subtitle: album.subtitle || "",
            year,
            description: album.description || "",
            coverUrl: album.cover && typeof album.cover === "object" ? album.cover.url : "",
            coverAlt: album.cover && typeof album.cover === "object" ? album.cover.alt || album.title : album.title,
            tracks: tracks
              .filter((track) => track && track.title)
              .map((track) => ({
                title: track.title,
                dur: track.duration || "",
                note: track.scriptureReference || "",
                src: track.audio && typeof track.audio === "object"
                  ? `/choir-audio-files/${encodeURIComponent(track.audio.filename || "")}`
                  : "",
              })),
          };
        })
        .filter((album) => album.tracks.length > 0);
    });
}

// Селектор альбома — горизонтальные «пилюли» (одинаков в обоих вариантах плеера)
function AlbumSelector({ albums, current, onSelect }) {
  return (
    <div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 18 }}>
      {albums.map((a, i) => {
        const on = i === current;
        return (
          <button key={a.title} onClick={() => onSelect(i)} style={{
            fontFamily: "var(--font-ui)", fontWeight: 600, fontSize: 13.5, cursor: "pointer",
            padding: "8px 16px", borderRadius: 999, transition: "all .2s ease", whiteSpace: "nowrap",
            border: "1px solid " + (on ? "var(--terracotta)" : "rgba(243,238,218,.22)"),
            background: on ? "var(--terracotta)" : "transparent",
            color: on ? "var(--cream)" : "rgba(243,238,218,.78)" }}>
            {a.title}
          </button>
        );
      })}
    </div>
  );
}

function formatAudioTime(seconds) {
  if (!Number.isFinite(seconds) || seconds <= 0) return "0:00";
  const mins = Math.floor(seconds / 60);
  const secs = Math.floor(seconds % 60);
  return `${mins}:${String(secs).padStart(2, "0")}`;
}

function AudioRow({ n, title, dur, note, src, active, playing, onPlay, onEnded }) {
  const [hover, setHover] = React.useState(false);
  const [current, setCurrent] = React.useState(0);
  const [duration, setDuration] = React.useState(0);
  const [volume, setVolume] = React.useState(1);
  const audioRef = React.useRef(null);
  const volumeRef = React.useRef(1);
  const realDuration = duration || 0;
  const progress = realDuration ? Math.min(100, (current / realDuration) * 100) : 0;
  const volumeProgress = Math.min(100, Math.max(0, volume * 100));

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

  React.useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;
    if (active && playing && src) {
      audio.play().catch(() => {});
    } else {
      audio.pause();
    }
  }, [active, playing, src]);

  React.useEffect(() => {
    if (!active) {
      setCurrent(0);
      setDuration(0);
    }
  }, [active, src]);

  const seek = (value) => {
    const next = Number(value);
    setCurrent(next);
    if (audioRef.current) audioRef.current.currentTime = next;
  };

  const changeVolume = (value) => {
    const next = Math.min(1, Math.max(0, Number(value)));
    volumeRef.current = next;
    setVolume(next);
    if (audioRef.current) audioRef.current.volume = next;
  };

  return (
    <div onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{ width: "100%", padding: active && src ? "12px 14px 14px" : "0",
        borderRadius: 14,
        background: active ? "rgba(243,238,218,.08)" : hover ? "rgba(243,238,218,.06)" : "transparent",
        boxShadow: active ? "inset 0 0 0 1px rgba(243,238,218,.08)" : "none",
        transition: "background .2s ease" }}>
      <button onClick={onPlay}
        style={{ display: "flex", alignItems: "center", gap: 16, width: "100%", textAlign: "left",
          padding: active && src ? 0 : "13px 16px", borderRadius: 14, cursor: "pointer", border: "none",
          background: "transparent" }}>
        <span style={{ width: 34, height: 34, borderRadius: 999, flex: "none",
          display: "flex", alignItems: "center", justifyContent: "center",
          background: active ? "var(--terracotta)" : "rgba(243,238,218,.1)",
          color: active ? "var(--cream)" : "rgba(243,238,218,.7)" }}>
          <Icon name={active && playing ? "pause" : "play"} size={15}
            color={active ? "var(--cream)" : "rgba(243,238,218,.7)"} />
        </span>
        <span style={{ width: 22, flex: "none", fontFamily: "var(--font-ui)", fontSize: 13,
          color: "rgba(243,238,218,.45)", fontVariantNumeric: "tabular-nums" }}>
          {String(n).padStart(2, "0")}
        </span>
        <span style={{ flex: 1, minWidth: 0 }}>
          <span style={{ display: "block", fontFamily: "var(--font-ui)", fontSize: 15.5,
            fontWeight: active ? 600 : 500, color: active ? "var(--cream)" : "rgba(243,238,218,.88)" }}>
            {title}
          </span>
          {note && <span style={{ display: "block", marginTop: 3, fontFamily: "var(--font-ui)",
            fontSize: 12.5, color: "rgba(243,238,218,.55)" }}>{note}</span>}
        </span>
        <span style={{ fontFamily: "var(--font-ui)", fontSize: 13.5, color: "rgba(243,238,218,.55)",
          fontVariantNumeric: "tabular-nums", flex: "none" }}>
          {active && src ? `${formatAudioTime(current)} / ${dur || formatAudioTime(realDuration)}` : dur}
        </span>
      </button>
      {active && src && (
        <div style={{ display: "grid", gridTemplateColumns: "minmax(0, 1fr) 92px", gap: 16, alignItems: "end",
          marginTop: 7, padding: "0 0 0 72px" }} className="audio-controls-grid">
          <audio ref={audioRef} src={src} preload="metadata"
            onLoadedMetadata={(e) => setDuration(e.currentTarget.duration || 0)}
            onTimeUpdate={(e) => setCurrent(e.currentTarget.currentTime || 0)}
            onEnded={onEnded} />
          <div>
            <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
              <div className="audio-range-shell audio-progress-shell" style={{ flex: 1 }}>
                <div style={{ position: "absolute", left: 0, right: 0, top: "50%", height: 5,
                  borderRadius: 999, transform: "translateY(-50%)",
                  background: "rgba(243,238,218,.16)", overflow: "hidden", pointerEvents: "none" }}>
                  <div style={{ width: `${progress}%`, height: "100%", borderRadius: 999,
                    background: "linear-gradient(90deg, var(--terracotta), var(--terracotta-bright))",
                    transition: playing ? "width .12s linear" : "width .2s ease" }} />
                </div>
                <input className="audio-range" type="range" min="0" max={realDuration || 0}
                  step="0.1" value={current} onChange={(e) => seek(e.target.value)}
                  aria-label={`Позиция: ${title}`} />
              </div>
            </div>
          </div>
          <div className="audio-volume-control"
            style={{ display: "flex", alignItems: "center", gap: 8, width: 92, flex: "none", paddingBottom: 1 }}>
            <Icon name={volume <= 0.02 ? "volume-x" : "volume-2"} size={16} color="var(--terracotta-bright)" />
            <div className="audio-range-shell audio-volume-shell" style={{ flex: 1 }}>
              <div style={{ position: "absolute", left: 0, right: 0, top: "50%", height: 5,
                borderRadius: 999, transform: "translateY(-50%)",
                background: "rgba(211,97,60,.22)", overflow: "hidden", pointerEvents: "none" }}>
                <div style={{ width: `${volumeProgress}%`, height: "100%", borderRadius: 999,
                  background: "linear-gradient(90deg, var(--terracotta), var(--terracotta-bright))",
                  transition: "none" }} />
              </div>
              <input className="audio-range audio-volume" type="range" min="0" max="1" step="0.01"
                value={volume} onInput={(e) => changeVolume(e.target.value)}
                onChange={(e) => changeVolume(e.target.value)}
                aria-label={`Громкость: ${title}`} />
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// Плеер детского хора — переиспользуется в «Медиа» и «Служении». Несколько альбомов.
function ChoirPlayer({ choir = "Детский хор", compact, albums, sourceLabel }) {
  const [payloadAlbums, setPayloadAlbums] = React.useState(null);
  const [albumIdx, setAlbumIdx] = React.useState(0);
  const [active, setActive] = React.useState(null);
  const [playing, setPlaying] = React.useState(false);
  const [showAllTracks, setShowAllTracks] = React.useState(false);

  React.useEffect(() => {
    if (albums && albums.length) return;
    let alive = true;
    getPayloadAudioAlbums()
      .then((items) => {
        if (alive) setPayloadAlbums(items);
      })
      .catch(() => {
        if (alive) setPayloadAlbums(null);
      });
    return () => { alive = false; };
  }, [albums]);

  const playerAlbums = albums && albums.length ? albums : payloadAlbums && payloadAlbums.length ? payloadAlbums : ALBUMS;
  const album = playerAlbums[albumIdx] || playerAlbums[0] || ALBUMS[0];
  const tracks = album.tracks;
  const trackPreviewLimit = 10;
  const visibleTracks = showAllTracks ? tracks : tracks.slice(0, trackPreviewLimit);
  const hiddenTrackCount = Math.max(0, tracks.length - visibleTracks.length);
  const coverUrl = album.coverUrl || "";
  const coverAlt = album.coverAlt || album.title;
  const select = (i) => { if (i === active) setPlaying(p => !p); else { setActive(i); setPlaying(true); } };
  const toggleAlbum = () => {
    if (active === null) {
      setActive(0);
      setPlaying(true);
    } else {
      setPlaying(p => !p);
    }
  };
  const pickAlbum = (i) => { setAlbumIdx(i); setActive(null); setPlaying(false); setShowAllTracks(false); };
  const finishTrack = (i) => {
    if (i + 1 < tracks.length) {
      if (i + 1 >= trackPreviewLimit) setShowAllTracks(true);
      setActive(i + 1);
      setPlaying(true);
    } else {
      setPlaying(false);
    }
  };
  const playerStyles = `
    .audio-range-shell {
      --audio-thumb-size: 14px;
      position: relative;
      height: 24px;
      padding-inline: calc(var(--audio-thumb-size) / 2);
    }
    .audio-range-shell > div {
      left: calc(var(--audio-thumb-size) / 2) !important;
      right: calc(var(--audio-thumb-size) / 2) !important;
    }
    .audio-volume-shell {
      --audio-thumb-size: 12px;
    }
    .audio-range {
      position: absolute;
      inset: 0 calc(var(--audio-thumb-size) / 2);
      width: calc(100% - var(--audio-thumb-size));
      height: 24px;
      margin: 0;
      appearance: none;
      -webkit-appearance: none;
      background: transparent;
      cursor: pointer;
      display: block;
    }
    .audio-range::-webkit-slider-runnable-track {
      height: 5px;
      border-radius: 999px;
      background: transparent;
    }
    .audio-range::-webkit-slider-thumb {
      width: 14px;
      height: 14px;
      margin-top: -4.5px;
      border-radius: 999px;
      border: 2px solid var(--cream);
      background: var(--terracotta-bright);
      box-shadow: 0 4px 12px rgba(0,0,0,.2);
      appearance: none;
      -webkit-appearance: none;
    }
    .audio-range::-moz-range-track {
      height: 5px;
      border-radius: 999px;
      background: transparent;
    }
    .audio-range::-moz-range-thumb {
      width: 14px;
      height: 14px;
      border-radius: 999px;
      border: 2px solid var(--cream);
      background: var(--terracotta-bright);
      box-shadow: 0 4px 12px rgba(0,0,0,.2);
    }
    .audio-volume::-webkit-slider-thumb {
      width: 12px;
      height: 12px;
      margin-top: -3.5px;
      border-color: rgba(243,238,218,.95);
      background: var(--terracotta-bright);
      box-shadow: 0 0 0 4px rgba(211,97,60,.2), 0 4px 12px rgba(0,0,0,.22);
    }
    .audio-volume::-moz-range-progress {
      height: 5px;
      border-radius: 999px;
      background: linear-gradient(90deg, var(--terracotta), var(--terracotta-bright));
    }
    .audio-volume::-moz-range-track {
      height: 5px;
      border-radius: 999px;
      background: rgba(211,97,60,.22);
    }
    @media (max-width: 720px) {
      .audio-controls-grid {
        grid-template-columns: minmax(0, 1fr) 92px !important;
        padding-left: 0 !important;
      }
      .audio-volume-control {
        width: 92px !important;
      }
    }
    @media (max-width: 520px) {
      .audio-controls-grid {
        grid-template-columns: 1fr !important;
      }
      .audio-volume-control {
        width: 96px !important;
        justify-self: end;
      }
    }
  `;

  // ── Лаконичный вариант: компактная «шапка» альбома + список в одном блоке ──
  if (compact) {
    return (
      <div style={{ background: "rgba(243,238,218,.05)", border: "1px solid rgba(243,238,218,.14)",
        borderRadius: 24, padding: 14 }}>
        <style>{playerStyles}</style>
        <div style={{ padding: "4px 4px 0" }}>
          <AlbumSelector albums={playerAlbums} current={albumIdx} onSelect={pickAlbum} />
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 16, padding: "4px 10px 16px",
          borderBottom: "1px solid rgba(243,238,218,.12)" }}>
          <div style={{ width: 60, height: 60, flex: "none", borderRadius: 14, background: "var(--terracotta)",
            display: "flex", alignItems: "center", justifyContent: "center", overflow: "hidden" }}>
            {coverUrl ? (
              <img src={coverUrl} alt={coverAlt} loading="lazy"
                style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
            ) : (
              <Logomark color="var(--cream)" width={38} />
            )}
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <h3 style={{ fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 18,
              letterSpacing: "-.01em", color: "var(--cream)", margin: 0 }}>{album.title}</h3>
            {album.subtitle && <p style={{ fontFamily: "var(--font-ui)", fontSize: 13.5,
              color: "rgba(243,238,218,.8)", margin: "3px 0 0", lineHeight: 1.35 }}>{album.subtitle}</p>}
            <p style={{ fontFamily: "var(--font-ui)", fontSize: 13.5, color: "rgba(243,238,218,.7)",
              margin: album.subtitle ? "2px 0 0" : "3px 0 0" }}>
              {choir} · {tracks.length} треков{album.year ? ` · ${album.year}` : ""}
            </p>
          </div>
          <button onClick={toggleAlbum} aria-label={playing ? "Пауза" : "Слушать альбом"}
            style={{ width: 48, height: 48, flex: "none", borderRadius: 999, border: "none", cursor: "pointer",
              background: "var(--terracotta)", color: "var(--cream)", display: "flex",
              alignItems: "center", justifyContent: "center" }}>
            <Icon name={playing ? "pause" : "play"} size={20} color="var(--cream)" />
          </button>
        </div>
        <div style={{ marginTop: 6 }}>
          {visibleTracks.map((t, i) => (
            <AudioRow key={t.title} n={i + 1} {...t} active={i === active} playing={playing}
              onPlay={() => select(i)} onEnded={() => finishTrack(i)} />
          ))}
          {hiddenTrackCount > 0 && (
            <div style={{ display: "flex", justifyContent: "center", padding: "12px 0 4px" }}>
              <Button variant="secondary" size="sm" icon="chevron-down" onClick={() => setShowAllTracks(true)}>
                Показать еще
              </Button>
            </div>
          )}
        </div>
      </div>
    );
  }

  return (
    <div style={{ display: "grid", gridTemplateColumns: "340px 1fr", gap: 36, alignItems: "start" }} className="kit-2col">
      <style>{playerStyles}</style>
      {/* Обложка альбома / плеер */}
      <div style={{ background: "rgba(243,238,218,.06)", border: "1px solid rgba(243,238,218,.14)",
        borderRadius: 24, padding: 22 }}>
        <div style={{ width: "100%", aspectRatio: "1 / 1", borderRadius: 16, background: "var(--terracotta)",
          display: "flex", alignItems: "center", justifyContent: "center", position: "relative", overflow: "hidden" }}>
          {coverUrl ? (
            <img src={coverUrl} alt={coverAlt} loading="lazy"
              style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
          ) : (
            <Logomark color="var(--cream)" width={150} />
          )}
        </div>
        <h3 style={{ fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 22,
          letterSpacing: "-.01em", color: "var(--cream)", margin: "20px 0 4px" }}>{album.title}</h3>
        {album.subtitle && <p style={{ fontFamily: "var(--font-ui)", fontSize: 15,
          color: "rgba(243,238,218,.82)", margin: "0 0 8px", lineHeight: 1.45 }}>{album.subtitle}</p>}
        <p style={{ fontFamily: "var(--font-ui)", fontSize: 14, color: "rgba(243,238,218,.7)", margin: "0 0 18px" }}>
          {choir} · {tracks.length} треков{album.year ? ` · ${album.year}` : ""}
        </p>
        {album.description && <p style={{ fontFamily: "var(--font-ui)", fontSize: 14,
          color: "rgba(243,238,218,.68)", margin: "-8px 0 18px", lineHeight: 1.55 }}>{album.description}</p>}
        <Button variant="primary" size="md" icon={playing ? "pause" : "play"}
          style={{ width: "100%", justifyContent: "center" }}
          onClick={toggleAlbum}>
          {playing ? "Пауза" : "Слушать альбом"}
        </Button>
      </div>
      {/* Список треков */}
      <div>
        <AlbumSelector albums={playerAlbums} current={albumIdx} onSelect={pickAlbum} />
        <div style={{ background: "rgba(243,238,218,.04)", border: "1px solid rgba(243,238,218,.12)",
          borderRadius: 24, padding: 12 }}>
          {visibleTracks.map((t, i) => (
            <AudioRow key={t.title} n={i + 1} {...t} active={i === active} playing={playing}
              onPlay={() => select(i)} onEnded={() => finishTrack(i)} />
          ))}
          {hiddenTrackCount > 0 && (
            <div style={{ display: "flex", justifyContent: "center", padding: "14px 0 2px" }}>
              <Button variant="secondary" size="sm" icon="chevron-down" onClick={() => setShowAllTracks(true)}>
                Показать еще
              </Button>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function MediaPage() {
  const [payloadAlbums, setPayloadAlbums] = React.useState(null);
  const [audioStatus, setAudioStatus] = React.useState("loading");

  React.useEffect(() => {
    let alive = true;
    getPayloadAudioAlbums()
      .then((albums) => {
        if (!alive) return;
        setPayloadAlbums(albums);
        setAudioStatus(albums.length ? "ready" : "empty");
      })
      .catch(() => {
        if (!alive) return;
        setPayloadAlbums(null);
        setAudioStatus("error");
      });
    return () => { alive = false; };
  }, []);

  const choirAlbums = payloadAlbums && payloadAlbums.length ? payloadAlbums : ALBUMS;
  const audioIntro = audioStatus === "ready"
    ? "Песни детского хора — это простое и тёплое свидетельство веры: о надежде, благодарности и Божьей любви."
    : audioStatus === "empty"
      ? "Здесь скоро появятся записи детского хора — песни о вере, надежде и радости во Христе."
      : audioStatus === "error"
        ? "Сейчас показываем пример оформления аудиораздела с песнями детского хора."
        : "Готовим записи детского хора к прослушиванию.";

  return (
    <div>
      <PageHero eyebrow="Медиа" title="Журналы и аудио"
        intro="Обложки печатных выпусков миссии и записи нашего детского хора — для знакомства, памяти и поддержки служения."
        image="../../assets/page-heroes/media.png" imagePosition="center center" />

      {/* Журналы */}
      <section id="media-jurnaly" style={{ background: "#fff", padding: "92px 40px", scrollMarginTop: 80 }}>
        <div data-anchor-content style={{ maxWidth: 1280, margin: "0 auto" }}>
          <SectionHead eyebrow="Журналы" title="Наши издания"
            intro="Христианские журналы миссии — для детей и для взрослых. Здесь собраны вышедшие выпуски." />

          <h3 id="media-tropinka" style={{ fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 24,
            letterSpacing: "-.01em", color: "var(--ink)", margin: "52px 0 0", scrollMarginTop: 96 }}>Журнал «Тропинка»</h3>
          <p style={{ fontFamily: "var(--font-ui)", fontSize: 15, color: "var(--stone)", margin: "6px 0 0" }}>Для детей</p>
          <MagazineSubscriptionNote>
            Здесь показаны обложки последних выпусков. Если хотите получать свежие номера по почте, можно оформить подписку на сайте.
          </MagazineSubscriptionNote>
          <MagazineGrid category="tropinka" initial={4} />

          <h3 id="media-vera" style={{ fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 24,
            letterSpacing: "-.01em", color: "var(--ink)", margin: "64px 0 0", scrollMarginTop: 96 }}>Журнал «Вера и жизнь»</h3>
          <p style={{ fontFamily: "var(--font-ui)", fontSize: 15, color: "var(--stone)", margin: "6px 0 0" }}>Для взрослых</p>
          <MagazineSubscriptionNote>
            Здесь показаны обложки последних выпусков. Если хотите получать свежие номера по почте, можно оформить подписку на сайте.
          </MagazineSubscriptionNote>
          <MagazineGrid category="vera" initial={4} />
        </div>
      </section>

      {/* Аудио — детский хор */}
      <section id="media-audio" style={{ background: "var(--green-deep)", padding: "92px 40px",
        position: "relative", overflow: "hidden", scrollMarginTop: 80 }}>
        <div data-anchor-content style={{ maxWidth: 1280, margin: "0 auto", position: "relative" }}>
          <SectionHead dark eyebrow="Аудио" title="Детский хор"
            intro={audioIntro} />
          <div style={{ marginTop: 48 }}>
            {audioStatus === "empty" ? (
              <div style={{ border: "1px solid rgba(243,238,218,.16)", borderRadius: 24,
                padding: 28, color: "rgba(243,238,218,.78)", fontFamily: "var(--font-ui)",
                background: "rgba(243,238,218,.05)" }}>
                Записи пока готовятся. Скоро здесь можно будет послушать песни детского хора.
              </div>
            ) : (
              <ChoirPlayer albums={choirAlbums} />
            )}
          </div>
        </div>
      </section>
    </div>
  );
}

Object.assign(window, { MediaPage, ChoirPlayer, MagazineCard, MagazineReader, MagazineCoverPlaceholder, AudioRow, ALBUMS, MagazineSubscriptionNote });
