// Shared data + chrome (Header) used by every page in the site.
// Exposed on window.Shared so each Babel <script> file (separate scope) can reuse it.

const LINE_HREF = 'https://line.me/R/ti/p/@3bprinting';

// Production is prerendered to real URLs (/gallery, /papers, /blog/<slug>) so crawlers can
// follow links and index each page — see scripts/prerender.js. The builder preview and any
// deploy served from /ui_kits/website/ have no such paths, so links fall back to the hash
// router there. App.jsx intercepts clicks on the real paths and keeps navigation client-side.
const PATH_ROUTING = !window.EDIT_MODE && !/\/ui_kits\//.test(window.location.pathname);
function url(route, focus) {
  if (!focus) return PATH_ROUTING ? route : `#${route}`;
  return PATH_ROUTING ? `${route}#${focus}` : `#${route}/${focus}`;
}

const t = {
  th: {
    heroEyebrow: 'Premium Business Cards',
    heroTh: 'นามบัตรที่ทำให้คนจดจำคุณ',
    heroEn: 'Cards people remember',
    cta: 'เริ่มสั่งซื้อ',
    heroCta1: 'ดูตัวอย่างนามบัตร',
    heroCta2: 'เลือกเนื้อกระดาษ',
    tiersTitle: 'Recommended Designs',
    galleryTitle: 'Card Galleries',
    galleryMore: 'ดูตัวอย่างทั้งหมด',
    paperTitle: 'Paper Types',
    paperSub: 'เลือกกระดาษให้เข้ากับดีไซน์ของคุณ — สัมผัสและน้ำหนักที่ต่างกัน ให้ความรู้สึกที่ต่างกัน',
    paperWeight: 'แกรม',
    compareTitle: 'Tier Comparision',
    blogTitle: 'Blogs',
    reviews: 'รีวิว',
    from1: '฿900 / 100 ใบ', from2: '฿1,900 / 100 ใบ', from3: '฿2,400 / 100 ใบ',
    galleryPageTitle: 'ตัวอย่างงานนามบัตร',
    galleryPageSub: 'ตัวอย่างงานพิมพ์จริงจากลูกค้าและงานตัวอย่างของสตูดิโอ ครบทุกเทคนิคการตกแต่ง',
    filterAll: 'ทั้งหมด',
    filterPaper: 'เนื้อกระดาษ',
    filterFinish: 'เทคนิคพิมพ์พิเศษ',
    samplesLabel: 'ตัวอย่างงาน',
    backHome: '← กลับหน้าแรก',
    papersPageTitle: 'เนื้อกระดาษทั้งหมด',
    papersPageSub: 'กระดาษทุกชนิดที่เราใช้พิมพ์นามบัตร — สัมผัส น้ำหนัก และสีเนื้อกระดาษที่ต่างกัน ให้ความรู้สึกที่ต่างกัน',
    filterWeight: 'น้ำหนักกระดาษ',
  },
  en: {
    heroEyebrow: 'Premium Business Cards',
    heroTh: 'Cards people remember',
    heroEn: 'นามบัตรที่ทำให้คนจดจำคุณ',
    cta: 'Start your Order',
    heroCta1: 'View Card Designs',
    heroCta2: 'Pick Paper Stocks',
    tiersTitle: 'Recommended Designs',
    galleryTitle: 'Cards Gallery',
    galleryMore: 'View full gallery',
    paperTitle: 'Paper Types',
    paperSub: 'Choose a stock to fit your design — each weight and texture reads differently in the hand.',
    paperWeight: 'gsm',
    compareTitle: 'Tier Comparison',
    blogTitle: 'Blogs',
    reviews: 'reviews',
    from1: '฿900 / 100 pcs', from2: '฿1,900 / 100 pcs', from3: '฿2,400 / 100 pcs',
    galleryPageTitle: 'Full Finish Gallery',
    galleryPageSub: 'Real client work and studio samples across every finishing technique we offer.',
    filterAll: 'All',
    filterPaper: 'Paper stock',
    filterFinish: 'Special printing techniques',
    samplesLabel: 'Cards Gallery',
    backHome: '← Back to home',
    papersPageTitle: 'Paper Stock Library',
    papersPageSub: 'Every stock we print business cards on — each weight, texture and paper tone reads differently in the hand.',
    filterWeight: 'Paper weight',
  },
};

