// app.jsx — 好學生筆記 首頁主邏輯

const { useState, useEffect, useRef, useMemo } = React;

// ─── Nav ───────────────────────────────────────────────────────────────────
// 全用 href + 讓 CSS(html { scroll-behavior: smooth }、.shelf { scroll-margin-top })
// 處理:點擊有 smooth scroll + 80px nav 緩衝 + URL 會更新 hash(可分享 / 右鍵新分頁)。
// 「關於」改連到 GENAI Web(對外站,target=_blank)。
function Nav() {
  const [scrolled, setScrolled] = useState(false);
  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 60);
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
  return (
    <nav className={"nav" + (scrolled ? " scrolled" : "")}>
      <a href="#top" className="nav-brand">好學生筆記</a>
      <div className="nav-links">
        <a className="hide-mb" href="#ocean">慢讀</a>
        <a href="#shelf-public">公開活動</a>
        <a href="#shelf-seminar">研討會</a>
        <a href="#shelf-reading">讀書會</a>
        <span className="nav-dot hide-mb"></span>
        <a
          className="hide-mb"
          href="https://shuotao.github.io/GENAI/web/index.html"
          target="_blank"
          rel="noopener noreferrer"
        >
          關於
        </a>
      </div>
    </nav>
  );
}

// ─── Hero ──────────────────────────────────────────────────────────────────
function Hero({ heroVariant, manifesto }) {
  const titles = ['好', '學', '生', '筆', '記'];

  const taglines = {
    default: (
      <>
        在 AI 替我們摘要之前，我們先學會把<em>每一個字</em>讀完。<br />
        一場演講的份量，藏在那些沒被剪掉的停頓裡。
      </>
    ),
    quiet: (
      <>
        這裡不做摘要、不做精華、不做你的二手筆記。<br />
        留下的是講者那一整週準備過、現場想過、停頓過的，整本。
      </>
    ),
    invitation: (
      <>
        如果你曾經在某場演講裡被某句話打到，<br />
        那句話的上下三段，現在還在這裡等你。
      </>
    ),
  };

  return (
    <section className="hero" id="top">
      <div className="hero-eyebrow">A library of unsummarized lectures · est. 2026</div>
      <h1 className="hero-title">
        {titles.map((c, i) => (
          <span key={i} className="ch" style={{ animationDelay: `${0.4 + i * 0.12}s` }}>{c}</span>
        ))}
      </h1>
      <p className="hero-tagline">{taglines[heroVariant] || taglines.default}</p>
      <HeroWave />
      <div className="hero-scroll-cue">scroll / 慢慢往下</div>
    </section>
  );
}

function HeroWave() {
  // 8 bars that breathe like a paused waveform — pause = not-yet-played, the
  // recording is still sitting there waiting for the listener.
  const bars = [16, 32, 22, 44, 18, 38, 28, 20];
  return (
    <svg className="hero-wave" viewBox="0 0 720 64" preserveAspectRatio="none" aria-hidden="true">
      <line x1="0" y1="32" x2="720" y2="32" stroke="var(--ink-faint)" strokeWidth="0.5" />
      {bars.flatMap((h, i) => {
        const x = (i + 0.5) * (720 / bars.length);
        return (
          <g key={i}>
            <line
              x1={x} x2={x}
              y1={32 - h} y2={32 + h}
              stroke="var(--ink)"
              strokeWidth="1.2"
              strokeLinecap="round"
              style={{
                animation: `waveBreathe ${2.4 + (i % 3) * 0.4}s ${i * 0.15}s ease-in-out infinite`,
                transformOrigin: `${x}px 32px`,
              }}
            />
          </g>
        );
      })}
      <style>{`
        @keyframes waveBreathe {
          0%, 100% { transform: scaleY(0.35); }
          50%      { transform: scaleY(1); }
        }
      `}</style>
    </svg>
  );
}

// ─── Word Ocean ────────────────────────────────────────────────────────────
function WordOcean({ statement, attribution, oceanSpeed }) {
  const rows = window.WORD_OCEAN || [];
  // Build 6 rows by reshuffling source lines. Concatenate each row twice for
  // seamless loop with translateX(-50%).
  const built = useMemo(() => {
    const out = [];
    for (let r = 0; r < 6; r++) {
      const order = rows.slice().sort(() => 0.5 - ((r * 37 + 11) % 7) / 7 + Math.random() * 0.1);
      out.push(order);
    }
    return out;
  }, [rows]);

  const speedScale = { slow: 1.6, medium: 1.0, fast: 0.55 }[oceanSpeed] || 1.0;

  return (
    <section className="ocean" id="ocean">
      <div className="ocean-label">word ocean · 慢讀體感</div>
      <div className="ocean-rows">
        {built.map((lines, i) => (
          <div
            key={i}
            className={`ocean-row r${i}` + (i % 2 ? ' reverse' : '')}
            style={{ animationDuration: `calc(var(--row-${i}-dur, 80s) * ${speedScale})` }}
          >
            {[...lines, ...lines, ...lines].map((line, j) => (
              <span key={j}>{line}  ·  {lines[(j + 1) % lines.length]}</span>
            ))}
          </div>
        ))}
      </div>
      <div className="ocean-statement">
        <h3>{statement}</h3>
        <p>{attribution}</p>
      </div>
    </section>
  );
}

