// Shared primitives — typography, lockup, corner stamp, fields, CTAs

// Watermark — SW Training Day patch behind page content. Drop this in as the
// FIRST child of a page wrapper that has position: relative; the parent's
// stacking context contains the blend so the watermark only mixes with that
// page's background, not the entire document. Admin pages skip it.
//
// Two positioning modes:
//   default                    fixed-to-viewport, follows scroll
//   anchor="top"               absolute, anchored to top of the page wrapper —
//                              scrolls away as the user reads further down
//
// `style` overrides any of the above for one-off tuning.
const Watermark = ({
  size = 720,
  opacity = 0.10,
  anchor = 'viewport',
  height,
  style = {},
}) => {
  const positioning = anchor === 'top'
    ? { position: 'absolute', top: 0, left: 0, right: 0, height: height || '1400px' }
    : { position: 'fixed', inset: 0 };
  return (
    <div
      aria-hidden="true"
      style={{
        ...positioning,
        pointerEvents: 'none',
        backgroundImage: 'url("media/sw-training-day-logo.png")',
        backgroundSize: `min(80vw, ${size}px) auto`,
        backgroundRepeat: 'no-repeat',
        backgroundPosition: 'center',
        opacity,
        mixBlendMode: 'lighten',     // patch's black bg → ignored against dark page bg
        zIndex: 0,
        ...style,
      }}
    />
  );
};

const Bebas = ({ size = 16, color = T.bone, style = {}, children, as: As = 'span' }) => (
  <As style={{ fontFamily: "'Bebas Neue',sans-serif", fontSize: size, letterSpacing: 0.5, color, lineHeight: 1, display: 'inline-block', ...style }}>{children}</As>
);
const Mono = ({ size = 11, color = T.mute, style = {}, children }) => (
  <span style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: size, color, letterSpacing: 0.5, lineHeight: 1.4, display: 'inline-block', ...style }}>{children}</span>
);
const Body = ({ size = 14, color = T.bone, style = {}, children }) => (
  <span style={{ fontFamily: "'Inter',sans-serif", fontSize: size, color, lineHeight: 1.55, display: 'inline-block', ...style }}>{children}</span>
);

const SWLockup = ({ size = 1, color = T.bone }) => (
  <div style={{ display: 'flex', alignItems: 'center', gap: 8 * size }}>
    <svg width={20 * size} height={20 * size} viewBox="0 0 24 24" fill="none">
      <path d="M12 2 L22 12 L12 22 L2 12 Z" stroke={color} strokeWidth="1.5" />
      <path d="M12 7 L17 12 L12 17 L7 12 Z" fill={color} />
    </svg>
    <div style={{ display: 'flex', flexDirection: 'column', lineHeight: 0.95 }}>
      <span style={{ fontFamily: "'Bebas Neue',sans-serif", fontSize: 14 * size, color, letterSpacing: 1.2 }}>SW TRAINING</span>
      <span style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: 8 * size, color: T.mute, letterSpacing: 1.5 }}>DAY · EVENT SERIES</span>
    </div>
  </div>
);

const CornerStamp = ({ time, place, style = {} }) => (
  <div style={{ position: 'absolute', top: 14, right: 14, fontFamily: "'JetBrains Mono',monospace", fontSize: 9, color: T.gold, letterSpacing: 1.2, textAlign: 'right', ...style }}>
    <div>{time}</div>
    <div style={{ color: T.mute, marginTop: 2 }}>{place}</div>
  </div>
);

const Field = ({ label, value, onChange, ph = '', type = 'text', error, style = {} }) => (
  <div style={{ display: 'flex', flexDirection: 'column', gap: 5, ...style }}>
    <span style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: 9, color: error ? T.red : T.mute, letterSpacing: 1 }}>{label}{error && ' · ' + error}</span>
    <input
      type={type}
      value={value || ''}
      placeholder={ph}
      onChange={(e) => onChange?.(e.target.value)}
      style={{
        background: 'transparent',
        border: `1px solid ${error ? T.red : T.rule}`,
        color: T.bone,
        padding: '10px 12px',
        fontSize: 14,
        fontFamily: "'Inter',sans-serif",
        outline: 'none',
        transition: 'border-color .15s',
      }}
      onFocus={(e) => (e.target.style.borderColor = T.gold)}
      onBlur={(e) => (e.target.style.borderColor = error ? T.red : T.rule)}
    />
  </div>
);