// Top nav is NOT its own content — it's the footer's "Products" column plus the first link of
// "Company" (Journal), so the header and footer are one navigation, edited in one place (the
// footer section in the builder) instead of two independently-maintained label/href lists that
// drift out of sync with each other.
// Nav/footer hrefs live in the CMS as hash links (#/gallery). Translate the ones that are real
// routes so path routing gets a crawlable href; in-page anchors (#tiers) pass through untouched.
function normalizeHref(href) {
  const m = /^#(\/(?:gallery|papers|blog\/[^/]+)?)$/.exec(href || '');
  return m ? url(m[1]) : href;
}

function navFromFooter(footerContent, lang) {
  const cols = (footerContent && footerContent.columns) || [];
  const links = [...((cols[0] && cols[0].links) || []), ...((cols[1] && cols[1].links) || []).slice(0, 1)];
  return links.map((l) => ({ label: lang === 'th' ? l.label_th : l.label_en, href: normalizeHref(l.href) }));
}

function Header({ lang, setLang, route, footerContent }) {
  const { CTAButton, LangToggle } = window.Ds3BPrintingDesignSystem_6b9cdf;
  const [dropdownOpen, setDropdownOpen] = React.useState(false);
  const s = t[lang];
  const navItems = navFromFooter(footerContent, lang);
  const submenu = [
    { label: s.samplesLabel, href: url('/gallery') },
    { label: s.filterPaper, href: url('/papers') },
    { label: s.filterFinish, href: url('/gallery', 'finish') },
  ];
  return (
    <header className="site-header" style={{
      position: 'fixed', top: 0, left: 0, right: 0, zIndex: 50,
      alignItems: 'center', gap: 'var(--space-6)',
      padding: '0 var(--space-6)', height: 93,
      background: 'var(--chrome-bg, rgba(250,249,247,0.75))', backdropFilter: 'var(--chrome-blur, blur(20px) saturate(1.4))', WebkitBackdropFilter: 'var(--chrome-blur, blur(20px) saturate(1.4))',
      borderBottom: '1px solid var(--line)', fontFamily: 'var(--font-body)', boxShadow: 'var(--chrome-shadow, none)',
      // display intentionally NOT set inline — .site-header in styles.css owns the
      // flex/none toggle so the mobile media query can hide it; see styles.css.
    }}>
      <a href={url('/')} style={{ display: 'flex', alignItems: 'center' }}>
        <img src="/assets/3b_logo.webp" alt="3B Printing" style={{ height: 86, width: 120 }} />
      </a>
      <nav style={{ display: 'flex', gap: 'var(--space-5)', flex: 1, justifyContent: 'center' }}>
        {navItems.map(({ label, href }, i) => {
          const active = /gallery/.test(href || '') && route === 'gallery';
          if (i === 0) {
            return (
              <div key={i}
                onMouseEnter={() => setDropdownOpen(true)}
                onMouseLeave={() => setDropdownOpen(false)}
                style={{ position: 'relative', paddingBottom: 16, marginBottom: -16 }}>
                <a href={href} className="nav-link" style={{ color: 'var(--ink-faint)', textDecoration: 'none', fontSize: 14, fontWeight: 400, display: 'flex', alignItems: 'center', gap: 4 }}>
                  {label}
                  <span style={{ fontSize: 10, transform: dropdownOpen ? 'rotate(180deg)' : 'none', transition: 'transform var(--duration-standard) var(--ease-standard)' }}>▾</span>
                </a>
                {dropdownOpen ? (
                  <div style={{
                    position: 'absolute', top: '100%', left: '50%', transform: 'translateX(-50%)',
                    background: 'var(--chrome-bg-solid, rgba(250,249,247,0.85))', backdropFilter: 'var(--chrome-blur, blur(20px) saturate(1.4))', WebkitBackdropFilter: 'var(--chrome-blur, blur(20px) saturate(1.4))',
                    border: '1px solid var(--line)',
                    borderRadius: 'var(--radius-card)', boxShadow: 'var(--shadow-lift)',
                    minWidth: 200, padding: 'var(--space-2)', display: 'flex', flexDirection: 'column',
                  }}>
                    {submenu.map((item, j) => (
                      <SubmenuLink key={j} href={item.href} label={item.label} />
                    ))}
                  </div>
                ) : null}
              </div>
            );
          }
          return (
            <a key={i} href={href} className="nav-link"
              style={{ color: active ? 'var(--ink)' : 'var(--ink-faint)', textDecoration: 'none', fontSize: 14, fontWeight: active ? 600 : 400 }}>
              {label}
            </a>
          );
        })}
      </nav>
      <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-4)' }}>
        <LangToggle value={lang} onChange={setLang} />
        <CTAButton href={LINE_HREF}>{t[lang].cta}</CTAButton>
      </div>
    </header>
  );
}

