// 228 ACADEMY · atoms partagés
// Tokens (CSS-var-backed pour theme switch), Lang context, Reveal,
// Sceau, Container, Kicker, MonoCap, Rule, CTA.

// ─── Tokens ────────────────────────────────────────────────────
// Tous les couleurs passent par les CSS vars définies dans academy.html.
// Le theme switch (data-theme) bascule ivory ↔ bg-deep, ink ↔ ivory, etc.
const TOK = {
  ivory:    'var(--bg)',
  paper:    'var(--bg-paper)',
  deep:     'var(--bg-deep)',
  ink:      'var(--fg)',
  inkSoft:  'var(--fg-soft)',
  inkMed:   'var(--fg-medium)',
  ink70:    'var(--fg-70)',
  ink55:    'var(--fg-55)',
  ink35:    'var(--fg-35)',
  ink22:    'var(--fg-22)',
  ink18:    'var(--fg-18)',
  ink10:    'var(--fg-10)',
  ink06:    'var(--fg-06)',
  bronze:   'var(--bronze)',
  aurum:    'var(--aurum)',
  gris:     'var(--gris)',
  card:     'var(--bg-card)',
  cardHov:  'var(--bg-card-hover)',
  tileB:    'var(--tile-border)',
  tileBOuv: 'var(--tile-border-ouvert)',
  sans:     '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
  mono:     '"JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace',
  garamond: '"EB Garamond", Georgia, serif',
};

// ─── Lang context (FR / EN) ────────────────────────────────────
const LangCtx = React.createContext({ lang: 'fr', setLang: () => {} });
function LangProvider({ children }) {
  const [lang, setLangState] = React.useState(() => {
    try { return localStorage.getItem('academy-lang') || 'fr'; } catch { return 'fr'; }
  });
  const setLang = (v) => {
    if (v === lang) return;
    const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (reduced) {
      setLangState(v);
      try { localStorage.setItem('academy-lang', v); } catch {}
      document.documentElement.setAttribute('data-lang', v);
      document.documentElement.setAttribute('lang', v);
      return;
    }
    // Crossfade FR ↔ EN · dim body 130ms, swap, restore 200ms
    document.body.style.transition = 'opacity .13s ease';
    document.body.style.opacity = '0.35';
    setTimeout(() => {
      setLangState(v);
      try { localStorage.setItem('academy-lang', v); } catch {}
      document.documentElement.setAttribute('data-lang', v);
      document.documentElement.setAttribute('lang', v);
      requestAnimationFrame(() => {
        document.body.style.transition = 'opacity .20s ease';
        document.body.style.opacity = '1';
      });
    }, 130);
  };
  React.useEffect(() => {
    document.documentElement.setAttribute('data-lang', lang);
    document.documentElement.setAttribute('lang', lang);
  }, [lang]);
  return <LangCtx.Provider value={{ lang, setLang }}>{children}</LangCtx.Provider>;
}
const useLang = () => React.useContext(LangCtx);
const useTr = () => { const { lang } = useLang(); return (m) => (m && typeof m === 'object' && 'fr' in m ? m[lang] || m.fr : m); };

// ─── Theme (light / dark) ──────────────────────────────────────
const ThemeCtx = React.createContext({ theme: 'light', setTheme: () => {} });
function ThemeProvider({ children }) {
  const [theme, setThemeState] = React.useState(() => {
    try { return localStorage.getItem('academy-theme') || 'dark'; } catch { return 'dark'; }
  });
  const setTheme = (v) => {
    setThemeState(v);
    try { localStorage.setItem('academy-theme', v); } catch {}
    document.documentElement.setAttribute('data-theme', v);
  };
  React.useEffect(() => {
    document.documentElement.setAttribute('data-theme', theme);
  }, [theme]);
  return <ThemeCtx.Provider value={{ theme, setTheme }}>{children}</ThemeCtx.Provider>;
}
const useTheme = () => React.useContext(ThemeCtx);

// ─── Reveal · IntersectionObserver fade-up ─────────────────────
function Reveal({ as = 'div', delay, children, style, className = '', once = true, threshold = 0.15 }) {
  const ref = React.useRef(null);
  const [shown, setShown] = React.useState(false);
  React.useEffect(() => {
    const el = ref.current; if (!el) return;
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setShown(true); if (once) io.disconnect(); }
      else if (!once) setShown(false);
    }, { threshold });
    io.observe(el);
    return () => io.disconnect();
  }, [once, threshold]);
  const Cls = as;
  return (
    <Cls ref={ref}
      className={`reveal ${shown ? 'is-in' : ''} ${delay ? 'reveal-delay-' + delay : ''} ${className}`}
      style={style}>{children}</Cls>
  );
}

