// app.jsx — main App: routing, palette/font wiring, tweaks panel.

const { useState, useEffect, useRef, useMemo } = React;
const {
  Placeholder, Icon, Stars, Nav, Hero, WalkInBanner,
  Services, SectionHead, Gallery, Team, Reviews, t,
  PortfolioBanner, usePortfolio, interceptCta,
} = window.Components;
const {
  BookingBlock, BookingModal, Location, Footer,
  ServicesPage, StylistPage,
} = window.Components2;

// ── Floating WhatsApp button ────────────────────────────────────────────
function WhatsAppFab({ business, lang }) {
  const C = window.SITE.COPY.booking;
  const waMsg = encodeURIComponent(`${t(C.waMsg, lang)} serviço`);
  const waUrl = `https://wa.me/${business.whatsapp.replace(/\D/g, '')}?text=${waMsg}`;
  const onClick = interceptCta(undefined, lang);
  return (
    <a href={waUrl} target="_blank" rel="noreferrer" className="wa-fab" aria-label="WhatsApp"
       onClick={onClick}>
      <svg viewBox="0 0 24 24" width="28" height="28" fill="currentColor">
        <path d="M3.5 20.5l1.5-4.7A8.5 8.5 0 1112 20.5a8.5 8.5 0 01-4.3-1.2l-4.2 1.2z"/>
        <path d="M8.5 10.5c.3 1.4 1.3 2.8 2.5 3.8 1.3 1 2.8 1.5 4.2 1.2l.8-1.7-2.1-.8-.8 1.2c-1.2-.4-2.1-1.2-2.5-2.5l1.2-.8-.8-2.1-2.5.7z"
              fill="white" stroke="none"/>
      </svg>
    </a>
  );
}

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": "ab-brand",
  "fontPair": "display-modern",
  "category": "aesthetics",
  "bookingProvider": "fresha",
  "lang": "en"
}/*EDITMODE-END*/;

// ── Inject Google Fonts links for active font pair ──────────────────────
function useFontLoader(pairKey) {
  useEffect(() => {
    const pair = window.SITE.FONT_PAIRS[pairKey];
    if (!pair) return;
    const href = `https://fonts.googleapis.com/css2?${pair.googleFonts}&display=swap`;
    const id = `font-${pairKey}`;
    if (document.getElementById(id)) return;
    const link = document.createElement('link');
    link.id = id; link.rel = 'stylesheet'; link.href = href;
    document.head.appendChild(link);
  }, [pairKey]);
}

// ── Apply palette tokens to :root as CSS custom properties ──────────────
function usePaletteTokens(paletteKey, fontPairKey) {
  useEffect(() => {
    const pal = window.SITE.PALETTES[paletteKey];
    const pair = window.SITE.FONT_PAIRS[fontPairKey];
    if (!pal || !pair) return;
    const root = document.documentElement;
    Object.entries(pal.tokens).forEach(([k, v]) => {
      if (typeof v === 'boolean') {
        root.setAttribute(`data-${k}`, v ? '1' : '0');
      } else {
        root.style.setProperty(`--c-${k}`, v);
      }
    });
    root.style.setProperty('--font-heading', pair.heading);
    root.style.setProperty('--font-body', pair.body);
    root.style.setProperty('--font-heading-weight', pair.headingWeight);
    root.style.setProperty('--font-heading-tracking', pair.headingStyle.letterSpacing || '0');
    root.setAttribute('data-palette', paletteKey);
  }, [paletteKey, fontPairKey]);
}