const PrimaryCTA = ({ children, onClick, blue, disabled, style = {} }) => (
  <button
    onClick={disabled ? undefined : onClick}
    disabled={disabled}
    style={{
      background: disabled ? T.rule : (blue ? T.blue2 : T.gold),
      color: disabled ? T.mute : (blue ? T.bone : T.bg),
      border: 'none',
      padding: '12px 22px',
      fontFamily: "'Bebas Neue',sans-serif",
      fontSize: 16,
      letterSpacing: 1.2,
      cursor: disabled ? 'not-allowed' : 'pointer',
      transition: 'transform .1s, filter .15s',
      ...style,
    }}
    onMouseEnter={(e) => !disabled && (e.currentTarget.style.filter = 'brightness(1.1)')}
    onMouseLeave={(e) => (e.currentTarget.style.filter = 'none')}
  >{children}</button>
);

const GhostCTA = ({ children, onClick, disabled, title, style = {} }) => (
  <button
    onClick={disabled ? undefined : onClick}
    disabled={disabled}
    title={title}
    style={{
      background: 'transparent',
      color: disabled ? T.mute : T.bone,
      border: `1px solid ${disabled ? T.rule : T.bone}`,
      padding: '12px 22px',
      fontFamily: "'Bebas Neue',sans-serif",
      fontSize: 16,
      letterSpacing: 1.2,
      cursor: disabled ? 'not-allowed' : 'pointer',
      transition: 'background .15s',
      ...style,
    }}
    onMouseEnter={(e) => !disabled && (e.currentTarget.style.background = 'rgba(245,241,230,0.08)')}
    onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}
  >{children}</button>
);

// Placeholder media slot — gradient terrain feel until user supplies media.
// Pass `src` to render an actual image or video; it scales with object-fit: cover
// so the same TerrainSlot works at any width (desktop, phone, etc.).
const TerrainSlot = ({ h = 240, label, src, poster, fade, objectPosition = 'center', mediaType, children, style = {} }) => {
  // Detect video either from an explicit mediaType ("video/*") or by extension.
  // Blob URLs don't have an extension, so consumers serving Blob video must
  // pass mediaType explicitly.
  const isVideo = src && (
    (mediaType && /^video\//.test(mediaType)) ||
    /\.(mp4|webm|mov)(\?.*)?$/i.test(src)
  );
  const fadeMask = fade
    ? { WebkitMaskImage: 'linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.15) 25%, rgba(0,0,0,0.6) 55%, rgba(0,0,0,1) 80%)',
        maskImage:       'linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.15) 25%, rgba(0,0,0,0.6) 55%, rgba(0,0,0,1) 80%)' }
    : null;

  // Keep the hero video playing across the events that mobile (and SPAs in
  // general) expose: initial mount, SPA route remount, tab/app switch
  // (visibilitychange), back/forward bfcache restore (pageshow), and window
  // refocus. iOS Safari is the most aggressive — it pauses video the moment
  // the page goes to the background and won't resume on its own; without
  // this effect the user comes back to a frozen first frame.
  const videoRef = React.useRef(null);
  React.useEffect(() => {
    if (!isVideo || !videoRef.current) return;
    const v = videoRef.current;
    const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (reduced) return;

    const tryPlay = () => {
      // Bail if the page is hidden — calling play() on a hidden video can
      // throw on some browsers and is meaningless even when it doesn't.
      if (document.hidden) return;
      // After a deep background cycle (e.g. iOS Safari → switch to another
      // app → return), the <video> element can be left in a suspended
      // state where play() silently rejects. Calling load() resets it back
      // to a known-good "haven't started" state; then play() works as if
      // it were the initial mount. This is a no-op cost on platforms that
      // didn't suspend us.
      if (v.paused || v.ended || v.readyState < 2) {
        try { v.load(); } catch (_) { /* iOS sometimes throws here too — fine */ }
      }
      const p = v.play();
      if (p && typeof p.catch === 'function') {
        p.catch(() => { /* autoplay still blocked — poster-frame fallback */ });
      }
    };

    tryPlay();
    document.addEventListener('visibilitychange', tryPlay);
    window.addEventListener('pageshow', tryPlay);
    window.addEventListener('focus', tryPlay);
    return () => {
      document.removeEventListener('visibilitychange', tryPlay);
      window.removeEventListener('pageshow', tryPlay);
      window.removeEventListener('focus', tryPlay);
    };
  }, [src, isVideo]);

  return (
    <div style={{
      height: h,
      width: '100%',
      background: `linear-gradient(135deg, #1a2030 0%, #0e0e10 50%, #2a1f15 100%)`,
      position: 'relative',
      overflow: 'hidden',
      ...style,
    }}>
      {/* Real media (if provided) — covers the slot, scales for any container width.
          Respect prefers-reduced-motion: don't autoplay; show first frame. */}
      {src && isVideo && (
        <video
          ref={videoRef}
          key={src}
          src={src}
          poster={poster}
          autoPlay
          muted loop playsInline preload="auto"
          onError={(e) => console.warn('hero video failed to load:', src, e?.target?.error?.message)}
          style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition, ...fadeMask }}
        />
      )}
      {src && !isVideo && (
        <img src={src} alt=""
          style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition, ...fadeMask }}
        />
      )}

      {/* Stylized topo lines — hidden when real media is supplied */}
      {!src && (
        <svg width="100%" height="100%" viewBox="0 0 400 240" preserveAspectRatio="none" style={{ position: 'absolute', inset: 0, opacity: 0.25 }}>
          <path d="M0,180 Q100,120 200,150 T400,140" stroke={T.gold} strokeWidth="0.5" fill="none" />
          <path d="M0,200 Q100,150 200,170 T400,160" stroke={T.gold} strokeWidth="0.5" fill="none" />
          <path d="M0,160 Q100,90 200,130 T400,120" stroke={T.bone} strokeWidth="0.3" fill="none" />
          <path d="M0,140 Q100,70 200,110 T400,100" stroke={T.bone} strokeWidth="0.3" fill="none" />
        </svg>
      )}
      {label && (
        <div style={{ position: 'absolute', bottom: 10, left: 12, fontFamily: "'JetBrains Mono',monospace", fontSize: 9, color: T.mute, letterSpacing: 1 }}>
          {label}
        </div>
      )}
      {children}
    </div>
  );
};

