/* global React */
/* Shared design tokens and tiny helpers used across the site. */

/* Cache-buster for images.
   Photos keep their filenames when one is swapped out, and nginx serves images
   with max-age=30d (DEPLOY.md), so a replaced photo would otherwise stay
   invisible to anyone who had already loaded the old one — Cloudflare included.
   The token is read back off the stylesheet link that scripts/bump-cache.sh
   already stamps, so there is only ever one thing to bump. */
const ASSET_V = (function () {
  const link = document.querySelector('link[rel="stylesheet"][href*="site.css"]');
  const m = link && link.getAttribute('href').match(/[?&]v=([^&"]+)/);
  return m ? m[1] : null;
})();

/** Append the site's cache-buster to a local asset path. */
function asset(path) {
  if (!path || !ASSET_V) return path;
  if (/^(https?:)?\/\//.test(path) || path.indexOf('data:') === 0) return path;
  return path + (path.indexOf('?') === -1 ? '?' : '&') + 'v=' + ASSET_V;
}

/** Photo frame. Real image when `src` given, brand-tinted stand-in otherwise.
 *  Pass `aspect={null}` to leave sizing entirely to CSS — inline styles beat
 *  media queries, so any frame that must reshape per breakpoint needs this. */
function Photo({ tone = 'warm', aspect = '4/5', children, style, className, caption, src, position }) {
  // A photo that 404s (renamed, not yet dropped in) shows the tinted stand-in
  // rather than an empty framed box.
  const [failed, setFailed] = React.useState(false);
  React.useEffect(() => { setFailed(false); }, [src]);
  const showImg = !!src && !failed;

  const frame = { boxShadow: 'var(--shadow-photo)', position: 'relative', overflow: 'hidden' };
  if (aspect) frame.aspectRatio = aspect;
  return (
    <div
      className={`kit-photo ${showImg ? 'kit-photo--has-img' : `kit-photo--${tone}`} ${className || ''}`}
      style={{ ...frame, ...style }}
    >
      {showImg ? (
        <img
          src={asset(src)}
          alt={caption || ''}
          className="kit-photo__img"
          style={{ objectPosition: position || '50% 35%' }}
          onError={() => setFailed(true)}
        />
      ) : null}
      {caption ? <span className="kit-photo__cap">{caption}</span> : null}
      {children}
    </div>
  );
}

/** Eyebrow with side rules. */
function Eyebrow({ children, center, color }) {
  return (
    <span className="kit-eyebrow" style={{ color, justifyContent: center ? 'center' : 'flex-start' }}>
      {children}
    </span>
  );
}

/** Small italic section indicator: N° 01 */
function SectionNum({ n, color = 'var(--clay)' }) {
  return (
    <span style={{ fontFamily: 'var(--font-display)', fontStyle: 'italic', fontWeight: 300, fontSize: 18, color }}>
      N° {n}
    </span>
  );
}

/** Hairline rule (clay, 56×1) */
function Rule({ center, style }) {
  return <hr className={`kit-rule ${center ? 'kit-rule--center' : ''}`} style={style} />;
}

/** True on phone-width screens, and stays correct across a rotate/resize. */
function useIsPhone() {
  const QUERY = '(max-width: 640px)';
  const [isPhone, setIsPhone] = React.useState(
    () => typeof window !== 'undefined' && window.matchMedia(QUERY).matches
  );
  React.useEffect(() => {
    const mq = window.matchMedia(QUERY);
    const onChange = () => setIsPhone(mq.matches);
    mq.addEventListener('change', onChange);
    return () => mq.removeEventListener('change', onChange);
  }, []);
  return isPhone;
}

/** A row that is plain text on a desktop and tap-to-expand on a phone.
 *  Lets long explanatory lists — the promises, the experience timeline — read
 *  as a scannable index on a small screen instead of running for two or three
 *  screenfuls, without changing anything above 640px. */
function Disclosure({ title, children, collapsible, className = '', lead }) {
  const [open, setOpen] = React.useState(false);
  if (!collapsible) {
    return (
      <div className={className}>
        {title}
        {children}
      </div>
    );
  }
  return (
    <div
      className={`${className} m-collapse ${open ? 'is-open' : ''}`}
      onClick={() => setOpen(o => !o)}
    >
      <div className="m-collapse__head">
        {lead ? <span className="m-collapse__lead">{lead}</span> : null}
        {title}
        <span className="m-collapse__toggle" aria-hidden="true">+</span>
      </div>
      <div className="m-collapse__body"><div>{children}</div></div>
    </div>
  );
}

/** "Slow & quiet" page transition wrapper */
function PageFade({ children, k }) {
  return <div key={k} className="page-fade">{children}</div>;
}

Object.assign(window, {
  Photo, Eyebrow, SectionNum, Rule, PageFade, asset, useIsPhone, Disclosure,
});