// ── Reveal-on-scroll: a single global IntersectionObserver that adds
// the .is-revealed class to anything tagged [data-reveal] when it enters
// the viewport. Section components opt in declaratively — no per-element
// JS. Honors prefers-reduced-motion (instantly reveals everything).
function useRevealObserver() {
  useEffect(() => {
    const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (reduced) {
      document.querySelectorAll('[data-reveal]').forEach(el => el.classList.add('is-revealed'));
      return;
    }
    const io = new IntersectionObserver((entries) => {
      for (const e of entries) {
        if (e.isIntersecting) {
          e.target.classList.add('is-revealed');
          io.unobserve(e.target);
        }
      }
    }, { threshold: 0.12, rootMargin: '0px 0px -60px 0px' });

    const seed = () => {
      document.querySelectorAll('[data-reveal]:not(.is-revealed)').forEach(el => {
        // Tag stagger children with --stagger from their index inside their parent
        const parent = el.closest('[data-reveal-group]');
        if (parent && !el.style.getPropertyValue('--stagger')) {
          const i = [...parent.querySelectorAll('[data-reveal]')].indexOf(el);
          if (i >= 0) el.style.setProperty('--stagger', `${i * 70}ms`);
        }
        io.observe(el);
      });
    };
    seed();

    // Re-seed when the DOM changes (route changes, lazy renders)
    const mo = new MutationObserver(() => seed());
    mo.observe(document.body, { childList: true, subtree: true });

    return () => { io.disconnect(); mo.disconnect(); };
  }, []);
}

// ── Smooth-scroll to anchor (with sticky nav offset) ────────────────────
function scrollToId(id) {
  const el = document.getElementById(id);
  if (!el) return;
  const top = el.getBoundingClientRect().top + window.scrollY - 70;
  window.scrollTo({ top, behavior: 'smooth' });
}

// Resolve the visitor's language once:
//  1. an explicit prior choice this visitor made (localStorage 'salon.lang')
//  2. otherwise the browser/OS UI language (navigator.language[s])
//  3. otherwise English as the safe default
//
// navigator.languages is already ordered by the visitor's own preference, so
// walking it in order picks their best match without asking them anything.
// Region subtags are ignored on purpose: pt-BR and pt-PT both get pt, es-MX
// and es-ES both get es. Most visitors never touch the picker.
function detectVisitorLang() {
  try {
    const saved = localStorage.getItem('salon.lang');
    if (window.SITE.LANGS.some(l => l.code === saved)) return saved;
  } catch (e) { /* private mode / disabled storage */ }
  const navLangs = (navigator.languages && navigator.languages.length)
    ? navigator.languages
    : [navigator.language || navigator.userLanguage || 'en'];
  for (const l of navLangs) {
    const base = String(l).toLowerCase().split('-')[0];
    if (window.SITE.LANGS.some(x => x.code === base)) return base;
  }
  return 'en';
}

// Resolve the visitor's colour theme once:
//  1. an explicit prior choice this visitor made (localStorage 'salon.theme')
//  2. otherwise the OS preference (prefers-color-scheme: dark)
//  3. otherwise the light theme
function detectVisitorTheme() {
  try {
    const saved = localStorage.getItem('salon.theme');
    if (saved === 'light' || saved === 'dark') return saved;
  } catch (e) { /* private mode / disabled storage */ }
  try {
    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark';
  } catch (e) { /* ignore */ }
  return 'light';
}