// ─── Typographic primitives ────────────────────────────────────
function Kicker({ children, color, style }) {
  return <div style={{
    fontFamily: TOK.mono, fontSize: 11, letterSpacing: '.22em',
    textTransform: 'uppercase', color: color || TOK.bronze, ...style,
  }}>{children}</div>;
}
function MonoCap({ children, color, style }) {
  return <span style={{
    fontFamily: TOK.mono, fontSize: 11, letterSpacing: '.2em',
    textTransform: 'uppercase', color: color || TOK.ink, ...style,
  }}>{children}</span>;
}
function Rule({ tone = 'soft', vertical = false, style }) {
  const c = tone === 'strong' ? TOK.ink : tone === 'gold' ? TOK.bronze : TOK.ink10;
  return <div style={{
    background: c,
    ...(vertical ? { width: 1, height: '100%' } : { height: 1, width: '100%' }),
    ...style,
  }} />;
}

// ─── Sceau (mini, géométrie cross-brand cercle + 3 cardinaux à 120°) ──
// Triangle équilatéral pointe en haut · respecte la verrou « 3 cardinaux à 120° »
// du brief (la version proto avait 3 dots à 90°, on aligne ici sur la spec).
function Sceau({ size = 22, color, withCenter = false, rotate = false, style }) {
  const c = color || TOK.bronze;
  // Cercle r=9.5 centré (11,11). 3 dots à 120° :
  // top (11, 1.5) · bottom-right (19.23, 15.75) · bottom-left (2.77, 15.75)
  return (
    <svg width={size} height={size} viewBox="0 0 22 22" style={style} aria-hidden="true">
      <circle cx="11" cy="11" r="9.5" fill="none" stroke={c} strokeWidth=".8" />
      <g>
        <circle cx="11" cy="11" r="6" fill="none" stroke={c} strokeWidth=".4" opacity=".5" />
        <circle cx="11"    cy="1.5"  r="1.3" fill={c} />
        <circle cx="19.23" cy="15.75" r="1.3" fill={c} />
        <circle cx="2.77"  cy="15.75" r="1.3" fill={c} />
        {rotate && (
          <animateTransform attributeName="transform" type="rotate"
            from="0 11 11" to="360 11 11" dur="24s" repeatCount="indefinite" />
        )}
      </g>
      {withCenter && <circle cx="11" cy="11" r="1.2" fill={c} />}
    </svg>
  );
}

// ─── Container ─────────────────────────────────────────────────
function Container({ children, style, narrow = false }) {
  return <div style={{
    maxWidth: narrow ? 1100 : 1400,
    margin: '0 auto',
    padding: '0 clamp(28px, 4vw, 72px)',
    width: '100%', position: 'relative',
    ...style,
  }}>{children}</div>;
}

// ─── CTA · primary (round noir) + tertiary (text + arrow bronze) ──
function CTA({ children, kind = 'primary', meta, onClick, href, style }) {
  const [hover, setHover] = React.useState(false);
  if (kind === 'primary') {
    const Btn = href ? 'a' : 'button';
    return (
      <div style={{ display: 'inline-flex', alignItems: 'center', gap: 18, flexWrap: 'wrap', ...style }}>
        <Btn
          href={href}
          onClick={onClick}
          onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
          style={{
            border: 0, cursor: 'pointer', textDecoration: 'none',
            background: TOK.ink, color: TOK.ivory,
            padding: '15px 28px 15px 26px', borderRadius: 999,
            fontFamily: TOK.sans, fontSize: 15, fontWeight: 500, letterSpacing: '.005em',
            display: 'inline-flex', alignItems: 'center', gap: 12,
            transition: 'background .25s, transform .25s, box-shadow .25s',
            boxShadow: hover
              ? `0 14px 38px rgba(10,10,10,.18), 0 0 0 6px rgba(139,111,31,.18)`
              : '0 1px 0 rgba(10,10,10,.04), 0 6px 22px rgba(10,10,10,.10)',
          }}>
          {children}
          <span style={{
            color: TOK.aurum,
            transform: `translateX(${hover ? 4 : 0}px)`,
            transition: 'transform .25s', display: 'inline-block',
          }}>→</span>
        </Btn>
        {meta && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
            {meta.map((m, i) => (
              <span key={i} style={{
                fontFamily: TOK.mono, fontSize: 11, letterSpacing: '.14em',
                color: TOK.ink55, textTransform: 'uppercase',
              }}>{m}</span>
            ))}
          </div>
        )}
      </div>
    );
  }
  // tertiary
  const Btn = href ? 'a' : 'button';
  return (
    <Btn href={href} onClick={onClick}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{
        all: 'unset', cursor: 'pointer',
        display: 'inline-flex', alignItems: 'center', gap: 8,
        fontFamily: TOK.sans, fontSize: 14, fontWeight: 500, color: TOK.ink,
        borderBottom: `1px solid ${hover ? TOK.bronze : TOK.ink22}`,
        paddingBottom: 4, transition: 'border-color .2s',
        ...style,
      }}>
      {children}
      <span style={{
        color: TOK.bronze,
        transform: `translateX(${hover ? 3 : 0}px)`,
        transition: 'transform .2s',
      }}>→</span>
    </Btn>
  );
}

