// Router: real paths in production (/gallery, /papers, /blog/<slug> — the URLs
// scripts/prerender.js writes real HTML for), hash routes everywhere else (builder preview,
// and any deploy served from a subdirectory). Both forms resolve to the same route names.
const PATH_ROUTES = /^\/(gallery|papers|blog\/[^/]+)\/?$/;
function pathRoute() {
  const m = PATH_ROUTES.exec(window.location.pathname);
  return m ? m[1] : null;
}
function getRoute() {
  const p = pathRoute();
  const h = p || window.location.hash.replace(/^#\/?/, '');
  if (h.startsWith('gallery')) return 'gallery';
  if (h.startsWith('papers')) return 'papers';
  if (h.startsWith('blog/')) return 'blog';
  return 'home';
}
// The blog slug is kept as its own state (not derived from `route`) so moving between two
// posts, which leaves `route` on 'blog', still re-renders.
function getSlug() {
  const h = pathRoute() || window.location.hash.replace(/^#\/?/, '');
  return h.startsWith('blog/') ? decodeURIComponent(h.slice(5).replace(/\/$/, '')) : null;
}

// Mood/type/chrome tweaks are expressed purely as CSS custom-property
// overrides, applied on a single wrapper div — every component already reads
// these vars (with matching fallbacks), so one object reshapes the whole site.
const MOOD_VARS = {
  'Ivory & Gold': {},
  'Midnight Luxury': {
    '--bg-light': '#17140F',
    '--paper': '#211D16',
    '--paper-dim': '#282319',
    '--ink': '#F5F1EA',
    '--ink-soft': '#C9C2B3',
    '--ink-faint': '#8F8878',
    '--line': 'rgba(245,241,234,0.14)',
    '--accent-gold': '#E0B975',
    '--surface-card': '#211D16',
  },
};

const CHROME_VARS = {
  'Glass': {
    ivory: { '--chrome-bg': 'rgba(250,249,247,0.75)', '--chrome-bg-solid': 'rgba(250,249,247,0.85)', '--chrome-blur-soft': 'blur(8px)' },
    midnight: { '--chrome-bg': 'rgba(23,20,15,0.7)', '--chrome-bg-solid': 'rgba(23,20,15,0.85)', '--chrome-blur-soft': 'blur(8px)' },
  },
  'Solid': {
    ivory: { '--chrome-bg': '#FAF9F7', '--chrome-bg-solid': '#FAF9F7', '--chrome-blur': 'none', '--chrome-blur-soft': 'none', '--chrome-shadow': '0 1px 0 var(--line), 0 8px 24px rgba(0,0,0,0.06)' },
    midnight: { '--chrome-bg': '#211D16', '--chrome-bg-solid': '#211D16', '--chrome-blur': 'none', '--chrome-blur-soft': 'none', '--chrome-shadow': '0 1px 0 var(--line), 0 8px 24px rgba(0,0,0,0.3)' },
  },
};

const TYPE_VARS = {
  'Classic Serif': {},
  'Bold Modern': { '--font-heading': "'Bricolage Grotesque', var(--font-heading-thai), sans-serif" },
};

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "mood": "Ivory & Gold",
  "typeVoice": "Classic Serif",
  "chrome": "Glass"
}/*EDITMODE-END*/;

// Pull-to-refresh: tracks a touch's vertical travel only while already at the top of the
// page (a one-time scrollY read at touchstart, not a continuous scroll listener) and, past
// a threshold, re-fetches CMS data via the same `cms-refresh` event editable.jsx's host
// bridge already dispatches — zero new data-plumbing needed.
function usePullToRefresh(onRefresh) {
  const [pull, setPull] = React.useState(0);
  const startY = React.useRef(null);
  React.useEffect(() => {
    const onStart = (e) => {
      startY.current = window.scrollY === 0 ? e.touches[0].clientY : null;
    };
    const onMove = (e) => {
      if (startY.current == null) return;
      const dy = e.touches[0].clientY - startY.current;
      if (dy > 0) setPull(Math.min(dy, 100));
    };
    const onEnd = () => {
      if (startY.current == null) return;
      startY.current = null;
      setPull((p) => { if (p > 60) onRefresh(); return 0; });
    };
    window.addEventListener('touchstart', onStart, { passive: true });
    window.addEventListener('touchmove', onMove, { passive: true });
    window.addEventListener('touchend', onEnd);
    return () => {
      window.removeEventListener('touchstart', onStart);
      window.removeEventListener('touchmove', onMove);
      window.removeEventListener('touchend', onEnd);
    };
  }, [onRefresh]);
  return pull;
}

function App() {
  const [route, setRoute] = React.useState(getRoute());
  const [slug, setSlug] = React.useState(getSlug());
  const pull = usePullToRefresh(React.useCallback(() => window.dispatchEvent(new CustomEvent('cms-refresh')), []));

  React.useEffect(() => {
    const onHash = () => {
      setRoute(getRoute());
      setSlug(getSlug());
      const h = window.location.hash.replace(/^#\/?/, '');
      if (h === '' || h === 'gallery' || h === 'papers') window.scrollTo(0, 0);
    };
    // Same-site path links (written by shared.jsx's url() so crawlers see real hrefs) are
    // intercepted here and turned into pushState navigation — the page never reloads, so the
    // route transitions and the already-fetched CMS data survive.
    const onClick = (e) => {
      if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
      const a = e.target.closest && e.target.closest('a');
      if (!a || a.target === '_blank') return;
      const href = a.getAttribute('href');
      if (!href || !/^\/(gallery|papers|blog\/[^/#]+)?(#.*)?$/.test(href)) return;
      e.preventDefault();
      window.history.pushState({}, '', href);
      onHash();
    };
    const onPop = () => onHash();
    window.addEventListener('hashchange', onHash);
    document.addEventListener('click', onClick);
    window.addEventListener('popstate', onPop);
    return () => {
      window.removeEventListener('hashchange', onHash);
      document.removeEventListener('click', onClick);
      window.removeEventListener('popstate', onPop);
    };
  }, []);

  const [lang, setLangState] = React.useState(() => {
    try { return window.localStorage.getItem('3b-lang') || 'th'; } catch (e) { return 'th'; }
  });
  const setLang = (l) => {
    setLangState(l);
    try { window.localStorage.setItem('3b-lang', l); } catch (e) {}
  };
  // Builder host can drive the preview language (editable.jsx dispatches `set-lang`).
  React.useEffect(() => {
    const onSetLang = (e) => setLangState(e.detail);
    window.addEventListener('set-lang', onSetLang);
    return () => window.removeEventListener('set-lang', onSetLang);
  }, []);

  const [tw, setTweak] = window.useTweaks(TWEAK_DEFAULTS);
  const { TweaksPanel, TweakSection, TweakRadio } = window;
  const cms = window.useCmsData();

  const moodKey = tw.mood === 'Midnight Luxury' ? 'midnight' : 'ivory';
  // Typography from site_settings wins over the demo TYPE_VARS tweak.
  const typoVars = (cms && window.Typography) ? window.Typography.resolve(cms.typography).vars : {};
  const cssVars = {
    ...MOOD_VARS[tw.mood],
    ...CHROME_VARS[tw.chrome][moodKey],
    ...TYPE_VARS[tw.typeVoice],
    ...typoVars,
  };

  // Load the webfonts (Google + custom @font-face) whenever the typography settings change.
  const typoKey = cms ? JSON.stringify(cms.typography) : null;
  React.useEffect(() => {
    if (cms && window.Typography) window.Typography.loadFonts(cms.typography);
  }, [typoKey]);

  if (!cms) {
    return <div style={{ ...cssVars, background: 'var(--bg-light)', minHeight: '100vh' }}></div>;
  }

  const { BottomTabBar, MobileLangPill, LookbookTransition, LangFlipTransition } = window.Shared;

  return (
    <div style={cssVars}>
      {pull > 0 && (
        <div style={{
          position: 'fixed', top: 0, left: 0, right: 0, zIndex: 70,
          display: 'flex', justifyContent: 'center', paddingTop: 10, pointerEvents: 'none',
          opacity: Math.min(pull / 50, 1), transform: `translateY(${Math.min(pull, 60) - 40}px)`,
        }}>
          <div style={{
            width: 26, height: 26, borderRadius: '50%',
            border: '2px solid var(--accent-gold)', borderTopColor: 'transparent',
            background: 'var(--chrome-bg-solid, #FAF9F7)', boxShadow: 'var(--shadow-lift)',
            transform: `rotate(${pull * 3}deg)`,
          }}></div>
        </div>
      )}
      {/* Route changes (Home <-> Gallery) use the lookbook rise/recede; language switches use
          the 4-panel flip below — two independent transitions, each keyed on only its own
          trigger, so switching one never re-plays the other. */}
      <LookbookTransition transitionKey={route} reverse={route === 'home'}>
        <LangFlipTransition transitionKey={lang} reverse={lang === 'th'}>
          {route === 'gallery'
            ? <window.GalleryPage lang={lang} setLang={setLang} route={route} cms={cms} />
            : route === 'papers'
            ? <window.PapersPage lang={lang} setLang={setLang} route={route} cms={cms} />
            : route === 'blog'
            ? <window.BlogPostPage lang={lang} setLang={setLang} route={route} cms={cms} slug={slug} />
            : <window.HomePage lang={lang} setLang={setLang} route={route} cms={cms} />}
        </LangFlipTransition>
      </LookbookTransition>
      {/* Rendered once, site-wide (not per-page) — mirrors Header's own route-awareness,
          visibility toggled purely via CSS media query, see styles.css. */}
      <BottomTabBar lang={lang} route={route} />
      <MobileLangPill lang={lang} setLang={setLang} />
      <TweaksPanel>
        <TweakSection label="Mood" />
        <TweakRadio label="Palette" value={tw.mood}
          options={['Ivory & Gold', 'Midnight Luxury']}
          onChange={(v) => setTweak('mood', v)} />
        <TweakSection label="Type" />
        <TweakRadio label="Heading voice" value={tw.typeVoice}
          options={['Classic Serif', 'Bold Modern']}
          onChange={(v) => setTweak('typeVoice', v)} />
        <TweakSection label="Navigation" />
        <TweakRadio label="Header chrome" value={tw.chrome}
          options={['Glass', 'Solid']}
          onChange={(v) => setTweak('chrome', v)} />
      </TweaksPanel>
    </div>
  );
}

window.App = App;
