/* global React, window */

// ── FM Polyfill — CSS transitions + IntersectionObserver + RAF (no CDN) ───────

function _fmEase(e) {
  if (!e) return 'ease';
  if (Array.isArray(e)) return `cubic-bezier(${e.join(',')})`;
  return ({easeOut:'cubic-bezier(0.25,1,0.5,1)',easeIn:'cubic-bezier(0.5,0,1,1)',easeInOut:'cubic-bezier(0.45,0,0.55,1)',linear:'linear'})[e] || 'ease';
}

function _fmCSS(fm) {
  if (!fm || typeof fm !== 'object') return {};
  const parts = [], out = {};
  if (fm.y !== undefined) parts.push(`translateY(${typeof fm.y === 'string' ? fm.y : fm.y+'px'})`);
  if (fm.x !== undefined) parts.push(`translateX(${typeof fm.x === 'string' ? fm.x : fm.x+'px'})`);
  if (fm.scale !== undefined) parts.push(`scale(${fm.scale})`);
  if (fm.scaleX !== undefined) parts.push(`scaleX(${fm.scaleX})`);
  if (fm.rotate !== undefined) parts.push(`rotate(${fm.rotate}deg)`);
  if (parts.length) out.transform = parts.join(' ');
  if (fm.opacity !== undefined) out.opacity = String(fm.opacity);
  return out;
}

function _makeEl(Tag) {
  return function MotionEl({ variants, initial: _i, animate: _a, transition: _t, exit: _e, _isExiting, style, children, ...rest }) {
    const ref = React.useRef(null);
    const prevKey = React.useRef(null);
    const prevExiting = React.useRef(false);
    const vInit    = typeof _i === 'string' && variants ? variants[_i] : _i;
    const vAnimRaw = typeof _a === 'string' && variants ? variants[_a] : _a;
    const inlineTr = vAnimRaw && typeof vAnimRaw === 'object' ? vAnimRaw.transition : null;
    const vTr      = inlineTr || _t;
    const vAnim    = vAnimRaw && typeof vAnimRaw === 'object'
      ? Object.fromEntries(Object.entries(vAnimRaw).filter(([k]) => k !== 'transition'))
      : vAnimRaw;
    const initCSS  = React.useMemo(() => _fmCSS(vInit), []);
    const animKey  = JSON.stringify(vAnim);
    React.useEffect(() => {
      const el = ref.current;
      if (!el || vAnim == null) return;
      if (prevKey.current === animKey) return;
      prevKey.current = animKey;
      const css   = _fmCSS(vAnim);
      const props = Object.keys(css);
      if (!props.length) return;
      const dur  = Math.round((vTr?.duration ?? 0.35) * 1000);
      const del  = Math.round((vTr?.delay   ?? 0)    * 1000);
      const ease = _fmEase(vTr?.ease);
      el.style.transition = props.map(p => `${p} ${dur}ms ${ease} ${del}ms`).join(', ');
      const id = requestAnimationFrame(() => {
        if (!ref.current) return;
        props.forEach(p => { ref.current.style[p] = css[p]; });
      });
      return () => cancelAnimationFrame(id);
    }, [animKey]);
    // Exit animation — triggered by AnimatePresence via _isExiting prop
    React.useEffect(() => {
      const el = ref.current;
      if (!el || !_isExiting || prevExiting.current) return;
      prevExiting.current = true;
      const vExitRaw = typeof _e === 'string' && variants ? variants[_e] : _e;
      if (!vExitRaw) return;
      const exTr = (typeof vExitRaw === 'object' ? vExitRaw.transition : null) || _t;
      const vExit = typeof vExitRaw === 'object'
        ? Object.fromEntries(Object.entries(vExitRaw).filter(([k]) => k !== 'transition'))
        : vExitRaw;
      const css   = _fmCSS(vExit);
      const props = Object.keys(css);
      if (!props.length) return;
      const dur  = Math.round((exTr?.duration ?? 0.28) * 1000);
      const ease = _fmEase(exTr?.ease);
      el.style.transition = props.map(p => `${p} ${dur}ms ${ease}`).join(', ');
      requestAnimationFrame(() => {
        if (!ref.current) return;
        props.forEach(p => { ref.current.style[p] = css[p]; });
      });
    }, [_isExiting]);
    return React.createElement(Tag, { ref, style: { ...initCSS, ...style }, ...rest }, children);
  };
}

const motion = ['div','span','section','img','p','h1','h2','a','button'].reduce(
  (acc, t) => { acc[t] = _makeEl(t); return acc; }, {}
);