function SubmenuLink({ href, label }) {
  const [hover, setHover] = React.useState(false);
  return (
    <a href={href}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{
        padding: '10px 16px', color: hover ? 'var(--ink)' : 'var(--ink-soft)', textDecoration: 'none', fontSize: 14,
        borderRadius: 'var(--radius-ui)', whiteSpace: 'nowrap',
        background: hover ? 'var(--paper)' : 'transparent',
        transition: 'all var(--duration-standard) var(--ease-standard)',
      }}>
      {label}
    </a>
  );
}

// Thin-stroke (1.5px) icons matching the site's hairline aesthetic — no icon font/CDN needed.
const TAB_ICONS = {
  home: <path d="M3 11l9-8 9 8M5 10v9a1 1 0 001 1h4v-6h4v6h4a1 1 0 001-1v-9" />,
  gallery: <><rect x="3" y="3" width="7" height="7" /><rect x="14" y="3" width="7" height="7" /><rect x="3" y="14" width="7" height="7" /><rect x="14" y="14" width="7" height="7" /></>,
  journal: <><rect x="4" y="3" width="16" height="18" rx="1" /><path d="M8 8h8M8 12h8M8 16h5" /></>,
  chat: <path d="M4 4h16v12H8l-4 4V4z" />,
};
function TabIcon({ name, active }) {
  return (
    <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke={active ? 'var(--accent-gold)' : 'currentColor'} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
      {TAB_ICONS[name]}
    </svg>
  );
}

// Mobile-only nav shell (replaces the desktop Header, shown/hidden purely via CSS media
// queries in styles.css — no JS width detection, no resize listener).
function BottomTabBar({ lang, route }) {
  const tabs = [
    { key: 'home', icon: 'home', label: lang === 'th' ? 'หน้าแรก' : 'Home', href: url('/'), active: route === 'home' },
    { key: 'gallery', icon: 'gallery', label: lang === 'th' ? 'ผลงาน' : 'Gallery', href: url('/gallery'), active: route === 'gallery' },
    { key: 'journal', icon: 'journal', label: lang === 'th' ? 'บทความ' : 'Journal', href: '#blog', active: false },
    { key: 'line', icon: 'chat', label: lang === 'th' ? 'ไลน์' : 'LINE', href: LINE_HREF, active: false, primary: true, external: true },
  ];
  return (
    <nav className="bottom-tab-bar" style={{
      position: 'fixed', bottom: 0, left: 0, right: 0, zIndex: 60,
      height: 64,
      background: 'var(--chrome-bg-solid, #FAF9F7)', borderTop: '1px solid var(--line)',
      fontFamily: 'var(--font-body)',
      // display intentionally NOT set inline — .bottom-tab-bar in styles.css owns the
      // none/flex toggle via the mobile media query; an inline value here would always
      // win over that stylesheet rule regardless of viewport width.
    }}>
      {tabs.map((tab) => (
        <a key={tab.key} href={tab.href} target={tab.external ? '_blank' : undefined} rel={tab.external ? 'noopener noreferrer' : undefined}
          style={{
            flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 3,
            textDecoration: 'none', color: tab.primary ? 'var(--accent-gold)' : (tab.active ? 'var(--ink)' : 'var(--ink-faint)'),
            minHeight: 'var(--touch-min, 44px)',
          }}>
          <TabIcon name={tab.icon} active={tab.active || tab.primary} />
          <span style={{ fontSize: 10, fontWeight: tab.active || tab.primary ? 600 : 400, letterSpacing: '0.02em' }}>{tab.label}</span>
        </a>
      ))}
    </nav>
  );
}

