/* global React, ReactDOM */
const { useState, useEffect, useMemo, useRef, useLayoutEffect } = React;

// ---------- i18n helpers ----------
const t = (path, vars) => (window.I18N ? window.I18N.t(path, vars) : "");
const curLang = () => (window.I18N ? window.I18N.lang : "zh-CN");
const EMAIL = "contact@dongxlab.com";

// 把字符串里的邮箱渲染成真实 mailto 链接（可见文字 === href 目标）
function emailLinkify(str) {
  if (typeof str !== "string" || str.indexOf(EMAIL) === -1) return str;
  const parts = str.split(EMAIL);
  const out = [];
  parts.forEach((p, i) => {
    if (i > 0) out.push(<a key={"m" + i} href={"mailto:" + EMAIL}>{EMAIL}</a>);
    if (p) out.push(<span key={"s" + i}>{p}</span>);
  });
  return out;
}

// 按第一个冒号（： 或 :）拆成 [标签, 内容]
function splitKV(str) {
  const idx = str.search(/：|:/);
  if (idx === -1) return [str, ""];
  const sep = str[idx];
  return [str.slice(0, idx), str.slice(idx + sep.length).trim()];
}

// ---------- OpenCC：仅在 zh-HK 下转换文章正文 .prose ----------
let _conv = null;
function getConverter() {
  if (_conv) return _conv;
  if (window.OpenCC && window.OpenCC.Converter) {
    try { _conv = window.OpenCC.Converter({ from: 'cn', to: 'tw' }); } catch (e) { _conv = null; }
  }
  return _conv;
}

function useProseHK(lang) {
  useLayoutEffect(() => {
    const root = document.getElementById('root');
    if (!root) return;
    const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
      acceptNode(n) {
        const p = n.parentElement;
        if (!p) return NodeFilter.FILTER_REJECT;
        if (!p.closest('.prose')) return NodeFilter.FILTER_REJECT; // 只碰正文
        if (!n.nodeValue || !/[一-鿿]/.test(n.nodeValue)) return NodeFilter.FILTER_REJECT;
        return NodeFilter.FILTER_ACCEPT;
      }
    });
    const conv = lang === 'zh-HK' ? getConverter() : null;
    let n;
    while ((n = walker.nextNode())) {
      if (n.__orig === undefined) n.__orig = n.nodeValue;
      const target = conv ? conv(n.__orig) : n.__orig;
      if (n.nodeValue !== target) n.nodeValue = target;
    }
  });
}

// ---------- helpers ----------
const MONTHS_EN = ["January", "February", "March", "April", "May", "June",
  "July", "August", "September", "October", "November", "December"];
const fmtDate = (s) => {
  const [y, m, d] = s.split("-");
  const lang = curLang();
  const full = lang === "en"
    ? `${MONTHS_EN[parseInt(m, 10) - 1]} ${parseInt(d, 10)}, ${y}`
    : `${y}年${parseInt(m, 10)}月${parseInt(d, 10)}日`;
  return { y, md: `${m}.${d}`, full };
};
const catName = (id) => {
  const c = window.CATEGORIES.find((x) => x.id === id);
  if (!c) return id;
  return c[curLang()] || c["en"] || id;
};

// Keep native new-tab/window gestures while using the existing view navigation.
function handleArticleLink(event, navigate) {
  event.stopPropagation();
  if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
  event.preventDefault();
  navigate();
}

