// Views: Archive, Entry, Pipeline + App root

// ─────────────────────────────────────────────
// Archive view
// ─────────────────────────────────────────────
function ArchiveView({ lang, query, onOpen }) {
  const ui = window.UI;
  const days = window.DAYS;
  const [moodFilter, setMoodFilter] = React.useState("all");
  const [bookFilter, setBookFilter] = React.useState("all");

  // Build unique mood and book lists
  const moods = React.useMemo(() => {
    const map = new Map();
    days.forEach(d => {
      const key = d.weather.mood;
      const eng = d.weather.mood_eng;
      if (!map.has(key)) map.set(key, { key, cht: key, eng, count: 0 });
      map.get(key).count++;
    });
    return Array.from(map.values()).sort((a, b) => b.count - a.count);
  }, []);

  const books = React.useMemo(() => {
    const map = new Map();
    days.forEach(d => {
      const key = d.scripture.book_cht;
      const eng = d.scripture.book_eng;
      if (!map.has(key)) map.set(key, { key, cht: key, eng, count: 0 });
      map.get(key).count++;
    });
    return Array.from(map.values()).sort((a, b) => b.count - a.count);
  }, []);

  // Search + filter
  const filtered = React.useMemo(() => {
    let result = days;
    if (moodFilter !== "all") result = result.filter(d => d.weather.mood === moodFilter);
    if (bookFilter !== "all") result = result.filter(d => d.scripture.book_cht === bookFilter);
    if (query.trim()) {
      const q = query.toLowerCase();
      result = result.filter(d => {
        const hay = [
          d.date_cht, d.date_eng, d.iso,
          d.weather.cht, d.weather.eng, d.weather.badge_cht, d.weather.badge_eng,
          d.weather.mood, d.weather.mood_eng,
          d.scripture.cht, d.scripture.niv, d.scripture.ref_cht, d.scripture.ref_eng,
          d.scripture.book_cht, d.scripture.book_eng,
          d.reflection.cht, d.reflection.eng,
          d.history.world.cht, d.history.world.eng, d.history.world.year,
          d.history.birth.cht, d.history.birth.eng, d.history.birth.year,
          d.history.death.cht, d.history.death.eng, d.history.death.year,
          d.history.custom.cht, d.history.custom.eng, d.history.custom.year
        ].join(" ").toLowerCase();
        return hay.includes(q);
      });
    }
    return result;
  }, [moodFilter, bookFilter, query]);

  const today = days[0]; // assume sorted desc by date

  const hasFilters = moodFilter !== "all" || bookFilter !== "all" || query.trim();

  return (
    <div className="page">
      {!query && !hasFilters && (
        <>
          {/* Hero */}
          <section className="archive-hero">

            <h1>{lang === "cht" ? "每日靜思" : "A Daily Moment"}</h1>
            <div className="alt-title">{lang === "cht" ? "A Daily Moment" : "每日靜思"}</div>
            <p className="hero-subtitle">{lang === "cht" ? <span>每一天都是新的開始，<br className="mobile-break" />願您重新得力</span> : "Every day is a new day, be enchanted."}</p>
            <div className="divider-cross">
              <span className="line"></span>
              <Icon name="cross" size={16} />
              <span className="line"></span>
            </div>
          </section>

          {/* Featured today */}
          <FeaturedToday day={today} lang={lang} onOpen={onOpen} />
        </>
      )}

      {/* Filters + grid */}
      <div className="filters-row">
        <div className="filters-left">
          <h2 className="section-title">{ui.nav_archive[lang]}</h2>
          <span className="section-count">{ui.archive_count[lang](days.length)}</span>
        </div>
      </div>

      <div className="filter-line">
        <span className="filter-group-label">{ui.filter_mood[lang]}</span>
        <button className={"chip" + (moodFilter === "all" ? " active" : "")} onClick={() => setMoodFilter("all")}>
          {ui.filter_all[lang]}<span className="count">{days.length}</span>
        </button>
        {moods.map(m => (
          <button
            key={m.key}
            className={"chip" + (moodFilter === m.key ? " active" : "")}
            onClick={() => setMoodFilter(moodFilter === m.key ? "all" : m.key)}
          >
            {lang === "cht" ? m.cht : m.eng}<span className="count">{m.count}</span>
          </button>
        ))}
      </div>

      <div className="filter-line">
        <span className="filter-group-label">{ui.filter_book[lang]}</span>
        <button className={"chip" + (bookFilter === "all" ? " active" : "")} onClick={() => setBookFilter("all")}>
          {ui.filter_all[lang]}<span className="count">{days.length}</span>
        </button>
        {books.map(b => (
          <button
            key={b.key}
            className={"chip" + (bookFilter === b.key ? " active" : "")}
            onClick={() => setBookFilter(bookFilter === b.key ? "all" : b.key)}
          >
            {lang === "cht" ? b.cht : b.eng}<span className="count">{b.count}</span>
          </button>
        ))}
        {hasFilters && (
          <button className="clear-filters" onClick={() => { setMoodFilter("all"); setBookFilter("all"); }}>
            {ui.filter_clear[lang]}
          </button>
        )}
      </div>

      <div style={{ marginTop: 6, marginBottom: 14, fontSize: 12.5, color: "var(--ink-muted)", letterSpacing: "0.04em", textAlign: "center" }}>
        {ui.filtered_count[lang](filtered.length, days.length)}
      </div>

      {filtered.length === 0 ? (
        <div className="empty">
          <div className="icon"><Icon name="info" size={28} /></div>
          <div>{ui.empty[lang]}</div>
        </div>
      ) : (
        <div className="grid">
          {filtered.map(d => (
            <article key={d.iso} className="card" onClick={() => onOpen(d.iso)}>
              <div className="card-thumb">
                {d.has_real_infographic ? (
                  <DayImage iso={d.iso} lang={lang} loading="lazy" />
                ) : (
                  <MiniInfographic day={d} lang={lang} />
                )}
              </div>
              <div className="card-body">
                <div className="card-date">{lang === "cht" ? d.date_cht : d.date_eng}</div>
                <div className="card-ref">{lang === "cht" ? d.scripture.ref_cht.replace(/\s*和合本\s*/g, "").trim() : d.scripture.ref_eng.replace(/\s*NIV\s*/gi, "").trim()}</div>
              </div>
            </article>
          ))}
        </div>
      )}
    </div>
  );
}

