/* primitives.jsx — shared building blocks (exported to window) */
const { useState, useEffect, useRef, useCallback } = React;

/* --- scroll reveal: adds .is-in when element enters viewport --- */
function useReveal() {
  useEffect(() => {
    const els = Array.from(document.querySelectorAll('[data-reveal]:not(.is-in)'));
    if (!('IntersectionObserver' in window)) { els.forEach((e) => e.classList.add('is-in')); return; }
    const io = new IntersectionObserver((entries) => {
      entries.forEach((en) => {
        if (en.isIntersecting) {
          const d = en.target.getAttribute('data-reveal-delay');
          if (d) en.target.style.transitionDelay = d + 'ms';
          en.target.classList.add('is-in');
          io.unobserve(en.target);
        }
      });
    }, { rootMargin: '0px 0px -12% 0px', threshold: 0.08 });
    els.forEach((e) => io.observe(e));
    return () => io.disconnect();
  });
}

/* --- parallax: returns a ref + drives translateY based on element center vs viewport --- */
function useParallax(strength = 0.18, enabled = true) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el || !enabled) { if (el) el.style.transform = ''; return; }
    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    let raf = 0;
    const update = () => {
      raf = 0;
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight || 1;
      const center = r.top + r.height / 2;
      const off = (center - vh / 2) / vh; // -1..1
      el.style.transform = `translate3d(0, ${(off * strength * 100).toFixed(2)}px, 0)`;
    };
    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); };
  }, [strength, enabled]);
  return ref;
}

/* --- a small eyebrow / kicker label in mono --- */
function Kicker({ children, className = '', dot = false }) {
  return (
    <div className={`flex items-center gap-3 font-mono text-[11.5px] tracking-label uppercase text-ink-faint ${className}`}>
      {dot && <span className="inline-block h-[6px] w-[6px] rounded-full" style={{ background: 'var(--accent)', boxShadow: '0 0 10px var(--accent-glow)' }}></span>}
      <span>{children}</span>
    </div>
  );
}

/* --- numbered section header with chrome numeral --- */
function SectionHead({ index, kicker, title, sub }) {
  return (
    <div className="mb-14 md:mb-20">
      <div className="flex items-baseline gap-5" data-reveal>
        <span className="font-mono text-[12px] tracking-label text-ink-ghost">{index}</span>
        <Kicker dot>{kicker}</Kicker>
      </div>
      <h2 data-reveal data-reveal-delay="80" className="font-display mt-5 max-w-[20ch] text-[clamp(2.4rem,6vw,5rem)] uppercase leading-[0.98] tracking-[-0.005em]">
        {title}
      </h2>
      {sub && (
        <p data-reveal data-reveal-delay="160" className="mt-6 max-w-[54ch] text-[16px] leading-relaxed text-ink-dim">
          {sub}
        </p>
      )}
    </div>
  );
}

/* --- framed photographic plate with caption; treats real auction photos w/ a gallery frame --- */
function Plate({ src, alt, caption, tag, className = '', frame = true, parallax = 0, fit = 'cover', ratio }) {
  const pref = useParallax(parallax, parallax !== 0);
  return (
    <figure className={`group relative overflow-hidden ${frame ? 'ring-1 ring-white/10' : ''} ${className}`} style={{ background: '#060607', aspectRatio: ratio }}>
      <div className="absolute inset-0 overflow-hidden">
        <img ref={pref} src={src} alt={alt}
             className="h-full w-full select-none"
             style={{ objectFit: fit, transition: 'transform 1.2s cubic-bezier(.16,.84,.36,1), filter .9s', filter: 'grayscale(0.18) contrast(1.04)' }}
             loading="lazy" />
      </div>
      {/* subtle inner shadow + scanline-free vignette */}
      <div className="pointer-events-none absolute inset-0" style={{ boxShadow: 'inset 0 0 120px rgba(0,0,0,0.6)', background: 'linear-gradient(180deg,rgba(0,0,0,0) 55%,rgba(0,0,0,0.55) 100%)' }}></div>
      {tag && (
        <div className="absolute left-0 top-0 m-3 flex items-center gap-2 bg-black/55 px-2.5 py-1 font-mono text-[10px] tracking-widest text-ink-dim backdrop-blur-sm">
          {tag}
        </div>
      )}
      {caption && (
        <figcaption className="absolute inset-x-0 bottom-0 flex items-end justify-between gap-4 p-4 font-mono text-[10.5px] tracking-wide text-ink-dim">
          <span className="max-w-[80%] leading-snug">{caption}</span>
        </figcaption>
      )}
    </figure>
  );
}

/* --- before/after reveal slider: drag the handle to wipe between two images.
   `before` shows left of the handle, `after` (clipped) shows right. Pointer +
   touch + keyboard accessible; no essential motion (reduced-motion safe). --- */