// Slideshow — auto-rotates through an array of image URLs with crossfade.
// Drop in any aspect ratio container; image uses object-fit: contain so the
// full image is always visible (no cropping). Background fills letterbox.
const Slideshow = ({ images = [], interval = 5000, fit = 'contain', aspect = '4 / 3', style = {} }) => {
  const list = images.filter(Boolean);
  const [idx, setIdx] = React.useState(0);
  React.useEffect(() => {
    if (list.length < 2) return;
    const id = setInterval(() => setIdx(i => (i + 1) % list.length), interval);
    return () => clearInterval(id);
  }, [list.length, interval]);

  const isEmpty = list.length === 0;

  return (
    <div style={{
      position: 'relative', width: '100%', aspectRatio: aspect,
      border: `1px solid ${T.rule}`, background: T.bg2, overflow: 'hidden', ...style,
    }}>
      {isEmpty && (
        <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 8 }}>
          <Mono size={10} color={T.mute}>// SLIDESHOW</Mono>
          <Mono size={9} color={T.mute}>NO IMAGES</Mono>
        </div>
      )}
      {list.map((src, i) => (
        <img
          key={src + i}
          src={src} alt=""
          style={{
            position: 'absolute', inset: 0,
            width: '100%', height: '100%',
            objectFit: fit, objectPosition: 'center',
            opacity: i === idx ? 1 : 0,
            transition: 'opacity .8s ease-in-out',
            background: T.bone, // letterbox pad — matches the white logo bg
          }}
        />
      ))}
      {list.length > 1 && (
        <div style={{ position: 'absolute', bottom: 10, left: 0, right: 0, display: 'flex', gap: 6, justifyContent: 'center' }}>
          {list.map((_, i) => (
            <button
              key={i}
              onClick={() => setIdx(i)}
              aria-label={`Slide ${i + 1}`}
              style={{
                width: 22, height: 3, padding: 0, border: 'none', cursor: 'pointer',
                background: i === idx ? T.gold : 'rgba(245,241,230,0.35)',
                transition: 'background .2s',
              }}
            />
          ))}
        </div>
      )}
    </div>
  );
};

// ── Format helpers ───────────────────────────────────────────────────
// Auto-format a US phone number as the user types: (XXX) XXX-XXXX.
// Idempotent — re-formatting an already formatted string yields the same.
const formatPhone = (raw) => {
  const d = (raw || '').replace(/\D/g, '').slice(0, 10);
  if (d.length === 0) return '';
  if (d.length <= 3)  return d;
  if (d.length <= 6)  return `(${d.slice(0, 3)}) ${d.slice(3)}`;
  return `(${d.slice(0, 3)}) ${d.slice(3, 6)}-${d.slice(6)}`;
};

// Auto-format a date as the user types: MM/DD/YYYY.
const formatDate = (raw) => {
  const d = (raw || '').replace(/\D/g, '').slice(0, 8);
  if (d.length === 0) return '';
  if (d.length <= 2)  return d;
  if (d.length <= 4)  return `${d.slice(0, 2)}/${d.slice(2)}`;
  return `${d.slice(0, 2)}/${d.slice(2, 4)}/${d.slice(4)}`;
};

Object.assign(window, { Bebas, Mono, Body, SWLockup, CornerStamp, Field, PrimaryCTA, GhostCTA, TerrainSlot, Slideshow, formatPhone, formatDate });