// Featured today block
function FeaturedToday({ day, lang, onOpen }) {
  const ui = window.UI;
  const m = moodFor(day);
  const [lightbox, setLightbox] = React.useState(false);

  async function handleFeaturedShare(e) {
    e.stopPropagation();
    const isCht = lang === "cht";
    const pageUrl = `https://daily.moment.bridge-n-build.org/#entry/${day.iso}`;
    const imageSrc = day.has_real_infographic
      ? imgBase(day.iso, lang) + ".webp"
      : null;

    const msg = isCht
      ? [`📅 今日：${day.date_cht}`, `🌤 天氣：${day.weather.badge_cht}`, ``, `✝ 今日金句（和合本）`, `「${day.scripture.cht}」`, `— ${day.scripture.ref_cht}`, ``, `🌿 靈修心語`, `${day.reflection.cht}`, ``, `🕊 每日靜思 · A Daily Moment`, pageUrl].join("\n")
      : [`📅 Today: ${day.date_eng}`, `🌤 Weather: ${day.weather.badge_eng}`, ``, `✝ Today's Scripture`, `"${day.scripture.niv}"`, `— ${day.scripture.ref_eng}`, ``, `🌿 Reflection`, `${day.reflection.eng}`, ``, `🕊 A reflection from:`, pageUrl].join("\n");

    if (imageSrc && navigator.canShare) {
      try {
        const resp = await fetch(imageSrc);
        const blob = await resp.blob();
        const file = new File([blob], `daily-moment-${day.iso}.png`, { type: blob.type || "image/png" });
        if (navigator.canShare({ files: [file] })) { await navigator.share({ files: [file], text: msg }); return; }
      } catch (e) {}
    }
    window.open("https://wa.me/?text=" + encodeURIComponent(msg), "_blank", "noopener");
  }

  return (
    <section className="featured" onClick={() => onOpen(day.iso)} role="button" tabIndex={0}
      onKeyDown={(e) => { if (e.key === "Enter") onOpen(day.iso); }}>
      <div className="featured-eyebrow featured-eyebrow-top">
        <span className="dot"></span>
        <span>{ui.today_label[lang]}</span>
      </div>
      {lightbox && <Lightbox src={imgBase(day.iso, lang) + ".webp"} onClose={() => setLightbox(false)} />}
      <div className="featured-image" style={{ cursor: day.has_real_infographic ? "zoom-in" : "default" }}
        onClick={day.has_real_infographic ? (e) => { e.stopPropagation(); setLightbox(true); } : undefined}>
        {day.has_real_infographic ? (
          <DayImage iso={day.iso} lang={lang} />
        ) : (
          <MiniInfographic day={day} lang={lang} />
        )}
      </div>
      <div className="featured-body">
        <div className="featured-eyebrow featured-eyebrow-body">
          <span className="dot"></span>
          <span>{ui.today_label[lang]}</span>
        </div>
        <div className="featured-date">{lang === "cht" ? day.date_cht : day.date_eng}</div>
        <div className="featured-date-sub">{lang === "cht" ? day.date_eng : day.date_cht}</div>

        <div className="featured-scripture">
          <div className="verse">
            {lang === "cht" ? `「${day.scripture.cht}」` : `"${day.scripture.niv}"`}
          </div>
          <div className="ref">— {lang === "cht" ? day.scripture.ref_cht.replace(/\s*和合本\s*/g,"").trim() : day.scripture.ref_eng.replace(/\s*NIV\s*/gi,"").trim()}</div>
          <div className="version-tag">{lang === "cht" ? "和合本" : "NIV"}</div>
        </div>

        <div className="featured-meta">
          <span className="meta-chip">
            <span className="ico"><Icon name={m.icon} size={14} /></span>
            {lang === "cht" ? day.weather.badge_cht : day.weather.badge_eng}
          </span>
          <span className="meta-chip">
            <span className="ico"><Icon name="leaf" size={14} /></span>
            {lang === "cht" ? day.reflection.cht : day.reflection.eng}
          </span>
        </div>

        <div className="featured-history">
          <div className="featured-history-row">
            <span>🌍</span>
            <span><strong>{day.history.world.year}{lang === "cht" ? "年" : ""}</strong> — {lang === "cht" ? day.history.world.cht : day.history.world.eng}</span>
          </div>
          <div className="featured-history-row">
            <span>🎂</span>
            <span><strong>{day.history.birth.year}{lang === "cht" ? "年" : ""}</strong> — {lang === "cht" ? day.history.birth.cht : day.history.birth.eng}</span>
          </div>
          <div className="featured-history-row">
            <span>🕊️</span>
            <span><strong>{day.history.death.year}{lang === "cht" ? "年" : ""}</strong> — {lang === "cht" ? day.history.death.cht : day.history.death.eng}</span>
          </div>
        </div>

        <div className="featured-actions">
          <a className="featured-cta">
            <span>{ui.read_today[lang]}</span>
            <Icon name="arrow-right" size={16} />
          </a>
          <button className="featured-share" onClick={handleFeaturedShare}>
            <Icon name="share" size={15} />
            <span>{lang === "cht" ? "分享" : "Share"}</span>
          </button>
        </div>
      </div>
    </section>
  );
}