// ─── Section Head ──────────────────────────────────────────────────────────
function SectionHead({ eyebrow, title, sub }) {
  return (
    <div className="section-head">
      <div className="eyebrow">{eyebrow}</div>
      <h2>{title}</h2>
      {sub && <p>{sub}</p>}
    </div>
  );
}

// ─── Bookshelf ─────────────────────────────────────────────────────────────
function Shelves() {
  const data = window.SHELVES || [];
  return (
    <section className="shelves" id="shelves">
      <SectionHead
        eyebrow="three shelves · 三道書架"
        title="一架一類，慢慢往下加。"
        sub="三道書架都已開張，書脊會隨每場新筆記橫向長出去。"
      />
      {data.map((shelf, idx) => (
        <Shelf key={shelf.id} shelf={shelf} idx={idx} />
      ))}
    </section>
  );
}

function Shelf({ shelf, idx }) {
  const catId = shelf.id; // 'public' | 'seminar' | 'reading'
  return (
    <div className="shelf" id={`shelf-${catId}`}>
      <div className="shelf-meta">
        <div className="shelf-meta-left">
          <div className="shelf-label-en">
            shelf · {String(idx + 1).padStart(2, '0')} · {shelf.labelEn}
          </div>
          <div className="shelf-label">
            {shelf.label}
            <span className="num">({shelf.books.filter((b) => !b.placeholder).length} 本)</span>
          </div>
        </div>
        <div className="shelf-desc">{shelf.description}</div>
      </div>
      <div className="shelf-scroller">
        <div className="shelf-rail">
          {shelf.books.map((book, i) => (
            <Spine key={book.id} book={book} cat={catId} index={i} total={shelf.books.length} />
          ))}
          {/* trailing whitespace so the rail can scroll a little past last spine */}
          <div style={{ flexShrink: 0, width: 80 }}></div>
        </div>
      </div>
    </div>
  );
}

// ─── Spine ─────────────────────────────────────────────────────────────────
function Spine({ book, cat, index }) {
  const spineRef = useRef(null);
  const quoteIdx = useMemo(() => {
    if (!book.quotes || book.quotes.length === 0) return -1;
    return Math.floor(Math.random() * book.quotes.length);
  }, [book.id]);
  const quote = quoteIdx >= 0 ? book.quotes[quoteIdx] : null;

  // Kindle 風開書過場:書脊原地放大成全螢幕封面 → 末段淡到紙色 → 同分頁導航
  const handleClick = () => {
    if (book.placeholder || !book.url) return;
    const el = spineRef.current;
    if (!el) { window.location.href = book.url; return; }

    const rect = el.getBoundingClientRect();
    const startBg = getComputedStyle(el).backgroundColor;
    const paperBg =
      getComputedStyle(document.documentElement).getPropertyValue('--paper').trim() || '#f4ece0';

    const overlay = document.createElement('div');
    overlay.style.cssText = `
      position: fixed;
      left: ${rect.left}px;
      top: ${rect.top}px;
      width: ${rect.width}px;
      height: ${rect.height}px;
      background: ${startBg};
      z-index: 9999;
      border-radius: 1px;
      box-shadow: 0 24px 48px rgba(0,0,0,0.35);
      transition:
        left 600ms cubic-bezier(0.16, 1, 0.3, 1),
        top 600ms cubic-bezier(0.16, 1, 0.3, 1),
        width 600ms cubic-bezier(0.16, 1, 0.3, 1),
        height 600ms cubic-bezier(0.16, 1, 0.3, 1),
        background-color 260ms 420ms ease,
        border-radius 600ms cubic-bezier(0.16, 1, 0.3, 1);
    `;
    document.body.appendChild(overlay);
    document.body.style.overflow = 'hidden';

    requestAnimationFrame(() => {
      overlay.style.left = '0px';
      overlay.style.top = '0px';
      overlay.style.width = '100vw';
      overlay.style.height = '100vh';
      overlay.style.backgroundColor = paperBg;
      overlay.style.borderRadius = '0';
    });

    setTimeout(() => { window.location.href = book.url; }, 700);
  };

  const style = {
    height: book.height,
    width: book.width,
  };

  const numLabel = `№ ${String(index + 1).padStart(3, '0')}`;

  return (
    <div
      ref={spineRef}
      className={'spine' + (book.placeholder ? ' placeholder' : '')}
      data-cat={cat}
      data-shade={book.spineShade || 0}
      style={style}
      onClick={handleClick}
      role="button"
      tabIndex={0}
    >
      <span className="spine-band"></span>
      <div className="spine-no">{numLabel}</div>
      <div className="spine-title">{book.title}</div>
      <span className="spine-band bottom"></span>

      <SpineCard book={book} quote={quote} />
    </div>
  );
}