// ---------- Header / Nav ----------
function TopBar({ view, setView, onSearch, lang, setLang }) {
  const LANGS = window.I18N.LANGS;
  const LABEL = window.I18N.LABEL;
  return (
    <header className="topbar">
      <div className="topbar-inner">
        <button className="brand" onClick={() => setView({ name: "home" })} aria-label="岁末冬至 · Solstice Notes">
          <span className="brand-mark">岁末冬至</span>
          <span className="brand-sub">Solstice Notes</span>
        </button>
        <nav className="nav" aria-label={t("nav.articles")}>
          <button className={view.name === "home" ? "active" : ""} onClick={() => setView({ name: "home" })}>{t("nav.articles")}</button>
          <button className={view.name === "archive" ? "active" : ""} onClick={() => setView({ name: "archive" })}>{t("nav.archive")}</button>
          <button className={view.name === "about" ? "active" : ""} onClick={() => setView({ name: "about" })}>{t("nav.about")}</button>
          <button onClick={onSearch} aria-label={t("nav.search")}>{t("nav.search")}</button>
          <div className="locale-toggle" role="group" aria-label={t("nav.langSwitch")}>
            {LANGS.map((lg, i) => (
              <React.Fragment key={lg}>
                {i > 0 && <span className="sep">·</span>}
                <button
                  className={lang === lg ? "on" : ""}
                  aria-pressed={lang === lg}
                  aria-label={lg}
                  onClick={() => setLang(lg)}
                >{LABEL[lg]}</button>
              </React.Fragment>
            ))}
          </div>
        </nav>
        <a className="portal-link" href={`https://dylanarc.com/?lang=${lang}`}>Dylan Arc</a>
      </div>
    </header>
  );
}

function Footer({ setView }) {
  return (
    <footer className="shell footer">
      <div>{t("footer.copyright")}</div>
      <div className="links">
        <button onClick={() => setView({ name: "about" })}>{t("footer.wechat")}</button>
      </div>
    </footer>
  );
}

// ---------- Home ----------
function Home({ articles, setView }) {
  const [activeCat, setActiveCat] = useState("all");
  const [limit, setLimit] = useState(20);

  const counts = useMemo(() => {
    const out = { all: articles.length };
    for (const a of articles) out[a.cat] = (out[a.cat] || 0) + 1;
    return out;
  }, [articles]);

  const filtered = useMemo(() => {
    const list = activeCat === "all" ? articles : articles.filter((a) => a.cat === activeCat);
    return [...list].sort((a, b) => b.date.localeCompare(a.date));
  }, [articles, activeCat]);

  const pinned = filtered[0];
  const rest = filtered.slice(1, limit);

  return (
    <main className="shell">
      <section className="hero">
        <div className="hero-eyebrow">{t("hero.eyebrow")}</div>
        <h1 className="hero-title">
          岁末冬至
        </h1>
        <p className="hero-lead">{t("hero.sub")}</p>
        <p className="hero-sub">{t("hero.desc")}</p>
        <div className="hero-meta">
          <span>{t("hero.meta", { postCount: articles.length })}</span>
        </div>
      </section>

      <nav className="cat-strip" aria-label={t("nav.articles")}>
        {window.CATEGORIES.map((c) => (
          <button
            key={c.id}
            className={"cat-chip " + (activeCat === c.id ? "active" : "")}
            onClick={() => { setActiveCat(c.id); setLimit(20); }}
          >
            {catName(c.id)}<span className="count">{counts[c.id] || 0}</span>
          </button>
        ))}
      </nav>

      {pinned && (
        <>
          <div className="section-head">
            <div className="section-title">{t("section.recent")}</div>
            <div className="section-aside">{fmtDate(pinned.date).full}</div>
          </div>
          <article className="pinned" onClick={() => setView({ name: "article", id: pinned.id })}>
            <div className="pin-date">
              <span className="y">{fmtDate(pinned.date).y}</span>
              {fmtDate(pinned.date).md}
            </div>
            <div>
              <div className="pin-cat">{catName(pinned.cat)}</div>
              <h2 className="pin-title"><a href={`?article=${pinned.id}&lang=${curLang()}`} onClick={(e) => handleArticleLink(e, () => setView({ name: "article", id: pinned.id }))}>{pinned.title}</a></h2>
              <p className="pin-excerpt">{pinned.excerpt || t("list.excerptFallback")}</p>
              <div className="pin-meta">{t("list.readingTime", { minutes: pinned.read })}</div>
            </div>
          </article>
        </>
      )}

      <div className="section-head">
        <div className="section-title">{activeCat === "all" ? t("section.all") : catName(activeCat)}</div>
        <div className="section-aside">{t("section.byDate", { count: filtered.length })}</div>
      </div>

      {rest.length === 0 ? (
        <div className="empty">{t("category.empty")}</div>
      ) : (
        <ul className="article-list">
          {rest.map((a) => (
            <li key={a.id} className="article-row" onClick={() => setView({ name: "article", id: a.id })}>
              <div className="row-date">{fmtDate(a.date).y}.{fmtDate(a.date).md}</div>
              <div className="row-main">
                <div className="row-cat">{catName(a.cat)}</div>
                <h3 className="row-title"><a href={`?article=${a.id}&lang=${curLang()}`} onClick={(e) => handleArticleLink(e, () => setView({ name: "article", id: a.id }))}>{a.title}</a></h3>
              </div>
              <div className="row-read">{t("list.readingTime", { minutes: a.read })}</div>
            </li>
          ))}
        </ul>
      )}

      {filtered.length > limit && (
        <button className="list-more" onClick={() => setLimit(limit + 20)}>
          {t("list.loadMore")}
        </button>
      )}
    </main>
  );
}