function App() {
  const [tw, setTw] = useTweaks(TWEAK_DEFAULTS);
  // Visitor language is independent of the design-time tweak default. It is
  // detected from the browser and remembered per-visitor in localStorage; the
  // language picker writes there, not into the persisted EDITMODE tweak block.
  const [lang, setLangState] = useState(detectVisitorLang);
  const setLang = (v) => {
    setLangState(v);
    try { localStorage.setItem('salon.lang', v); } catch (e) { /* ignore */ }
  };
  // Screen readers and search engines read this attribute, not our state.
  useEffect(() => { document.documentElement.lang = lang; }, [lang]);

  // Visitor colour theme — independent of the design-time palette tweak.
  // Detected from the OS, remembered per-visitor; the nav toggle writes here.
  const [theme, setThemeState] = useState(detectVisitorTheme);
  const setTheme = (v) => {
    setThemeState(v);
    try { localStorage.setItem('salon.theme', v); } catch (e) { /* ignore */ }
  };
  // Dark mode swaps the active palette for its dark counterpart, so the
  // toggle works whatever light palette the design-time tweak selected.
  const darkPaletteKey =
    (window.SITE.PALETTES[tw.palette] || {}).darkVariant || 'ab-brand-dark';
  const activePalette = theme === 'dark' ? darkPaletteKey : tw.palette;

  useFontLoader(tw.fontPair);
  usePaletteTokens(activePalette, tw.fontPair);
  useRevealObserver();

  // Keep <html data-theme> in sync with the toggle.
  useEffect(() => {
    document.documentElement.setAttribute('data-theme', theme);
  }, [theme]);

  // Router: 'home' | 'services' | { kind: 'stylist', id }
  const [view, setView] = useState('home');
  const [stylistId, setStylistId] = useState(null);
  const [pendingAnchor, setPendingAnchor] = useState(null);
  const [bookingModal, setBookingModal] = useState(null); // null | { preset? }

  // After switching back to home, scroll to pending anchor
  useEffect(() => {
    if (view === 'home' && pendingAnchor) {
      requestAnimationFrame(() => {
        if (pendingAnchor === 'hero') window.scrollTo({ top: 0, behavior: 'smooth' });
        else scrollToId(pendingAnchor);
        setPendingAnchor(null);
      });
    }
  }, [view, pendingAnchor]);

  // Top-of-page on sub-route enter
  useEffect(() => {
    if (view !== 'home') window.scrollTo({ top: 0, behavior: 'auto' });
  }, [view, stylistId]);

  const onNav = (target, anchor) => {
    if (target === 'home') {
      if (view === 'home') {
        if (anchor === 'hero') window.scrollTo({ top: 0, behavior: 'smooth' });
        else scrollToId(anchor);
      } else {
        setPendingAnchor(anchor);
        setView('home');
      }
    } else if (target === 'services') {
      setView('services');
    } else if (target === 'stylist') {
      setStylistId(anchor);
      setView('stylist');
    }
  };

  const portfolio = usePortfolio();
  // Booking has two entry points now:
  //   1. onBookFresha — every generic "Book" CTA opens the live Fresha
  //      booking page directly in a new tab. No modal, no service picker —
  //      Fresha already has its own.
  //   2. onBookWhatsApp — the WhatsApp card in BookingBlock opens the modal
  //      so the visitor can pick services and we build the WA message body.
  // In portfolio mode every booking attempt is intercepted with an alert
  // (both flows). Real client deploys (PORTFOLIO.enabled: false) get the
  // normal handlers.
  const freshaUrl = window.SITE.BOOKING_PROVIDERS.fresha.mockUrl;
  const onBookFresha = portfolio.enabled
    ? interceptCta(undefined, lang)
    : (() => window.open(freshaUrl, '_blank', 'noopener,noreferrer'));
  const onBookWhatsApp = portfolio.enabled
    ? interceptCta(undefined, lang)
    : ((preset = null) => setBookingModal({ preset }));
  const closeBookingModal = () => setBookingModal(null);

  const business = window.SITE.BUSINESS;
  // Keep the booking/location card layout identical in portfolio mode;
  // hide only the floating WhatsApp FAB so the demo isn't dialling out.
  const hasRealWhatsapp = !!business.whatsapp
    && !/^\{\{.*\}\}$/.test(business.whatsapp);
  const hasWhatsapp = hasRealWhatsapp;
  const showFab = hasRealWhatsapp && !portfolio.enabled;

  return (
    <div className={`app ${portfolio.enabled ? 'app--portfolio' : ''}`}>
      <PortfolioBanner lang={lang}/>
      <Nav lang={lang} setLang={setLang} view={view}
           theme={theme} setTheme={setTheme}
           business={business}
           onBook={onBookFresha}
           onNav={onNav}/>

      {view === 'home' && (
        <main>
          <Hero lang={lang} category={tw.category} palette={activePalette}
                onBook={onBookFresha}
                onScrollTo={(id) => scrollToId(id)}/>
          <WalkInBanner lang={lang}/>
          <Services lang={lang} category={tw.category}
                    onBook={onBookFresha}
                    onViewAll={() => setView('services')}/>
          <Gallery lang={lang}/>
          {/* Solo studio: <About/> carries the portrait, the story and the
              link into the full profile, so the <Team/> grid would just
              repeat the same photo one screen later. Multi-stylist clients
              of this template render <Team/> here instead. */}
          <About lang={lang} onProfile={(id) => onNav('stylist', id)}/>
          <Reviews lang={lang}/>
          <BookingBlock lang={lang} business={business}
                        bookingProvider={tw.bookingProvider}
                        hasWhatsapp={hasWhatsapp}
                        freshaUrl={freshaUrl}
                        onOpenWhatsAppModal={() => onBookWhatsApp()}/>
          <Location lang={lang} business={business} hasWhatsapp={hasWhatsapp}/>
        </main>
      )}

      {view === 'services' && (
        <main className="page-main">
          <ServicesPage lang={lang} category={tw.category}
                        onBook={onBookFresha}
                        onBack={() => onNav('home', 'services')}/>
        </main>
      )}

      {view === 'stylist' && (
        <main className="page-main">
          <StylistPage lang={lang} memberId={stylistId} category={tw.category}
                       onBook={onBookFresha}
                       onBack={() => onNav('home', 'team')}/>
        </main>
      )}

      <Footer lang={lang} business={business} onNav={onNav}/>

      {bookingModal && (
        <BookingModal lang={lang} category={tw.category}
                      bookingProvider={tw.bookingProvider}
                      business={business}
                      preset={bookingModal.preset}
                      onClose={closeBookingModal}/>
      )}

      {showFab && (
        <WhatsAppFab business={business} lang={lang}/>
      )}

      <TweaksPanel title={business.name && !/^\{\{.*\}\}$/.test(business.name) ? business.name : 'Salon Template'}>
        <TweakSection label={lang === 'en' ? 'Palette' : 'Paleta'}>
          <TweakColor label={lang === 'en' ? 'Colour theme' : 'Tema de cores'}
                      value={paletteSwatch(tw.palette)}
                      options={Object.keys(window.SITE.PALETTES).map(paletteSwatch)}
                      onChange={(v) => setTw('palette', paletteFromSwatch(v))}/>
          <div className="twk-palette-name">{t(window.SITE.PALETTES[tw.palette].label, lang)}
            <span className="twk-palette-vibe"> · {t(window.SITE.PALETTES[tw.palette].vibe, lang)}</span>
          </div>
        </TweakSection>

        <TweakSection label={lang === 'en' ? 'Typography' : 'Tipografia'}>
          <TweakSelect label={lang === 'en' ? 'Font pair' : 'Par de fontes'}
                       value={tw.fontPair}
                       options={Object.entries(window.SITE.FONT_PAIRS).map(([k, v]) =>
                         ({ value: k, label: t(v.label, lang) }))}
                       onChange={(v) => setTw('fontPair', v)}/>
        </TweakSection>
      </TweaksPanel>
    </div>
  );
}

// Encode the palette as an array of 3 colors so TweakColor renders it as a
// real palette-swatch chip (hero + 2 dots). The decode goes by reference
// equality on the first colour, so swap is lossless.
function paletteSwatch(key) {
  const p = window.SITE.PALETTES[key].tokens;
  return [p.primary, p.bg, p.text, p.accent];
}
function paletteFromSwatch(arr) {
  const hero = Array.isArray(arr) ? arr[0] : arr;
  return Object.entries(window.SITE.PALETTES).find(
    ([, v]) => v.tokens.primary === hero
  )?.[0] || 'ab-brand';
}

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