function SpineCard({ book, quote }) {
  const wordsLabel = book.words ? `${book.words.toLocaleString()} 字` : '—';
  return (
    <div className="spine-card">
      <div className="spine-card-title">
        {book.title}
        {book.subtitle && <span style={{ fontWeight: 300, color: 'var(--ink-mute)' }}> · {book.subtitle}</span>}
      </div>
      {quote ? (
        <div className="spine-card-quote">「{quote}」</div>
      ) : (
        <div className="spine-card-quote" style={{ color: 'var(--ink-mute)', fontStyle: 'italic' }}>
          逐字稿尚未上線，先佔個位置。
        </div>
      )}
      <div className="spine-card-meta">
        <span>{book.date}</span>
        <span>{book.duration}</span>
        <span>{wordsLabel}</span>
      </div>
    </div>
  );
}

// ─── Manifesto / Closing ───────────────────────────────────────────────────
function Manifesto({ text }) {
  // text is a longer-form statement; split on blank lines into paragraphs.
  const paragraphs = text.split(/\n\n+/).map((p) => p.trim()).filter(Boolean);
  return (
    <section className="manifesto" id="manifesto">
      <div className="manifesto-mark"></div>
      <div className="manifesto-text">
        {paragraphs.map((p, i) => (
          <p key={i}>{p}</p>
        ))}
      </div>
    </section>
  );
}

function Footer() {
  return (
    <footer className="footer">
      <div>好學生筆記 · A Library of Unsummarized Talks</div>
      <div>goodedunote.web.app · 2026 —</div>
      <div className="footer-license">
        程式碼 MIT · 站台文案與筆記{' '}
        <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener noreferrer">
          CC BY 4.0
        </a>
        {' '}· 講者話語著作權歸各場講者個人
      </div>
    </footer>
  );
}

// ─── App ───────────────────────────────────────────────────────────────────
function App() {
  const [t, setTweak] = useTweaks(window.TWEAK_DEFAULTS);

  // Sync palette + motion to <html> attributes so CSS variables flip globally.
  useEffect(() => {
    document.documentElement.setAttribute('data-palette', t.palette);
    document.documentElement.setAttribute('data-motion', t.motionLevel);
  }, [t.palette, t.motionLevel]);

  // 修 React-Babel render 比瀏覽器預設 anchor scroll 慢造成的 race:
  // 從書本回連 ../#shelf-reading 時,瀏覽器第一時間找不到 #shelf-reading
  // (此元素由 React render 後才存在)→ 預設滾到 hero。
  // 等 mount + Babel compile + 字體 + scroll-snap layout 穩定後,再主動
  // scrollIntoView 一次,讓 hash anchor 真正落地到目標書架。
  useEffect(() => {
    const hash = window.location.hash.slice(1);
    if (!hash) return;
    const timer = setTimeout(() => {
      document.getElementById(hash)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
    }, 120);
    return () => clearTimeout(timer);
  }, []);

  return (
    <>
      <Nav />
      <Hero heroVariant={t.heroVariant} manifesto={t.manifesto} />

      {t.showWordOcean && (
        <WordOcean
          statement={t.manifesto}
          attribution="— 好學生筆記，零省略宣言"
          oceanSpeed={t.oceanSpeed}
        />
      )}

      <Shelves />

      <Manifesto text={t.closing} />
      <Footer />

      <TweaksPanel title="Tweaks · 首頁設計">
        <TweakSection label="色溫 · Palette" />
        <TweakRadio
          label="底色"
          value={t.palette}
          options={[
            { value: 'earth', label: '大地' },
            { value: 'paper', label: '紙頁' },
            { value: 'dusk',  label: '黃昏' },
          ]}
          onChange={(v) => setTweak('palette', v)}
        />

        <TweakSection label="動態 · Motion" />
        <TweakRadio
          label="強度"
          value={t.motionLevel}
          options={[
            { value: 'minimal', label: '極簡' },
            { value: 'rich',    label: '豐富' },
          ]}
          onChange={(v) => setTweak('motionLevel', v)}
        />
        <TweakToggle
          label="顯示字海段落"
          value={t.showWordOcean}
          onChange={(v) => setTweak('showWordOcean', v)}
        />
        <TweakSelect
          label="字海流速"
          value={t.oceanSpeed}
          options={[
            { value: 'slow',   label: '慢（適合慢讀）' },
            { value: 'medium', label: '中' },
            { value: 'fast',   label: '快（戲劇感）' },
          ]}
          onChange={(v) => setTweak('oceanSpeed', v)}
        />

        <TweakSection label="首頁文案 · Hero" />
        <TweakSelect
          label="開場語氣"
          value={t.heroVariant}
          options={[
            { value: 'default',    label: '宣示（預設）' },
            { value: 'quiet',      label: '安靜' },
            { value: 'invitation', label: '邀請' },
          ]}
          onChange={(v) => setTweak('heroVariant', v)}
        />
        <TweakText
          label="字海中央那句"
          value={t.manifesto}
          onChange={(v) => setTweak('manifesto', v)}
        />

        <TweakSection label="結尾宣言 · Closing" />
        <TweakText
          label="結尾段落（用空行分段）"
          value={t.closing}
          onChange={(v) => setTweak('closing', v)}
        />
      </TweaksPanel>
    </>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