// ---------- Article ----------
function Article({ article, all, setView }) {
  const [progress, setProgress] = useState(0);
  const [activeH, setActiveH] = useState(null);
  const proseRef = useRef(null);

  const rawHtml = article.html || "<p></p>";
  const toc = useMemo(() => {
    const div = document.createElement("div");
    div.innerHTML = rawHtml;
    return Array.from(div.querySelectorAll("h2, h3")).map((h, i) => ({
      id: `h-${i}`, text: h.textContent, level: h.tagName.toLowerCase(),
    }));
  }, [rawHtml]);

  const bodyWithIds = useMemo(() => {
    let i = 0;
    return rawHtml.replace(/<(h2|h3)>/g, (_, tag) => `<${tag} id="h-${i++}">`);
  }, [rawHtml]);

  useEffect(() => {
    const onScroll = () => {
      const h = document.documentElement;
      const scrolled = h.scrollTop;
      const total = h.scrollHeight - h.clientHeight;
      setProgress(total > 0 ? Math.min(100, (scrolled / total) * 100) : 0);
      if (proseRef.current) {
        const hs = proseRef.current.querySelectorAll("h2");
        let cur = null;
        hs.forEach((h2) => {
          if (h2.getBoundingClientRect().top < 120) cur = h2.id;
        });
        setActiveH(cur);
      }
    };
    window.addEventListener("scroll", onScroll);
    window.scrollTo(0, 0);
    onScroll();
    return () => window.removeEventListener("scroll", onScroll);
  }, [article.id]);

  const sorted = useMemo(() => [...all].sort((a, b) => b.date.localeCompare(a.date)), [all]);
  const idx = sorted.findIndex((a) => a.id === article.id);
  const prev = sorted[idx + 1];
  const next = sorted[idx - 1];

  return (
    <>
      <div className="progress" style={{ width: `${progress}%` }} />
      <main className="shell">
        <article className="article">
          <div className="article-eyebrow">{catName(article.cat)}</div>
          <h1 className="article-title">{article.title}</h1>
          <div className="article-meta">
            <span>{fmtDate(article.date).full}</span>
            <span>{t("list.readingTime", { minutes: article.read })}</span>
            <span>{t("article.location")}</span>
          </div>
          <div className="prose" ref={proseRef} dangerouslySetInnerHTML={{ __html: bodyWithIds }} />

          <div className="article-foot">
            <div style={{ fontFamily: "var(--sans)", fontSize: 12, color: "var(--muted)", letterSpacing: "0.18em", textTransform: "uppercase" }}>
              {t("article.end")}
            </div>
            {article.link && (
              <p style={{ fontFamily: "var(--sans)", fontSize: 12, color: "var(--muted)", marginTop: 18 }}>
                {t("article.source")} · <a href={article.link} target="_blank" rel="noopener" style={{ color: "var(--seal)" }}>{t("article.openWechat")}</a>
              </p>
            )}
            <nav className="article-nav">
              {prev ? (
                <a href={`?article=${prev.id}&lang=${curLang()}`} onClick={(e) => handleArticleLink(e, () => setView({ name: "article", id: prev.id }))}>
                  <span className="nav-label">{t("article.prev")}</span>
                  <span className="nav-title">{prev.title}</span>
                </a>
              ) : <span />}
              {next ? (
                <a href={`?article=${next.id}&lang=${curLang()}`} onClick={(e) => handleArticleLink(e, () => setView({ name: "article", id: next.id }))}>
                  <span className="nav-label">{t("article.next")}</span>
                  <span className="nav-title">{next.title}</span>
                </a>
              ) : <span />}
            </nav>
          </div>
        </article>
      </main>

      {toc.length > 0 && (
        <aside className="toc">
          <div className="toc-label">{t("nav.articles")}</div>
          {toc.map((tc) => (
            <a key={tc.id} href={`#${tc.id}`} className={activeH === tc.id ? "active" : ""}>
              {tc.text}
            </a>
          ))}
        </aside>
      )}
    </>
  );
}