function AnimatePresence({ children, mode }) {
  let child;
  try { child = React.Children.only(children); } catch(e) { return React.createElement(React.Fragment, null, children); }
  if (!child) return null;
  const [current, setCurrent] = React.useState(child);
  const [isExiting, setIsExiting] = React.useState(false);
  const pendingRef = React.useRef(null);
  React.useEffect(() => {
    if (!child || child.key === current.key) return;
    pendingRef.current = child;
    setIsExiting(true);
    const t = setTimeout(() => {
      if (pendingRef.current) { setCurrent(pendingRef.current); pendingRef.current = null; }
      setIsExiting(false);
    }, 340);
    return () => clearTimeout(t);
  }, [child && child.key]);
  return React.createElement(React.Fragment, null,
    React.cloneElement(current, { _isExiting: isExiting })
  );
}

function useInView(ref, { once = true, amount = 0 } = {}) {
  const [v, setV] = React.useState(false);
  React.useEffect(() => {
    const el = ref && ref.current;
    if (!el) return;
    const obs = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setV(true); if (once) obs.disconnect(); }
    }, { threshold: amount });
    obs.observe(el);
    return () => obs.disconnect();
  }, []);
  return v;
}

function useMotionValue(init) {
  const v = React.useRef(init);
  const fns = React.useRef([]);
  return React.useMemo(() => ({
    get: () => v.current,
    set: x => { v.current = x; fns.current.forEach(f => f(x)); },
    onChange: f => { fns.current.push(f); return () => { fns.current = fns.current.filter(g => g !== f); }; },
    _mv: true,
  }), []);
}

function useSpring(src, { stiffness = 100, damping = 10, mass = 1 } = {}) {
  const init = src && src._mv ? src.get() : (typeof src === 'number' ? src : 0);
  const pos = React.useRef(init), vel = React.useRef(0), tgt = React.useRef(init);
  const fns = React.useRef([]);
  const obj = React.useMemo(() => ({
    get: () => pos.current, _mv: true,
    onChange: f => { fns.current.push(f); return () => { fns.current = fns.current.filter(g => g !== f); }; },
  }), []);
  React.useEffect(() => { if (src && src._mv) return src.onChange(v => { tgt.current = v; }); }, [src]);
  React.useEffect(() => {
    let on = true;
    const dt = 1/60;
    const tick = () => {
      if (!on) return;
      vel.current += (-stiffness*(pos.current-tgt.current)-damping*vel.current)/mass*dt;
      pos.current += vel.current*dt;
      fns.current.forEach(f => f(pos.current));
      requestAnimationFrame(tick);
    };
    requestAnimationFrame(tick);
    return () => { on = false; };
  }, [stiffness, damping, mass]);
  return obj;
}

function useScroll({ container } = {}) {
  const mv = useMotionValue(0);
  React.useEffect(() => {
    const el = container && container.current ? container.current : window;
    const fn = () => {
      if (el === window) { const t = document.documentElement.scrollHeight-window.innerHeight; mv.set(t>0?window.scrollY/t:0); }
      else { const t = el.scrollHeight-el.clientHeight; mv.set(t>0?el.scrollTop/t:0); }
    };
    el.addEventListener('scroll', fn, { passive: true });
    return () => el.removeEventListener('scroll', fn);
  }, []);
  return { scrollYProgress: mv };
}

function useTransform(mv, fn) { return typeof fn === 'function' ? fn(mv && mv._mv ? mv.get() : mv) : mv; }

function animate(from, to, { duration = 0.35, ease, onUpdate } = {}) {
  const start = performance.now(), dur = duration * 1000;
  const cb = Array.isArray(ease) ? ease : null;
  const sample = cb ? t => {
    let s = t;
    for (let i = 0; i < 6; i++) {
      const f = 3*cb[0]*s*(1-s)**2+3*cb[2]*s**2*(1-s)+s**3-t;
      const d = 3*cb[0]*(1-4*s+3*s**2)+3*cb[2]*(2*s-3*s**2)+3*s**2;
      s = Math.max(0, Math.min(1, s - f/(d||0.001)));
    }
    return 3*cb[1]*s*(1-s)**2+3*cb[3]*s**2*(1-s)+s**3;
  } : t => t;
  let id;
  const tick = now => {
    const t = Math.min(1, (now-start)/dur);
    if (onUpdate) onUpdate(from+(to-from)*sample(t));
    if (t < 1) id = requestAnimationFrame(tick);
  };
  id = requestAnimationFrame(tick);
  return { stop: () => cancelAnimationFrame(id) };
}

window.FM = { motion, AnimatePresence, useInView, useMotionValue, useSpring, useScroll, useTransform, animate };

// Shared scroll context — desktop Site provides its scrollable container ref
const SiteScrollCtx = React.createContext(null);