// Mobile-only floating language switch — Header (which normally hosts LangToggle) is
// hidden on mobile in favor of BottomTabBar, so this keeps language switching reachable.
function MobileLangPill({ lang, setLang }) {
  const { LangToggle } = window.Ds3BPrintingDesignSystem_6b9cdf;
  return (
    <div className="mobile-lang-pill" style={{
      position: 'fixed', top: 12, right: 12, zIndex: 61,
      // LangToggle is now a self-contained glass pill — no outer background/shadow, or it'd nest a box.
    }}>
      <LangToggle value={lang} onChange={setLang} />
    </div>
  );
}

function SectionTitle({ children, lang = 'th', spot }) {
  const suffix = spot ? `--section-${spot}` : '';
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-4)', margin: '0 0 var(--space-6)' }}>
      <h2 style={{
        margin: 0, fontFamily: lang === 'th' ? `var(--font-heading-thai${suffix})` : `var(--font-heading${suffix})`,
        fontSize: lang === 'th' ? 'var(--text-h2-thai)' : 'var(--text-h2)',
        lineHeight: lang === 'th' ? 'var(--leading-h2-thai)' : 'var(--leading-h2)',
        fontWeight: lang === 'th' ? `var(--weight-heading-thai${suffix})` : `var(--weight-heading${suffix}, 600)`, color: 'var(--ink)',
      }}>{children}</h2>
      <span style={{ flex: 1, height: 1, background: 'var(--line)' }}></span>
    </div>
  );
}

function SiteFooter({ lang, footer }) {
  const { Footer } = window.Ds3BPrintingDesignSystem_6b9cdf;
  if (!footer) return null;
  const addressLines = ((footer.contact && footer.contact.address) || '').split('\n');
  return (
    <Footer
      logoSrc={footer.logo_path || '/assets/3b_logo.webp'}
      lineHref={footer.line_href}
      theme={footer.theme}
      contact={{
        address: addressLines.map((line, i) => (
          <React.Fragment key={i}>{line}{i < addressLines.length - 1 ? <br /> : null}</React.Fragment>
        )),
        phone: footer.contact && footer.contact.phone,
        email: footer.contact && footer.contact.email,
        website: footer.contact && footer.contact.website,
      }}
      legal={footer.legal}
      columns={(footer.columns || []).map((col) => ({
        title: lang === 'th' ? col.title_th : col.title_en,
        links: (col.links || []).map((l) => ({ label: lang === 'th' ? l.label_th : l.label_en, href: l.href })),
      }))}
      socials={footer.socials || []}
    />
  );
}

// Lookbook page/language transition — like turning the page of a high-end physical lookbook.
// The outgoing page recedes (scale 0.95, fade to 0.5) while the incoming page glides up from the
// bottom (translateY 100vh → 0) and fades in over the top of it. Both animate transform +
// opacity only, so the browser GPU-composites them — smooth across the long 1.2s ease-out.
//
// The outgoing page is captured as a cheap, inert DOM *clone* (a freeze-frame), NOT re-mounted
// as a second live React tree. Re-mounting the whole page (its gallery is ~115 images) ran every
// component + effect synchronously before paint — that was the hitch before the animation, and
// its duplicate LangToggle even wrote the old language back to localStorage. A clone has no
// components, effects, or side effects, so the transition starts instantly and cleanly. React
// only exposes the pre-update DOM via getSnapshotBeforeUpdate (a class hook), so a tiny
// snapshotter grabs the clone the instant before React paints the new language.
const LOOKBOOK_DURATION = 1200;   // ms — must match the CSS keyframe durations

// Generic getSnapshotBeforeUpdate helper: captures an inert clone of `liveRef`'s DOM the instant
// before `transitionKey` changes and React paints the new content. Shared by every transition
// below that needs a cheap freeze-frame of the outgoing page rather than a live, re-mounted
// tree (re-mounting the whole page — ~115 gallery images — ran every component + effect
// synchronously before paint, which is what caused the pre-transition lag in an earlier pass).
class DomSnapshotter extends React.Component {
  getSnapshotBeforeUpdate(prevProps) {
    if (prevProps.transitionKey !== this.props.transitionKey && this.props.liveRef.current) {
      const node = this.props.liveRef.current.cloneNode(true);   // exact freeze-frame of the OLD page
      node.classList.remove('lookbook-in');   // strip any in-flight animation class from the clone
      return { node, scrollY: window.scrollY };
    }
    return null;
  }
  componentDidUpdate(prevProps, prevState, snapshot) {
    if (snapshot) this.props.onCapture(snapshot);
  }
  render() { return this.props.children; }
}