// ---------- About ----------
function About() {
  const items = [
    splitKV(t("about.doing")),
    splitKV(t("about.writing")),
    splitKV(t("about.location")),
    splitKV(t("about.contact")),
  ];
  return (
    <main className="shell about">
      <h1 className="about-title">{t("about.title")}</h1>
      <div className="about-prose">
        <p className="lead">{t("about.lead")}</p>
        <p>{t("about.p1")}</p>
        <p>{t("about.p2")}</p>
      </div>

      <ul className="about-list">
        {items.map(([k, v], i) => (
          <li key={i}><span className="k">{k}</span><span>{emailLinkify(v)}</span></li>
        ))}
      </ul>

      <aside className="wechat-card">
        <div className="wechat-inner">
          <div className="wechat-text">
            <div className="wc-eyebrow">{t("about.wcEyebrow")}</div>
            <h3>{t("about.wcTitle")}</h3>
            <p>{t("about.wcDesc")}</p>
            <div className="wc-meta">
              <span>{t("about.wcSearch")}</span>
              <a href={"mailto:" + EMAIL}>{t("about.emailBtn")}</a>
            </div>
          </div>
          <div className="qr-wrap">
            <div className="qr-frame">
              <span className="c1"></span><span className="c2"></span>
              <img src="assets/wechat-qr.jpg" alt={t("about.qrAlt")} />
            </div>
            <div className="qr-caption">{t("about.wcScan")}</div>
          </div>
        </div>
      </aside>
    </main>
  );
}

// ---------- Archive ----------
function Archive({ articles, setView }) {
  const byYear = useMemo(() => {
    const out = {};
    for (const a of articles) {
      const y = a.date.slice(0, 4);
      (out[y] ||= []).push(a);
    }
    for (const y in out) out[y].sort((a, b) => b.date.localeCompare(a.date));
    return out;
  }, [articles]);
  const years = Object.keys(byYear).sort((a, b) => b.localeCompare(a));
  const [activeYear, setActiveYear] = useState('all');

  const visibleYears = activeYear === 'all' ? years : [activeYear];

  return (
    <main className="shell">
      <header className="archive-head">
        <h1 className="archive-title">{t("nav.archive")}</h1>
        <div className="archive-sub">{t("archive.sub", { count: articles.length, from: years[years.length - 1], to: years[0] })}</div>
      </header>

      <nav className="year-tabs" aria-label={t("nav.archive")}>
        <button className={'year-tab ' + (activeYear === 'all' ? 'active' : '')} onClick={() => setActiveYear('all')}>
          {t("section.allYears")}<span className="yt-count">{articles.length}</span>
        </button>
        {years.map((y) => (
          <button key={y}
            className={'year-tab ' + (activeYear === y ? 'active' : '')}
            onClick={() => setActiveYear(y)}>
            {y}<span className="yt-count">{byYear[y].length}</span>
          </button>
        ))}
      </nav>

      {visibleYears.map((y) => (
        <section className="year-block" key={y}>
          <div className="year-head">
            <div className="year-num">{y}</div>
            <div className="year-meta">{t("section.yearCount", { count: byYear[y].length })}</div>
          </div>
          <ul className="article-list">
            {byYear[y].map((a) => (
              <li key={a.id} className="article-row" onClick={() => setView({ name: "article", id: a.id })}>
                <div className="row-date">{fmtDate(a.date).md}</div>
                <div className="row-main">
                  <div className="row-cat">{catName(a.cat)}</div>
                  <h3 className="row-title"><a href={`?article=${a.id}&lang=${curLang()}`} onClick={(e) => handleArticleLink(e, () => setView({ name: "article", id: a.id }))}>{a.title}</a></h3>
                </div>
                <div className="row-read">{t("list.readingTime", { minutes: a.read })}</div>
              </li>
            ))}
          </ul>
        </section>
      ))}
    </main>
  );
}