// ─────────────────────────────────────────────
// Entry view
// ─────────────────────────────────────────────
const _CHT_ONES = ["", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
function numToCht(n) {
  n = parseInt(n, 10);
  if (n <= 0) return "";
  if (n < 10) return _CHT_ONES[n];
  if (n < 20) return "十" + _CHT_ONES[n % 10];
  if (n < 100) {
    const t = Math.floor(n / 10), o = n % 10;
    return _CHT_ONES[t] + "十" + (o ? _CHT_ONES[o] : "");
  }
  // 100–999
  const h = Math.floor(n / 100), rem = n % 100;
  const hundreds = _CHT_ONES[h] + "百";
  if (rem === 0) return hundreds;
  if (rem < 10) return hundreds + "零" + _CHT_ONES[rem];
  if (rem < 20) return hundreds + "一十" + _CHT_ONES[rem % 10];
  const t = Math.floor(rem / 10), o = rem % 10;
  return hundreds + _CHT_ONES[t] + "十" + (o ? _CHT_ONES[o] : "");
}

function dateToChtSpoken(iso) {
  const [y, mo, d] = iso.split("-").map(Number);
  return `${y}年${numToCht(mo)}月${numToCht(d)}日`;
}

function dateToEngSpoken(iso) {
  const [y, mo, d] = iso.split("-").map(Number);
  const ORD = ["","first","second","third","fourth","fifth","sixth","seventh","eighth","ninth","tenth",
    "eleventh","twelfth","thirteenth","fourteenth","fifteenth","sixteenth","seventeenth","eighteenth","nineteenth","twentieth",
    "twenty-first","twenty-second","twenty-third","twenty-fourth","twenty-fifth","twenty-sixth","twenty-seventh","twenty-eighth","twenty-ninth","thirtieth","thirty-first"];
  const MON = ["","January","February","March","April","May","June","July","August","September","October","November","December"];
  const ONES = ["","one","two","three","four","five","six","seven","eight","nine","ten","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen"];
  const TENS = ["","","twenty","thirty","forty","fifty","sixty","seventy","eighty","ninety"];
  function yearToEng(y) {
    const thou = Math.floor(y / 1000), rem = y % 1000, hund = Math.floor(rem / 100), to = rem % 100;
    const parts = [];
    if (thou) parts.push(ONES[thou] + " thousand");
    if (hund) parts.push(ONES[hund] + " hundred");
    if (to) {
      const prefix = parts.length ? "and " : "";
      if (to < 20) parts.push(prefix + ONES[to]);
      else parts.push(prefix + TENS[Math.floor(to / 10)] + (to % 10 ? "-" + ONES[to % 10] : ""));
    }
    return parts.join(" ");
  }
  return `The ${ORD[d]} of ${MON[mo]}, ${yearToEng(y)}`;
}

function imgBase(iso, lang) {
  return "assets/" + iso.replace(/-/g, "") + "/" + (lang === "cht" ? "cht" : "eng");
}
function DayImage({ iso, lang, alt, loading, className, onClick, style }) {
  const base = imgBase(iso, lang);
  return (
    <picture onClick={onClick} style={style}>
      <source srcSet={base + ".webp"} type="image/webp" />
      <img src={base + ".png"} alt={alt || ""} loading={loading} className={className} />
    </picture>
  );
}

function Lightbox({ src, onClose }) {
  const [scale, setScale] = React.useState(1);
  const [pos, setPos] = React.useState({ x: 0, y: 0 });
  const panning = React.useRef(false);
  const lastPan = React.useRef(null);
  const lastPinch = React.useRef(null);

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

  React.useEffect(() => { if (scale <= 1) setPos({ x: 0, y: 0 }); }, [scale]);

  const onWheel = (e) => {
    e.preventDefault();
    setScale(s => Math.max(1, Math.min(8, s * (e.deltaY < 0 ? 1.1 : 0.9))));
  };
  const onMouseDown = (e) => { panning.current = true; lastPan.current = { x: e.clientX, y: e.clientY }; };
  const onMouseMove = (e) => {
    if (!panning.current || !lastPan.current) return;
    setPos(p => ({ x: p.x + e.clientX - lastPan.current.x, y: p.y + e.clientY - lastPan.current.y }));
    lastPan.current = { x: e.clientX, y: e.clientY };
  };
  const onMouseUp = () => { panning.current = false; lastPan.current = null; };
  const onTouchStart = (e) => {
    if (e.touches.length === 2) {
      const dx = e.touches[0].clientX - e.touches[1].clientX;
      const dy = e.touches[0].clientY - e.touches[1].clientY;
      lastPinch.current = { dist: Math.hypot(dx, dy), scale };
    } else {
      lastPan.current = { x: e.touches[0].clientX, y: e.touches[0].clientY };
    }
  };
  const onTouchMove = (e) => {
    e.preventDefault();
    if (e.touches.length === 2 && lastPinch.current) {
      const dx = e.touches[0].clientX - e.touches[1].clientX;
      const dy = e.touches[0].clientY - e.touches[1].clientY;
      setScale(Math.max(1, Math.min(8, lastPinch.current.scale * (Math.hypot(dx, dy) / lastPinch.current.dist))));
    } else if (e.touches.length === 1 && lastPan.current && scale > 1) {
      setPos(p => ({ x: p.x + e.touches[0].clientX - lastPan.current.x, y: p.y + e.touches[0].clientY - lastPan.current.y }));
      lastPan.current = { x: e.touches[0].clientX, y: e.touches[0].clientY };
    }
  };
  const onTouchEnd = () => { lastPinch.current = null; lastPan.current = null; };

  return (
    <div className="lightbox-overlay"
      onClick={() => { if (scale <= 1) onClose(); }}
      onWheel={onWheel}
      onMouseDown={onMouseDown} onMouseMove={onMouseMove} onMouseUp={onMouseUp} onMouseLeave={onMouseUp}>
      <button className="lightbox-close" onClick={(e) => { e.stopPropagation(); onClose(); }} aria-label="Close">
        <Icon name="x" size={22} />
      </button>
      <img src={src} alt="" className="lightbox-img" draggable={false}
        style={{ transform: `translate(calc(-50% + ${pos.x}px), calc(-50% + ${pos.y}px)) scale(${scale})`, cursor: scale > 1 ? "grab" : "zoom-in" }}
        onClick={e => e.stopPropagation()}
        onTouchStart={onTouchStart} onTouchMove={onTouchMove} onTouchEnd={onTouchEnd}
      />
    </div>
  );
}

function refToChtSpoken(ref) {
  const m = ref.match(/^(.+?)\s*(\d+)\s*[：:]\s*(\d+)(?:\s*[-–]\s*(\d+))?/);
  if (!m) return ref;
  const book = m[1], chapter = m[2], verse1 = m[3], verse2 = m[4];
  const chapterWord = book.trim() === "詩篇" ? "篇" : "章";
  let spoken = book + numToCht(chapter) + chapterWord + numToCht(verse1);
  if (verse2) spoken += "到" + numToCht(verse2);
  spoken += "節";
  return spoken;
}

function refToEngSpoken(ref) {
  const cleaned = ref.replace(/\s*NIV\s*/gi, "").trim();
  const m = cleaned.match(/^(.+?)\s+(\d+)\s*:\s*(\d+)(?:\s*[-–]\s*(\d+))?/);
  if (!m) return cleaned;
  const book = m[1], chapter = parseInt(m[2]), verse1 = parseInt(m[3]), verse2 = m[4] ? parseInt(m[4]) : null;
  const ONES = ["","one","two","three","four","five","six","seven","eight","nine","ten","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen"];
  const TENS = ["","","twenty","thirty","forty","fifty","sixty","seventy","eighty","ninety"];
  function numToEng(n) {
    if (n < 20) return ONES[n];
    return TENS[Math.floor(n / 10)] + (n % 10 ? "-" + ONES[n % 10] : "");
  }
  let spoken = `${book}, chapter ${numToEng(chapter)}, verse ${numToEng(verse1)}`;
  if (verse2) spoken += ` to ${numToEng(verse2)}`;
  return spoken;
}

function CalendarPopup({ currentIso, onSelect, onClose, lang }) {
  const days = window.DAYS;
  const isoSet = new Set(days.map(d => d.iso));
  const [y, mo] = currentIso.split("-").map(Number);
  const [viewYear, setViewYear] = React.useState(y);
  const [viewMonth, setViewMonth] = React.useState(mo);

  const MONTH_CHT = ["","一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"];
  const MONTH_ENG = ["","January","February","March","April","May","June","July","August","September","October","November","December"];
  const DOW_CHT = ["日","一","二","三","四","五","六"];
  const DOW_ENG = ["Su","Mo","Tu","We","Th","Fr","Sa"];
  const dow = lang === "cht" ? DOW_CHT : DOW_ENG;

  function prevMonth() { if (viewMonth === 1) { setViewYear(v => v-1); setViewMonth(12); } else setViewMonth(m => m-1); }
  function nextMonth() { if (viewMonth === 12) { setViewYear(v => v+1); setViewMonth(1); } else setViewMonth(m => m+1); }

  const firstDay = new Date(viewYear, viewMonth - 1, 1).getDay();
  const daysInMonth = new Date(viewYear, viewMonth, 0).getDate();
  const cells = [];
  for (let i = 0; i < firstDay; i++) cells.push(null);
  for (let d = 1; d <= daysInMonth; d++) cells.push(d);

  function isoFor(d) { return `${viewYear}-${String(viewMonth).padStart(2,"0")}-${String(d).padStart(2,"0")}`; }

  return (
    <div className="cal-overlay" onClick={onClose}>
      <div className="cal-popup" onClick={e => e.stopPropagation()}>
        <div className="cal-header">
          <button className="cal-nav" onClick={prevMonth}><Icon name="chevron-left" size={14} /></button>
          <span className="cal-title">{lang === "cht" ? `${viewYear}年${MONTH_CHT[viewMonth]}` : `${MONTH_ENG[viewMonth]} ${viewYear}`}</span>
          <button className="cal-nav" onClick={nextMonth}><Icon name="chevron-right" size={14} /></button>
        </div>
        <div className="cal-grid">
          {dow.map(d => <div key={d} className="cal-dow">{d}</div>)}
          {cells.map((d, i) => {
            if (!d) return <div key={"e"+i} />;
            const iso = isoFor(d);
            const hasPost = isoSet.has(iso);
            const isCurrent = iso === currentIso;
            return (
              <button key={iso} className={"cal-day" + (isCurrent ? " current" : "") + (hasPost ? " has-post" : " no-post")}
                onClick={() => { if (hasPost) { onSelect(iso); onClose(); } }}
                disabled={!hasPost}>
                {d}
              </button>
            );
          })}
        </div>
      </div>
    </div>
  );
}

function EntryView({ iso, lang, onNav, onOpen }) {
  const ui = window.UI;
  const days = window.DAYS;
  const idx = days.findIndex(d => d.iso === iso);
  const day = days[idx];
  if (!day) return null;
  const prev = days[idx + 1]; // older
  const next = days[idx - 1]; // newer
  const m = moodFor(day);

  // Admin preview override — when admin uploads a day and clicks Preview,
  // it stashes blob URLs here so the entry shows the freshly-uploaded images.
  const previewUrls = (window.__ADMIN_PREVIEW_URLS && window.__ADMIN_PREVIEW_URLS[iso]) || null;
  const imageSrc = previewUrls
    ? (lang === "cht" ? previewUrls.cht : previewUrls.eng)
    : imgBase(day.iso, lang) + ".webp";

  // Whether a standalone HTML article exists for this language (loaded via iframe)
  const hasArticle = day.articles && day.articles[lang];
  const isoFlat = day.iso.replace(/-/g, "");
  const articleSrc = hasArticle ? `assets/${isoFlat}/devotional_${lang}_${isoFlat}.html` : null;
  // Optional in-page preview from admin (Blob URL)
  const articlePreviewUrl = (window.__ADMIN_PREVIEW_ARTICLES && window.__ADMIN_PREVIEW_ARTICLES[iso] && window.__ADMIN_PREVIEW_ARTICLES[iso][lang]) || null;
  const finalArticleSrc = articlePreviewUrl || articleSrc;

  const [lightbox, setLightbox] = React.useState(false);
  const [showCalendar, setShowCalendar] = React.useState(false);
  const [speaking, setSpeaking] = React.useState(false);
  const [fontSize, setFontSize] = React.useState(1); // multiplier: 1 / 1.2 / 1.4
  const iframeRef = React.useRef(null);
  const speakTimerRef = React.useRef(null);

  function handleIframeLoad() {
    const iframe = iframeRef.current;
    if (!iframe) return;
    try {
      const doc = iframe.contentDocument;
      // Normalise CHT text in iframe
      const rules = [
        [/義大利/g, "意大利"],
        [/裡/g, "裏"],
        [/著(?!作|名)/g, "着"],
      ];
      const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT);
      let node;
      while ((node = walker.nextNode())) {
        let t = node.nodeValue;
        rules.forEach(([pat, rep]) => { t = t.replace(pat, rep); });
        if (t !== node.nodeValue) node.nodeValue = t;
      }
      const h = doc.body.scrollHeight;
      if (h > 0) iframe.style.height = h + "px";
    } catch (e) {}
  }

  React.useEffect(() => { window.scrollTo({ top: 0, behavior: "instant" }); }, [iso]);
  React.useEffect(() => { return () => window.speechSynthesis && window.speechSynthesis.cancel(); }, [iso]);

  function handleSpeak() {
    if (!window.speechSynthesis) return;
    if (speaking) {
      window.speechSynthesis.cancel();
      clearTimeout(speakTimerRef.current);
      setSpeaking(false);
      return;
    }
    const isCht = lang === "cht";

    // Pull prayer + reflection from same-origin iframe
    let prayerText = "";
    let reflectionText = isCht ? day.reflection.cht : day.reflection.eng;
    try {
      const doc = iframeRef.current && iframeRef.current.contentDocument;
      if (doc) {
        prayerText = doc.querySelector(".prayer-text")?.textContent?.trim() || "";
        reflectionText = doc.querySelector(".reflection-text")?.textContent?.trim() || reflectionText;
      }
    } catch (e) {}

    const dateSpoken = isCht ? dateToChtSpoken(day.iso) : dateToEngSpoken(day.iso);
    const scriptureText = isCht ? day.scripture.cht : day.scripture.niv;
    const ref = isCht
      ? refToChtSpoken(day.scripture.ref_cht.replace(/\s*和合本\s*/g, "").trim()) + "，和合本"
      : refToEngSpoken(day.scripture.ref_eng) + ", New International Version";
    const closing = isCht ? "每一天都是新的開始，願您重新得力" : "Every day is a new day, be enchanted.";
    const sep = isCht ? "。" : ". ";

    const parts = [dateSpoken,
      isCht ? "今日金句" : "Today's Scripture", scriptureText + sep + ref];
    if (prayerText) parts.push(isCht ? "讓我們一同禱告" : "Today's Prayer", prayerText);
    parts.push(isCht ? "每日靜思" : "A Daily Moment", reflectionText, closing);

    const audio = window.__musicAudio;

    // CHT pronunciation corrections: display text → spoken text
    const CHT_PRONOUNCE = [
      [/聖名/g, "聖明"],
    ];
    function normaliseCht(t) {
      return CHT_PRONOUNCE.reduce((s, [pat, rep]) => s.replace(pat, rep), t);
    }

    function startReading() {
      const uttLang = isCht ? "zh-HK" : "en-GB";
      let cancelled = false;

      // Pick a random voice — prefer female or male, strictly matching the target locale
      const allVoices = window.speechSynthesis.getVoices();
      const femaleKeywords = ["female", "woman", "girl", "zira", "susan", "karen", "samantha", "fiona", "victoria", "moira", "sin-ji"];
      const maleKeywords   = ["male", "man", "david", "mark", "daniel", "alex", "fred", "thomas"];
      // For Cantonese, only use zh-HK voices — never zh-CN (Mandarin)
      const targetLang = isCht ? "zh-hk" : "en";
      const langVoices = allVoices.filter(v => v.lang.toLowerCase().startsWith(targetLang));
      const femaleVoices = langVoices.filter(v => femaleKeywords.some(k => v.name.toLowerCase().includes(k)));
      const maleVoices   = langVoices.filter(v => maleKeywords.some(k => v.name.toLowerCase().includes(k)));
      const useFemale = Math.random() < 0.5;
      const pool = useFemale && femaleVoices.length ? femaleVoices
                 : !useFemale && maleVoices.length  ? maleVoices
                 : langVoices;
      // Only assign a voice if we found a matching one — otherwise let utt.lang drive the selection
      const chosenVoice = pool.length ? pool[Math.floor(Math.random() * pool.length)] : null;

      function speakNext(idx) {
        if (cancelled || idx >= parts.length) {
          if (!cancelled) {
            setSpeaking(false);
            // After closing line finishes, let music play 5 more seconds then stop
            if (audio) speakTimerRef.current = setTimeout(() => audio.pause(), 5000);
          }
          return;
        }
        const utt = new SpeechSynthesisUtterance(isCht ? normaliseCht(parts[idx]) : parts[idx]);
        utt.lang = uttLang;
        utt.rate = isCht ? 0.9 : 0.85;
        if (chosenVoice) utt.voice = chosenVoice;
        utt.onend = () => { speakTimerRef.current = setTimeout(() => speakNext(idx + 1), 1000); };
        utt.onerror = (e) => { if (e.error !== "canceled") setSpeaking(false); else cancelled = true; };
        window.speechSynthesis.speak(utt);
      }

      // Patch cancel so the onerror branch knows it's intentional
      const origCancel = window.speechSynthesis.cancel.bind(window.speechSynthesis);
      window.speechSynthesis.cancel = () => { cancelled = true; clearTimeout(speakTimerRef.current); origCancel(); window.speechSynthesis.cancel = origCancel; };

      speakNext(0);
    }

    // Always start/resume music, wait 2 s, then begin reading
    if (audio) audio.play().catch(() => {});
    setSpeaking(true);
    speakTimerRef.current = setTimeout(startReading, 2000);
  }

  const fontSizes = [1, 1.2, 1.45];
  const fontLabels = ["A", "A+", "A++"];

  async function handleShare() {
    const isCht = lang === "cht";
    const pageUrl = `https://daily.moment.bridge-n-build.org/#entry/${iso}`;

    const msg = isCht
      ? [
          `📅 今日：${day.date_cht}`,
          `🌤 天氣：${day.weather.badge_cht}`,
          ``,
          `✝ 今日金句（和合本）`,
          `「${day.scripture.cht}」`,
          `— ${day.scripture.ref_cht}`,
          ``,
          `🌿 靈修心語`,
          `${day.reflection.cht}`,
          ``,
          `🕊 每日靜思 · A Daily Moment`,
          pageUrl,
        ].join("\n")
      : [
          `📅 Today: ${day.date_eng}`,
          `🌤 Weather: ${day.weather.badge_eng}`,
          ``,
          `✝ Today's Scripture`,
          `"${day.scripture.niv}"`,
          `— ${day.scripture.ref_eng}`,
          ``,
          `🌿 Reflection`,
          `${day.reflection.eng}`,
          ``,
          `🕊 A reflection from:`,
          pageUrl,
        ].join("\n");

    // Try Web Share API with image file (works on mobile)
    if ((day.has_real_infographic || previewUrls) && imageSrc && navigator.canShare) {
      try {
        const resp = await fetch(imageSrc);
        const blob = await resp.blob();
        const file = new File([blob], `daily-moment-${iso}.png`, { type: blob.type || "image/png" });
        if (navigator.canShare({ files: [file] })) {
          await navigator.share({ files: [file], text: msg });
          return;
        }
      } catch (e) {}
    }

    // Fallback: open WhatsApp with text
    window.open("https://wa.me/?text=" + encodeURIComponent(msg), "_blank", "noopener");
  }

  const toolbarControls = (
    <>
      <button className={"toolbar-btn" + (speaking ? " active" : "")} onClick={handleSpeak} title={lang === "cht" ? "朗讀" : "Read aloud"}>
        <span style={{ fontSize: 15 }}>{speaking ? "⏹" : "🔊"}</span>
        <span>{speaking ? (lang === "cht" ? "停止" : "Stop") : (lang === "cht" ? "朗讀" : "Read aloud")}</span>
      </button>
      <div className="toolbar-font-group" style={{ margin: 0 }}>
        {fontLabels.map((label, i) => (
          <button key={i} className={"toolbar-font-btn" + (fontSize === i ? " active" : "")} onClick={() => setFontSize(i)}>{label}</button>
        ))}
      </div>
    </>
  );

  return (
    <div className="page">
      <div className="entry-topnav">
        <div className="entry-topnav-left">
          <button className="toolbar-btn" onClick={handleShare} title={lang === "cht" ? "分享" : "Share"}>
            <Icon name="share" size={15} />
            <span>{lang === "cht" ? "分享" : "Share"}</span>
          </button>
          <button className="entry-back" onClick={() => onNav("archive")}>
            <Icon name="arrow-left" size={14} />
            <span>{ui.back_to_archive[lang]}</span>
          </button>
        </div>
        <div className="day-stepper" style={{ position: "relative" }}>
          <button onClick={() => prev && onOpen(prev.iso)} disabled={!prev} aria-label="Previous day" title={ui.prev_day[lang]}>
            <Icon name="chevron-left" size={16} />
          </button>
          <button className="label label-btn" onClick={() => setShowCalendar(v => !v)} title={lang === "cht" ? "選擇日期" : "Pick a date"}>
            {lang === "cht" ? day.date_cht : day.date_eng}
          </button>
          <button onClick={() => next && onOpen(next.iso)} disabled={!next} aria-label="Next day" title={ui.next_day[lang]}>
            <Icon name="chevron-right" size={16} />
          </button>
          {showCalendar && <CalendarPopup currentIso={iso} onSelect={onOpen} onClose={() => setShowCalendar(false)} lang={lang} />}
        </div>
        <div className="entry-topnav-right">
          {toolbarControls}
        </div>
      </div>

      <div className="entry">
        {lightbox && <Lightbox src={imageSrc} onClose={() => setLightbox(false)} />}
        <div className="entry-image-col">
          <div className="entry-image-frame" style={{ cursor: (day.has_real_infographic || previewUrls) ? "zoom-in" : "default" }}
            onClick={() => { if ((day.has_real_infographic || previewUrls) && imageSrc) setLightbox(true); }}>
            {(day.has_real_infographic || previewUrls) && imageSrc ? (
              <DayImage iso={day.iso} lang={lang} alt={lang === "cht" ? "資訊圖" : "Infographic"} />
            ) : (
              <MiniInfographic day={day} lang={lang} />
            )}
          </div>
          <div className="entry-image-actions">
            <button className="image-action" onClick={() => {
              const base = imgBase(day.iso, lang);
              const tryDownload = (url) => fetch(url).then(r => {
                if (!r.ok) throw new Error();
                return r.blob();
              }).then(blob => {
                const a = document.createElement("a");
                a.href = URL.createObjectURL(blob);
                a.download = "daily-moment-" + day.iso + "-" + lang + (url.endsWith(".webp") ? ".webp" : ".png");
                a.click();
                URL.revokeObjectURL(a.href);
              });
              tryDownload(base + ".webp").catch(() => tryDownload(base + ".png").catch(() => {}));
            }}>
              <Icon name="download" size={14} />
              <span>{ui.download_image[lang]}</span>
            </button>
            <button className="image-action image-action-share" onClick={handleShare}>
              <Icon name="share" size={14} />
              <span>{ui.share[lang]}</span>
            </button>
          </div>
        </div>

        <div className="entry-toolbar-float">
          {toolbarControls}
        </div>

        <article className="article-col">
          {finalArticleSrc ? (
            <div className="article-iframe-wrap">
              <div className="article-iframe-bar">
                <div className="article-iframe-meta">
                  <span className="dot-light"></span>
                  <span>{lang === "cht" ? "完整文章" : "Full article"}</span>
                </div>
                <div className="article-iframe-date-center">
                  <span className="article-iframe-date">{lang === "cht" ? day.date_cht : day.date_eng}</span>
                </div>
                <div className="article-iframe-actions">
                  <a className="iframe-action" href={finalArticleSrc} target="_blank" rel="noopener" title={lang === "cht" ? "在新分頁開啟" : "Open in new tab"}>
                    <span style={{ fontSize: 15, lineHeight: 1 }}>⛶</span>
                    <span>{lang === "cht" ? "新分頁" : "New tab"}</span>
                  </a>
                </div>
              </div>
              <iframe
                key={finalArticleSrc}
                ref={iframeRef}
                src={finalArticleSrc}
                className="article-iframe"
                title={(lang === "cht" ? day.date_cht : day.date_eng) + " — full article"}
                loading="lazy"
                onLoad={handleIframeLoad}
                style={{ zoom: fontSizes[fontSize] }}
              />
            </div>
          ) : (
            <div style={{ zoom: fontSizes[fontSize] }}><>
              <h2 className="article-title">{lang === "cht" ? "每日靜思" : "A Daily Moment"}</h2>
              <div className="article-date">{lang === "cht" ? day.date_cht : day.date_eng}</div>
              <div className="article-no-html-note">
                {lang === "cht"
                  ? "此語言的完整文章尚未提供。以下顯示結構化內容。"
                  : "Full article for this language not yet available — showing structured content below."}
              </div>
              <div className="article-divider"></div>

              <section className="article-section">
                <div className="article-section-eyebrow">{ui.weather_today[lang]}</div>
                <div className="weather-card">
                  <span className="badge-pill">
                    <Icon name={m.icon} size={13} />
                    {lang === "cht" ? day.weather.badge_cht : day.weather.badge_eng}
                  </span>
                  <div className="article-body">
                    <p>{lang === "cht" ? day.weather.cht : day.weather.eng}</p>
                    <p style={{ fontSize: 13, color: "var(--ink-muted)", marginTop: 12 }}>
                      <Icon name="leaf" size={12} /> {day.history.custom.year} — {lang === "cht" ? day.history.custom.cht : day.history.custom.eng}
                    </p>
                  </div>
                </div>
              </section>

              <section className="article-section">
                <div className="article-section-eyebrow">{lang === "cht" ? "歷史上的今天" : "On this day"}</div>
                <div className="history-rows">
                  <div className="history-card">
                    <div className="icon">🌍</div>
                    <div className="label">{ui.world_event[lang]}</div>
                    <div className="year">{day.history.world.year}</div>
                    <div className="text">{lang === "cht" ? day.history.world.cht : day.history.world.eng}</div>
                  </div>
                  <div className="history-card">
                    <div className="icon">🎂</div>
                    <div className="label">{ui.born_today[lang]}</div>
                    <div className="year">{day.history.birth.year}</div>
                    <div className="text">{lang === "cht" ? day.history.birth.cht : day.history.birth.eng}</div>
                  </div>
                  <div className="history-card">
                    <div className="icon">🕊️</div>
                    <div className="label">{ui.passed_today[lang]}</div>
                    <div className="year">{day.history.death.year}</div>
                    <div className="text">{lang === "cht" ? day.history.death.cht : day.history.death.eng}</div>
                  </div>
                </div>
              </section>

              <section className="article-section">
                <div className="scripture-panel">
                  <div className="label">
                    ✝ {lang === "cht" ? "今日金句" : "Today's Scripture"}
                  </div>
                  <div className="verse">
                    {lang === "cht" ? `「${day.scripture.cht}」` : `"${day.scripture.niv}"`}
                  </div>
                  <div className="verse-bar"></div>
                  <div className="ref">— {lang === "cht" ? day.scripture.ref_cht.replace(/\s*和合本\s*/g,"").trim() : day.scripture.ref_eng.replace(/\s*NIV\s*/gi,"").trim()}</div>
                  <div className="version-tag">{lang === "cht" ? "和合本" : "NIV"}</div>
                </div>
              </section>

              <section className="article-section">
                <div className="article-section-eyebrow">{ui.reflection[lang]}</div>
                <div className="reflection-card">
                  <p className="text">
                    <span className="heart">♥&nbsp;&nbsp;</span>
                    {lang === "cht" ? day.reflection.cht : day.reflection.eng}
                  </p>
                </div>
              </section>

              <div className="article-sources">
                {ui.sources_label[lang]}: {ui.sources_line[lang]}
              </div>
            </></div>
          )}
        </article>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────
// About / Reflection view
// ─────────────────────────────────────────────
function AboutView({ lang }) {
  const a = window.ABOUT;
  const paragraphs = lang === "cht" ? a.paragraphs_cht : a.paragraphs_eng;
  return (
    <div className="about-page">
      <div className="about-eyebrow">
        <span className="line"></span>
        <Icon name="cross" size={12} />
        <span>{a.eyebrow[lang]}</span>
        <Icon name="cross" size={12} />
        <span className="line"></span>
      </div>
      <h2 className="about-title">{a.title[lang]}</h2>

      <div className="about-body">
        {paragraphs.map((p, i) => (
          <p key={i} className={"about-para" + (i === paragraphs.length - 1 ? " about-para-final" : "")}>
            {p}
          </p>
        ))}
      </div>

      <a className="rthk-card" href={a.link.href} target="_blank" rel="noopener">
        <div className="rthk-mark">
          <span style={{ fontSize: 24 }}>📻</span>
        </div>
        <div className="rthk-text">
          <div className="rthk-eyebrow">RTHK · Radio 4</div>
          <div className="rthk-title">{lang === "cht" ? "晚禱" : "Reflections"}</div>
          <div className="rthk-sub">{lang === "cht" ? "每周一至五 · 23:57 (UTC+08:00) · 播放中" : "Monday to Friday · 23:57 (UTC+08:00) · still on air"}</div>
        </div>
        <div className="rthk-cta">
          <span>{lang === "cht" ? a.link.label_cht : a.link.label_eng}</span>
          <Icon name="arrow-right" size={14} />
        </div>
      </a>

      <div className="about-signoff">
        <Icon name="leaf" size={14} />
        <span>{lang === "cht" ? "Bridge & Build × 每日靜思" : "Bridge & Build × A Daily Moment"}</span>
      </div>
    </div>
  );
}

function DisclaimerView({ lang, onNav }) {
  const isCht = lang === "cht";
  const sections = isCht ? [
    { title: "聖經經文 Scripture",          body: "中文經文引用《聖經和合本》，英文經文引用 New International Version (NIV)。版權歸原出版機構所有，經文僅供靈修默想之用。" },
    { title: "天氣資訊 Weather",            body: "天氣資料來源為香港天文台（HKO），實際天氣情況請以香港天文台官方公佈為準。" },
    { title: "歷史資料 Historical Content", body: "歷史事件及人物資料引用自維基百科等公開來源，僅供文化及教育參考之用。" },
    { title: "禱告與靈修心語 Prayer & Reflection", body: "每日禱告、靈修心語及相關圖像，均以當日歷史事件與聖經經文為基礎，由 AI 輔助撰寫及製作，旨在引發個人默想與反思，內容與任何宗派立場無關。如有更深入的屬靈需要，邀請您尋求所屬教會牧師或傳道人的牧養與指引。" },
    { title: "外部連結 External Links",     body: "本網站包含指向外部網站的連結，我們對該等網站之內容概不負責。" },
    { title: "內容更新 Content",            body: "本網站內容定期更新，Bridge & Build 保留修改內容之權利，恕不另行通知。" },
    { title: "聯絡我們 Contact",            body: "如對內容有任何意見或查詢，歡迎與我們聯絡。", link: "bridge-n-build.org/contact" },
  ] : [
    { title: "Scripture",         body: "Chinese scripture is drawn from the Chinese Union Version (和合本 CUV). English scripture is drawn from the New International Version (NIV). All rights remain with the respective copyright holders. Scripture is shared solely for personal devotional use." },
    { title: "Weather",           body: "Weather information is sourced from the Hong Kong Observatory (HKO). For the most accurate and up-to-date forecasts, please refer to the HKO's official announcements." },
    { title: "Historical Content",body: "Historical events and biographical information are referenced from Wikipedia and other publicly available sources, for cultural and educational purposes only." },
    { title: "Prayer & Reflection", body: "The daily prayer, reflection, and accompanying visuals are AI-assisted, drawing from This Day in History and the day's scripture. They are intended to encourage personal reflection and are not affiliated with any denomination or theological position. We warmly invite you to seek the pastoral care and guidance of your pastor or minister should you have deeper spiritual needs." },
    { title: "External Links",    body: "This site may contain links to external websites. We are not responsible for the content of those sites." },
    { title: "Content",           body: "Content on this site is updated regularly. Bridge & Build reserves the right to modify any content without prior notice." },
    { title: "Contact",           body: "If you have any questions or feedback, please don't hesitate to get in touch.", link: "bridge-n-build.org/contact" },
  ];

  return (
    <div className="disclaimer-page about-page">
      <div className="about-eyebrow">
        <span className="line"></span>
        <Icon name="cross" size={12} />
        <span>{isCht ? "📜 免責聲明" : "📜 Disclaimer"}</span>
        <Icon name="cross" size={12} />
        <span className="line"></span>
      </div>
      <h2 className="about-title">{isCht ? "免責聲明" : "Disclaimer"}</h2>
      <div className="about-body">
        <p className="about-para">{isCht
          ? "每日靜思 A Daily Moment 是由 Bridge & Build 製作的雙語靈修網站，願每一天的內容都能為您帶來片刻的平靜與安慰。"
          : "A Daily Moment 每日靜思 is a bilingual daily devotional site created by Bridge & Build. We hope each day's content brings you a quiet moment of peace and comfort."
        }</p>
        {sections.map((s, i) => (
          <div key={i} className="disclaimer-section">
            <div className="disclaimer-section-title">{s.title}</div>
            <div className="disclaimer-section-body">
              {s.body}
              {s.link && <><br /><a href={"https://" + s.link} target="_blank" rel="noopener">{s.link} ↗</a></>}
            </div>
          </div>
        ))}
      </div>
      <div className="about-signoff">
        <Icon name="leaf" size={14} />
        <span>{isCht ? "Bridge & Build × 每日靜思" : "Bridge & Build × A Daily Moment"}</span>
      </div>
    </div>
  );
}

Object.assign(window, { ArchiveView, EntryView, AboutView, DisclaimerView, FeaturedToday });