// `reverse` plays the transition backwards (used when returning to the homepage): instead of the
// incoming page rising from the bottom, the OUTGOING page is pulled down off-screen while the
// incoming one swells back up from the receded state underneath it.
function LookbookTransition({ transitionKey, reverse = false, children }) {
  const liveRef = React.useRef(null);
  const idRef = React.useRef(0);
  const [tx, setTx] = React.useState(null);   // active transition: { id, node, scrollY }

  const onCapture = React.useCallback((snap) => {
    idRef.current += 1;
    setTx({ id: idRef.current, node: snap.node, scrollY: snap.scrollY });
  }, []);

  // Clear the overlay once the animation has run its course (timer-driven so it always clears).
  React.useEffect(() => {
    if (!tx) return;
    const done = setTimeout(() => setTx(null), LOOKBOOK_DURATION + 100);
    return () => clearTimeout(done);
  }, [tx]);

  // (Re)start the incoming rise on every transition — INCLUDING one fired mid-flight — by
  // removing the class, forcing a reflow, then re-adding it. `tx` is a fresh object each time,
  // so this runs for every trigger even though the live page node itself never remounts.
  const inClass = reverse ? 'lookbook-in-rev' : 'lookbook-in';
  React.useLayoutEffect(() => {
    const el = liveRef.current;
    if (!el) return;
    el.classList.remove('lookbook-in', 'lookbook-in-rev');
    if (tx) { void el.offsetWidth; el.classList.add(inClass); }
  }, [tx]);

  // Drop the inert clone into the receding overlay. Keyed by id, so every transition gets a
  // fresh element — and thus a fresh recede animation — even when re-fired mid-flight.
  const mountClone = React.useCallback((container) => {
    if (!container || !tx || container.firstChild) return;
    const inner = document.createElement('div');
    inner.style.transform = `translateY(${-tx.scrollY}px)`;
    inner.appendChild(tx.node);
    container.appendChild(inner);
  }, [tx]);

  return (
    <DomSnapshotter transitionKey={transitionKey} liveRef={liveRef} onCapture={onCapture}>
      <div ref={liveRef}>{children}</div>
      {tx && <div className={reverse ? 'lookbook-out-rev' : 'lookbook-out'} aria-hidden="true" key={tx.id} ref={mountClone}></div>}
    </DomSnapshotter>
  );
}

// "Gallery curtain" unveil — language-switch only. Nothing slides and nothing is cloned: each
// block of the re-rendered page is masked by a clip-path inset that retracts across it, while
// the images inside settle from scale(1.05) to scale(1). Blocks stagger (header 0ms, hero 100ms,
// then 150/200ms) and everything shares one 0.8s cubic-bezier(0.25, 1, 0.5, 1). No overlay, no
// freeze-frames — the page stays live and interactive throughout. See .lang-dissolve* in styles.css.
//
// `reverse` draws the curtain the other way (right→left) — used when switching back to Thai, so
// the two directions mirror each other rather than always sweeping the same way.
function LangFlipTransition({ transitionKey, reverse = false, children }) {
  const liveRef = React.useRef(null);
  const first = React.useRef(true);

  // Restart the curtain on every language switch — including one fired mid-flight — by removing
  // the class, forcing a reflow, then re-adding it. The live page node itself never remounts, so
  // scroll position and component state survive the transition untouched. Skipped on the very
  // first render: the page shouldn't unveil on initial load.
  React.useLayoutEffect(() => {
    if (first.current) { first.current = false; return; }
    const el = liveRef.current;
    if (!el) return;
    el.classList.remove('lang-dissolve', 'lang-dissolve-rev');
    void el.offsetWidth;
    el.classList.add(reverse ? 'lang-dissolve-rev' : 'lang-dissolve');
  }, [transitionKey]);

  return <div ref={liveRef}>{children}</div>;
}

Object.assign(window, {
  Shared: { LINE_HREF, url, t, Header, SectionTitle, SiteFooter, BottomTabBar, MobileLangPill, LookbookTransition, LangFlipTransition },
});