// ---------- Search ----------
function SearchOverlay({ articles, onClose, setView }) {
  const [q, setQ] = useState("");
  const inputRef = useRef(null);
  useEffect(() => { inputRef.current?.focus(); }, []);
  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onClose]);

  const results = useMemo(() => {
    if (!q.trim()) return articles.slice(0, 8);
    const s = q.toLowerCase();
    return articles.filter((a) =>
      a.title.toLowerCase().includes(s) || catName(a.cat).toLowerCase().includes(s)
    ).slice(0, 12);
  }, [q, articles]);

  const noResults = q.trim() && results.length === 0;

  return (
    <div className="search-overlay" onClick={onClose}>
      <div className="search-box" onClick={(e) => e.stopPropagation()}>
        <input
          ref={inputRef}
          className="search-input"
          placeholder={t("search.placeholder")}
          aria-label={t("search.placeholder")}
          value={q}
          onChange={(e) => setQ(e.target.value)}
        />
        <div className="search-results">
          {noResults ? (
            <div className="empty">{t("search.noResults")}</div>
          ) : results.map((a) => (
            <a key={a.id} href={`?article=${a.id}&lang=${curLang()}`} className="search-result" onClick={(e) => handleArticleLink(e, () => { setView({ name: "article", id: a.id }); onClose(); })}>
              <div className="sr-cat">{catName(a.cat)} · {a.date}</div>
              <div className="sr-title">{a.title}</div>
            </a>
          ))}
        </div>
        <div className="search-hint">{t("search.close")}</div>
      </div>
    </div>
  );
}

// ---------- App ----------
function App() {
  const [view, setView] = useState({ name: "home" });
  const [searchOpen, setSearchOpen] = useState(false);
  const [lang, setLangState] = useState(() => window.I18N.lang);
  const setLang = (l) => { window.I18N.setLang(l); setLangState(l); };
  const articles = window.ARTICLES;
  useProseHK(lang);

  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const id = params.get('article');
    if (id && articles.some((a) => a.id === id)) {
      setView({ name: 'article', id });
    }
  }, []);

  useEffect(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key === "k") { e.preventDefault(); setSearchOpen(true); }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  let body;
  if (view.name === "home") body = <Home articles={articles} setView={setView} />;
  else if (view.name === "about") body = <About />;
  else if (view.name === "archive") body = <Archive articles={articles} setView={setView} />;
  else if (view.name === "article") {
    const a = articles.find((x) => x.id === view.id) || articles[0];
    body = <Article article={a} all={articles} setView={setView} />;
  }

  return (
    <div className={`blog-view view-${view.name}`}>
      <TopBar view={view} setView={setView} onSearch={() => setSearchOpen(true)} lang={lang} setLang={setLang} />
      {body}
      <Footer setView={setView} />
      {searchOpen && <SearchOverlay articles={articles} onClose={() => setSearchOpen(false)} setView={setView} />}
    </div>
  );
}

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