// ── Ph — unchanged photo container ───────────────────────────────────────────
function Ph({ project, label, ratio, src, className = "", style = {} }) {
  const url = src || (project && project.img);
  const lbl = label || (project ? `${project.cat} · ${project.loc}` : "");
  return (
    <div
      className={`ph ${className}`}
      style={{
        ...style,
        ...(ratio ? { aspectRatio: ratio } : {}),
        backgroundImage: url ? `url(${url})` : undefined,
        backgroundSize: "cover",
        backgroundPosition: "center",
      }}
    >
      {lbl ? <span className="ph__label">{lbl}</span> : null}
    </div>
  );
}

// ── RevealPh — wipe overlay + scale reveal on scroll-in ──────────────────────
function RevealPh(props) {
  const ref = React.useRef(null);
  const inView = useInView(ref, { once: true, amount: 0.12 });
  const Mdiv = motion.div || "div";
  return (
    <div ref={ref} style={{ position: "relative", overflow: "hidden" }}>
      <Mdiv
        style={{ position: "absolute", inset: 0, background: "var(--bg-warm)", zIndex: 2, transformOrigin: "right center" }}
        initial={{ scaleX: 1 }}
        animate={{ scaleX: inView ? 0 : 1 }}
        transition={{ duration: 1.05, ease: [0.77, 0, 0.175, 1], delay: 0.08 }}
      />
      <Mdiv
        initial={{ scale: 1.1 }}
        animate={{ scale: inView ? 1 : 1.1 }}
        transition={{ duration: 1.4, ease: [0.16, 1, 0.3, 1] }}
      >
        <Ph {...props} />
      </Mdiv>
    </div>
  );
}

// ── AnimCounter — counts up from 0 when element enters viewport ───────────────
function AnimCounter({ value, suffix = "" }) {
  const ref = React.useRef(null);
  const inView = useInView(ref, { once: true, amount: 0.5 });
  const [display, setDisplay] = React.useState(0);
  React.useEffect(() => {
    if (!inView) return;
    const num = parseInt(String(value).replace(/\D/g, ""), 10);
    if (isNaN(num)) return;
    const ctrl = animate(0, num, {
      duration: 2.0,
      ease: [0.16, 1, 0.3, 1],
      onUpdate: (v) => setDisplay(Math.round(v)),
    });
    return () => { if (ctrl && ctrl.stop) ctrl.stop(); };
  }, [inView]);
  return <span ref={ref}>{display}{suffix}</span>;
}

// ── Brand ─────────────────────────────────────────────────────────────────────
function Brand({ small }) {
  if (small) {
    return (
      <div className="mnav__brand">
        <div className="mark">A</div>
        <div>AAKV</div>
      </div>
    );
  }
  return (
    <div className="nav__brand">
      <div className="mark">A</div>
      <div>AAKV<small>Architecture & Énergie</small></div>
    </div>
  );
}

function Arrow() {
  return (
    <svg className="arrow" width="16" height="10" viewBox="0 0 16 10" fill="none" stroke="currentColor" strokeWidth="1">
      <path d="M0 5 H14 M9 1 L14 5 L9 9" />
    </svg>
  );
}

function SecHead({ eyebrow, title, body, dark }) {
  return (
    <div className="sec__head">
      <div>
        <div className="eyebrow" style={dark ? { color: "var(--stone)" } : null}>{eyebrow}</div>
      </div>
      <div>
        <h2 dangerouslySetInnerHTML={{ __html: title }} />
        {body ? <p className="body" style={{ marginTop: 28 }}>{body}</p> : null}
      </div>
    </div>
  );
}

function StatusBar() {
  return (
    <div className="mobile__statusbar">
      <span>9:41</span>
      <span style={{ display: "flex", gap: 6, alignItems: "center" }}>
        <svg width="16" height="10" viewBox="0 0 16 10" fill="currentColor"><rect x="0" y="6" width="3" height="4"/><rect x="4" y="4" width="3" height="6"/><rect x="8" y="2" width="3" height="8"/><rect x="12" y="0" width="3" height="10"/></svg>
        <svg width="14" height="10" viewBox="0 0 14 10" fill="none" stroke="currentColor" strokeWidth="1"><path d="M1 4 Q7 -1 13 4 M3 6 Q7 3 11 6 M5 8 Q7 7 9 8"/></svg>
        <svg width="22" height="10" viewBox="0 0 22 10" fill="none"><rect x="0.5" y="0.5" width="18" height="9" rx="2" stroke="currentColor"/><rect x="2" y="2" width="14" height="6" rx="1" fill="currentColor"/><rect x="19.5" y="3.5" width="1.5" height="3" rx="0.5" fill="currentColor"/></svg>
      </span>
    </div>
  );
}

Object.assign(window, { Ph, RevealPh, AnimCounter, Brand, Arrow, SecHead, StatusBar, SiteScrollCtx });