// ─── Scroll progress · hairline bronze 1px top ──────────────
function ScrollProgress() {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current; if (!el) return;
    let raf;
    const update = () => {
      const h = document.documentElement.scrollHeight - window.innerHeight;
      const p = h > 0 ? Math.max(0, Math.min(1, window.scrollY / h)) : 0;
      el.style.width = (p * 100) + '%';
      raf = null;
    };
    const onScroll = () => { if (!raf) raf = requestAnimationFrame(update); };
    update();
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);
  return (
    <div aria-hidden="true" ref={ref} style={{
      position: 'fixed', top: 0, left: 0, height: 2,
      width: '0%', background: TOK.bronze,
      zIndex: 60, pointerEvents: 'none',
    }} />
  );
}

// ─── Tweaks context ────────────────────────────────────────────
const TweaksCtx = React.createContext({ t: {}, setTweak: () => {} });
const useTweaksLive = () => React.useContext(TweaksCtx);

// ─── Page context · multi-page routing (home / mentions / privacy / cgv) ──
// Mirror OPERATORS V12 page system. Hash-based pour URL bookmarkable.
const PageCtx = React.createContext({ page: 'home', setPage: () => {} });
const VALID_PAGES = ['mandats', 'immobilier', 'contact', 'mentions', 'privacy', 'cgv'];
function PageProvider({ children }) {
  const initial = (() => {
    try {
      const h = (window.location.hash || '').replace('#', '');
      if (VALID_PAGES.includes(h)) return h;
    } catch {}
    return 'home';
  })();
  const [page, setPageState] = React.useState(initial);
  const setPage = (p) => {
    setPageState(p);
    try {
      if (p === 'home') {
        history.pushState({}, '', window.location.pathname);
      } else {
        window.location.hash = p;
      }
      window.scrollTo({ top: 0, behavior: 'auto' });
    } catch {}
  };
  React.useEffect(() => {
    const onHash = () => {
      const h = (window.location.hash || '').replace('#', '');
      setPageState(VALID_PAGES.includes(h) ? h : 'home');
    };
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);
  return <PageCtx.Provider value={{ page, setPage }}>{children}</PageCtx.Provider>;
}
const usePage = () => React.useContext(PageCtx);

// ─── Viewport context · isMobile detection ────────────────────
const ViewportCtx = React.createContext({ isMobile: false });
function ViewportProvider({ children }) {
  const [isMobile, setIsMobile] = React.useState(() => {
    try { return window.matchMedia('(max-width: 720px)').matches; } catch { return false; }
  });
  React.useEffect(() => {
    const mq = window.matchMedia('(max-width: 720px)');
    const update = () => setIsMobile(mq.matches);
    mq.addEventListener ? mq.addEventListener('change', update) : mq.addListener(update);
    return () => {
      mq.removeEventListener ? mq.removeEventListener('change', update) : mq.removeListener(update);
    };
  }, []);
  return <ViewportCtx.Provider value={{ isMobile }}>{children}</ViewportCtx.Provider>;
}
const useViewport = () => React.useContext(ViewportCtx);

// ─── LegalBlock · pattern label kicker + body, pour pages mentions/privacy/cgv ──
function LegalBlock({ label, children }) {
  return (
    <div>
      <div style={{
        fontFamily: TOK.mono, fontSize: 10, letterSpacing: '.22em',
        color: TOK.bronze, marginBottom: 12, textTransform: 'uppercase',
      }}>{label}</div>
      <div style={{
        fontFamily: TOK.sans, fontSize: 15.5, lineHeight: 1.65,
        color: TOK.inkSoft, textWrap: 'pretty',
      }}>{children}</div>
    </div>
  );
}

Object.assign(window, {
  TOK, LangCtx, LangProvider, useLang, useTr,
  ThemeCtx, ThemeProvider, useTheme,
  Reveal, Kicker, MonoCap, Rule, Sceau, Container, CTA,
  TweaksCtx, useTweaksLive, ScrollProgress,
  PageCtx, PageProvider, usePage,
  ViewportCtx, ViewportProvider, useViewport,
  LegalBlock,
});
