/* shared.jsx — hash router, content index loader, interactive loader, nav atoms */

/* 公开站 URL（外链跳转目标）；如有变化请在此更新。 */
const PUBLIC_SITE_URL = 'https://slowcompact.com';

/* ---------------- Hash Router ---------------- */

function parseHash() {
  const h = (window.location.hash || '#/').replace(/^#/, '');
  const parts = h.split('/').filter(Boolean);
  if (parts.length === 0) return { name: 'library' };
  if (parts[0] === 'read' && parts.length >= 3) {
    return { name: 'reader', type: parts[1], slug: parts[2] };
  }
  return { name: 'library' };
}

function useRoute() {
  const [route, setRoute] = React.useState(parseHash);
  React.useEffect(() => {
    const onHash = () => {
      setRoute(parseHash());
      window.scrollTo(0, 0);
    };
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);
  return route;
}

function nav(path) {
  window.location.hash = path;
}

/* ---------------- Content Index Loader ----------------
   Loads app/content/index.json once on app start. Items listed there
   are considered published and renderable. */

const ContentContext = React.createContext({ loading: true, items: [], error: null });

function ContentProvider({ children }) {
  const [state, setState] = React.useState({ loading: true, items: [], error: null });
  React.useEffect(() => {
    authFetch('content/index.json', { cache: 'no-cache' })
      .then(async r => {
        if (r.status === 401) throw new Error('登录已过期，请重新登录。');
        if (r.status === 403) throw new Error('此邮箱未被允许访问内容。');
        if (!r.ok) {
          let detail = '';
          try { detail = (await r.json()).error; } catch {}
          throw new Error(detail || 'content/index.json 不存在 — 请在父仓库根目录运行 bin/sync-drafts-to-webapp.sh');
        }
        return r.json();
      })
      .then(j => setState({ loading: false, items: j.items || [], error: null }))
      .catch(e => setState({ loading: false, items: [], error: e.message }));
  }, []);
  return <ContentContext.Provider value={state}>{children}</ContentContext.Provider>;
}

function useContent() { return React.useContext(ContentContext); }

function findItem(items, type, slug) {
  return items.find(i => i.type === type && i.slug === slug);
}

function contentPathForLang(item, lang) {
  if (lang === 'en' && item.content_path_en) return item.content_path_en;
  return item.content_path_zh || item.content_path;
}

function wordCountForLang(item, lang) {
  if (lang === 'en' && item.word_count_en) return item.word_count_en;
  return item.word_count_zh || item.word_count || 0;
}

/* ---------------- Interactive Loader ----------------
   Babel Standalone does not auto-transpile dynamically inserted
   <script type="text/babel">. So we fetch the file, transform it
   manually, then inject as a plain <script>. */

const _interactivesLoaded = new Set();

/* Resolve the React component name for an interactive entry.
   Accepts a single filename (string) or an array (multi-file).
   For arrays the last file holds the main component. */
function interactiveComponentName(files) {
  const filename = Array.isArray(files) ? files[files.length - 1] : files;
  const base = filename.replace(/^.*\//, '').replace(/\.jsx?$/, '');
  const camel = base.split(/[-_]/)
    .map(s => s.charAt(0).toUpperCase() + s.slice(1))
    .join('');
  return camel + 'Interactive';
}

/* Load one or many JSX files, Babel-transform each, inject as <script>.
   Array entries are loaded sequentially (preserving dependency order). */
async function loadInteractive(files) {
  const list = Array.isArray(files) ? files : [files];
  for (const filename of list) {
    if (_interactivesLoaded.has(filename)) continue;
    const path = 'interactive/' + filename;
    const res = await authFetch(path, { cache: 'no-cache' });
    if (!res.ok) throw new Error('找不到互动组件文件：' + path);
    const src = await res.text();
    const out = window.Babel.transform(src, { presets: ['react'] }).code;
    const script = document.createElement('script');
    script.textContent = out;
    document.body.appendChild(script);
    _interactivesLoaded.add(filename);
  }
}

/* Load CSS files for an interactive (idempotent). */
const _cssLoaded = new Set();
async function loadInteractiveCSS(files) {
  const list = Array.isArray(files) ? files : [files];
  for (const filename of list) {
    if (_cssLoaded.has(filename)) continue;
    const path = 'interactive/' + filename;
    const res = await authFetch(path, { cache: 'no-cache' });
    if (!res.ok) throw new Error('找不到互动样式文件：' + path);
    const style = document.createElement('style');
    style.setAttribute('data-interactive-css', filename);
    style.textContent = await res.text();
    document.head.appendChild(style);
    _cssLoaded.add(filename);
  }
}

/* ---------------- Nav atoms ---------------- */

function GlobalNav() {
  const { lang, setLang } = useLang();
  const { theme, setTheme } = useTheme();
  const auth = useAuth();
  const [showEmail, setShowEmail] = React.useState(false);
  React.useEffect(() => setShowEmail(false), [auth && auth.email]);

  return (
    <header className="g-nav">
      <div className="g-nav__left">
        <a href="#/" className="g-nav__brand">
          <span className="g-nav__brand-mark"></span>
          <span><T zh="缓约 · 阅读" en="Slow Compact · Read" /></span>
        </a>
      </div>
      <nav className="g-nav__center">
        <a href="#/"><T zh="书架" en="Library" /></a>
      </nav>
      <div className="g-nav__right">
        {auth && auth.isAuthenticated && (
          <>
            <EmailText email={auth.email} reveal={showEmail} className="g-nav__user" />
            <button
              type="button"
              className="g-nav__email-toggle"
              aria-pressed={showEmail}
              onClick={() => setShowEmail(v => !v)}
              title={showEmail
                ? (lang === 'zh' ? '隐藏完整邮箱' : 'Hide full email')
                : (lang === 'zh' ? '显示完整邮箱' : 'Show full email')}
            >
              {showEmail ? <T zh="隐藏邮箱" en="Hide email" /> : <T zh="显示邮箱" en="Show email" />}
            </button>
            <button className="g-nav__signout" onClick={auth.signoutRedirect}>
              <T zh="退出" en="Sign out" />
            </button>
          </>
        )}
        <a href={PUBLIC_SITE_URL} target="_blank" rel="noopener" className="g-nav__outlink">
          <T zh="公开介绍 →" en="Public site →" />
        </a>
        <button className="g-nav__theme" onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')} title={theme === 'dark' ? '切换亮色' : 'Switch to dark'}>
          {theme === 'dark' ? '☀' : '☾'}
        </button>
        <button className="g-nav__lang" onClick={() => setLang(lang === 'zh' ? 'en' : 'zh')}>
          {lang === 'zh' ? 'EN' : '中'}
        </button>
      </div>
    </header>
  );
}

function SiteFooter() {
  return (
    <footer className="site-footer">
      <small>
        <T zh="缓约 · Slow Compact" en="Slow Compact" />
        <span className="sep">·</span>
        <a href={PUBLIC_SITE_URL} target="_blank" rel="noopener">
          <T zh="公开介绍" en="Public site" />
        </a>
      </small>
    </footer>
  );
}

/* ---------------- POV badge & accents ---------------- */

const POV_LABEL = {
  eoani:   { zh: '衡族 POV',     en: 'Eoani POV',   accent: 'var(--eoani)' },
  ketoi:   { zh: '鲸语族 POV',   en: 'Ketoi POV',   accent: 'var(--ketoi)' },
  huvari:  { zh: '涣族 POV',     en: 'Huvari POV',  accent: 'var(--huvari)' },
  thavari: { zh: '震族 POV',     en: 'Thavari POV', accent: 'var(--thavari)' },
  human:   { zh: '人类 POV',     en: 'Human POV',   accent: 'var(--human)' },
  multi:   { zh: '多视角',       en: 'Multi-POV',   accent: 'var(--ink)' },
};

function PovBadge({ pov }) {
  const meta = POV_LABEL[pov];
  if (!meta) return null;
  const { lang } = useLang();
  return (
    <span className="pov-badge" style={{ borderColor: meta.accent, color: meta.accent }}>
      {lang === 'zh' ? meta.zh : meta.en}
    </span>
  );
}

Object.assign(window, {
  PUBLIC_SITE_URL,
  parseHash, useRoute, nav,
  ContentContext, ContentProvider, useContent, findItem, contentPathForLang, wordCountForLang,
  loadInteractive, loadInteractiveCSS, interactiveComponentName,
  GlobalNav, SiteFooter, PovBadge, POV_LABEL,
});