function CompareSlider({ before, beforeAlt, beforeLabel, after, afterAlt, afterLabel, ratio = '16 / 9', tag, caption, start = 50 }) {
  const [pos, setPos] = useState(start);
  const [hint, setHint] = useState(true);          // one-time "drag" affordance
  const ref = useRef(null);
  const dragging = useRef(false);
  const clamp = (n) => Math.max(0, Math.min(100, n));
  const setFromClientX = (x) => {
    const el = ref.current; if (!el) return;
    const r = el.getBoundingClientRect();
    setPos(clamp(((x - r.left) / r.width) * 100));
  };
  const onDown = (e) => { dragging.current = true; setHint(false); try { e.currentTarget.setPointerCapture(e.pointerId); } catch (_) {} setFromClientX(e.clientX); };
  const onMove = (e) => { if (dragging.current) setFromClientX(e.clientX); };
  const onUp = () => { dragging.current = false; };
  const onKey = (e) => {
    let d = 0;
    if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') d = -2;
    else if (e.key === 'ArrowRight' || e.key === 'ArrowUp') d = 2;
    else if (e.key === 'Home') return (setHint(false), setPos(0));
    else if (e.key === 'End') return (setHint(false), setPos(100));
    if (d) { e.preventDefault(); setHint(false); setPos((p) => clamp(p + d)); }
  };

  // subtle one-time auto-nudge when first scrolled into view, hinting interactivity
  useEffect(() => {
    const el = ref.current;
    if (!el || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    if (!('IntersectionObserver' in window)) return;
    let done = false;
    const io = new IntersectionObserver((ents) => {
      ents.forEach((en) => {
        if (en.isIntersecting && !done && hint) {
          done = true; io.disconnect();
          const steps = [start + 12, start - 6, start];
          steps.forEach((v, i) => setTimeout(() => dragging.current || setPos(v), 360 + i * 260));
        }
      });
    }, { threshold: 0.5 });
    io.observe(el);
    return () => io.disconnect();
  }, []);

  const imgStyle = { objectFit: 'cover', filter: 'grayscale(0.12) contrast(1.04)' };
  const pill = 'pointer-events-none absolute bottom-3 z-10 bg-black/55 px-2.5 py-1 font-mono text-[10px] uppercase tracking-widest text-ink-dim backdrop-blur-sm';
  return (
    <figure data-reveal className="group">
      <div ref={ref}
           className="relative select-none overflow-hidden ring-1 ring-white/10"
           style={{ aspectRatio: ratio, background: '#060607', touchAction: 'pan-y', cursor: 'ew-resize' }}
           onPointerDown={onDown} onPointerMove={onMove} onPointerUp={onUp} onPointerCancel={onUp}>
        {/* before (bottom) */}
        <img src={before} alt={beforeAlt} draggable="false" loading="lazy"
             className="absolute inset-0 h-full w-full" style={imgStyle} />
        {/* after (top, clipped to the right of the handle) */}
        <div className="absolute inset-0" style={{ clipPath: `inset(0 0 0 ${pos}%)` }}>
          <img src={after} alt={afterAlt} draggable="false" loading="lazy"
               className="absolute inset-0 h-full w-full" style={imgStyle} />
        </div>
        {/* shared vignette */}
        <div className="pointer-events-none absolute inset-0" style={{ boxShadow: 'inset 0 0 120px rgba(0,0,0,0.6)', background: 'linear-gradient(180deg,rgba(0,0,0,0) 60%,rgba(0,0,0,0.5) 100%)' }}></div>

        {tag && (
          <div className="pointer-events-none absolute left-0 top-0 z-10 m-3 flex items-center gap-2 bg-black/55 px-2.5 py-1 font-mono text-[10px] tracking-widest text-ink-dim backdrop-blur-sm">{tag}</div>
        )}
        {beforeLabel && <figcaption className={`${pill} left-3`}>{beforeLabel}</figcaption>}
        {afterLabel && <figcaption className={`${pill} right-3`}>{afterLabel}</figcaption>}

        {/* divider + handle */}
        <div className="pointer-events-none absolute inset-y-0 z-20" style={{ left: `${pos}%`, transform: 'translateX(-50%)' }}>
          <div className="absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-white/70" style={{ boxShadow: '0 0 10px rgba(0,0,0,0.6)' }}></div>
          <button type="button" role="slider" tabIndex={0} onKeyDown={onKey}
                  aria-label="Reveal slider: handwritten autograph to engraved brass plate"
                  aria-valuemin={0} aria-valuemax={100} aria-valuenow={Math.round(pos)}
                  className="pointer-events-auto absolute top-1/2 left-1/2 grid h-10 w-10 -translate-x-1/2 -translate-y-1/2 place-items-center rounded-full border border-white/70 bg-black/55 text-ink backdrop-blur-sm transition-colors hover:border-white focus:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent-text)]"
                  style={{ cursor: 'ew-resize' }}>
            <span aria-hidden="true" className="text-[15px] leading-none">↔</span>
          </button>
        </div>

        {hint && (
          <div className="pointer-events-none absolute left-1/2 top-3 z-20 -translate-x-1/2 bg-black/55 px-2.5 py-1 font-mono text-[9.5px] uppercase tracking-[0.24em] text-ink-dim backdrop-blur-sm transition-opacity duration-500">Drag to compare</div>
        )}
      </div>
      {caption && (
        <figcaption className="flex items-center justify-between gap-4 px-1 pt-3 font-mono text-[10.5px] uppercase tracking-[0.2em] text-ink-faint">
          <span>{caption}</span>
        </figcaption>
      )}
    </figure>
  );
}

/* --- a generic styled placeholder (striped) for media not yet supplied --- */
function Slot({ label, ratio = '16 / 9', className = '', children }) {
  return (
    <div className={`relative grid place-items-center overflow-hidden ring-1 ring-white/10 ${className}`}
         style={{ aspectRatio: ratio, background:
           'repeating-linear-gradient(135deg, #0e0e11 0 14px, #121216 14px 28px)' }}>
      <div className="absolute inset-0" style={{ boxShadow: 'inset 0 0 90px rgba(0,0,0,0.7)' }}></div>
      <div className="relative text-center">
        <div className="font-mono text-[10.5px] uppercase tracking-[0.32em] text-ink-faint">{label}</div>
        {children}
      </div>
    </div>
  );
}

/* --- small circular "i" info trigger; place inside a `relative` container --- */
function InfoButton({ onClick, label = 'More info', className = '' }) {
  return (
    <button type="button" onClick={onClick} aria-label={label}
      className={`absolute right-2.5 top-2.5 z-10 grid h-[22px] w-[22px] place-items-center rounded-full border border-white/20 text-ink-faint transition-colors hover:border-[color:var(--accent)] hover:text-[color:var(--accent)] ${className}`}>
      <svg viewBox="0 0 24 24" width="12" height="12" fill="none" aria-hidden="true">
        <circle cx="12" cy="12" r="9.5" stroke="currentColor" strokeWidth="1.6" />
        <circle cx="12" cy="7.6" r="1.15" fill="currentColor" />
        <path d="M12 10.8v6" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" />
      </svg>
    </button>
  );
}

/* --- generic right-side slide-in panel (generalized from Journey's DetailDrawer) --- */
function InfoDrawer({ open, title, eyebrow, onClose, children }) {
  useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [open, onClose]);

  // Portal to <body> so `position: fixed` always anchors to the viewport, even when
  // a transformed / will-change ancestor (e.g. [data-reveal]) would otherwise create
  // a containing block and mis-position the panel.
  return ReactDOM.createPortal(
    <div className="fixed inset-0 z-[80] overflow-hidden" style={{ pointerEvents: open ? 'auto' : 'none' }} aria-hidden={!open}>
      <div onClick={onClose} className="absolute inset-0 transition-opacity duration-300"
           style={{ background: 'rgba(4,4,5,0.7)', backdropFilter: open ? 'blur(3px)' : 'none', opacity: open ? 1 : 0 }}></div>
      <aside className="absolute right-0 top-0 flex h-full w-full max-w-[440px] flex-col border-l border-white/12 bg-coal-800"
             style={{ transform: open ? 'translateX(0)' : 'translateX(100%)', transition: 'transform 500ms cubic-bezier(.16,.84,.36,1)', boxShadow: '-30px 0 80px rgba(0,0,0,0.6)' }}
             role="dialog" aria-modal="true">
        <div className="flex items-start justify-between gap-4 border-b border-white/10 p-6">
          <div>
            {eyebrow && <div className="mb-2 font-mono text-[10.5px] uppercase tracking-[0.24em]" style={{ color: 'var(--accent-text)' }}>{eyebrow}</div>}
            <h3 className="font-display text-[1.7rem] uppercase leading-none tracking-tight text-ink">{title}</h3>
          </div>
          <button onClick={onClose} aria-label="Close"
                  className="grid h-9 w-9 shrink-0 place-items-center border border-white/30 text-ink transition-colors hover:border-white/55">✕</button>
        </div>
        <div className="flex-1 overflow-y-auto p-6">
          {children}
        </div>
      </aside>
    </div>,
    document.body
  );
}

/* --- consistent body paragraph(s) for InfoDrawer; accepts a string or array --- */
function InfoBody({ children }) {
  const paras = Array.isArray(children) ? children : [children];
  return (
    <div className="space-y-4">
      {paras.map((p, i) => (
        <p key={i} className="text-[14px] leading-relaxed text-ink">{p}</p>
      ))}
    </div>
  );
}

Object.assign(window, { useReveal, useParallax, Kicker, SectionHead, Plate, CompareSlider, Slot, InfoButton, InfoDrawer, InfoBody });
