/* global React */
const { useState, useEffect, useRef } = React;

/* =====================================================
   VideoBackground — image-first, video lazy + a11y aware
   The "video" here is a CSS-animated simulator that
   stands in for client-supplied footage during prototype.
   In production, swap the .video-sim div for a <video>
   element + poster image; the lazy/reduced-motion logic
   is already wired.
   ===================================================== */
function VideoBackground({ kind = "flag", image, video, poster, overlay = true, position = "center", playbackRate = 1, children }) {
  const [reduced, setReduced] = useState(false);
  const [videoReady, setVideoReady] = useState(false);
  const videoRef = useRef(null);

  useEffect(() => {
    const m = window.matchMedia("(prefers-reduced-motion: reduce)");
    const update = () => setReduced(m.matches);
    update();
    m.addEventListener?.("change", update);
    return () => m.removeEventListener?.("change", update);
  }, []);

  // Lazy-load the video only after the static layer is in place.
  // Skip entirely on reduced motion.
  useEffect(() => {
    if (!video || reduced) return;
    const el = videoRef.current;
    if (!el) return;
    const onCanPlay = () => {
      try { el.playbackRate = playbackRate; } catch (_) {}
      el.play().then(() => setVideoReady(true)).catch(() => {
        // autoplay blocked — keep the static fallback
        setVideoReady(false);
      });
    };
    el.addEventListener("canplay", onCanPlay, { once: true });
    // Trigger load after a frame so the poster paints first
    const t = setTimeout(() => {el.load();}, 50);
    return () => {
      clearTimeout(t);
      el.removeEventListener("canplay", onCanPlay);
    };
  }, [video, reduced]);

  const simClass = `v-sim-${kind}`;
  const staticImage = poster || image;
  return (
    <div className={`video-bg ${videoReady ? "video-ready" : ""}`}>
      {staticImage &&
      <div
        className="v-image"
        aria-hidden="true"
        style={{
          backgroundImage: `url(${staticImage})`,
          backgroundPosition: position
        }}>
      </div>
      }
      <div
        className={`v-fallback ${simClass} ${reduced ? "reduced" : ""} ${staticImage ? "behind-image" : ""}`}
        aria-hidden="true">
      </div>
      {video && !reduced &&
      <video
        ref={videoRef}
        muted
        loop
        playsInline
        preload="none"
        poster={poster}
        aria-hidden="true">
        
          <source src={video} type="video/mp4" />
        </video>
      }
      {overlay && <div className="v-overlay" aria-hidden="true" style={{ opacity: "1" }}></div>}
      {children}
    </div>);

}

/* ====== Inline icons ====== */
const Icon = {
  Star: ({ size = 14, ...p }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" {...p}>
      <path d="M12 2l2.9 6.6 7.1.6-5.4 4.7 1.7 7-6.3-3.8L5.7 21l1.7-7L2 9.2l7.1-.6L12 2z" />
    </svg>,

  Arrow: ({ size = 16, ...p }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <line x1="5" y1="12" x2="19" y2="12" /><polyline points="13 6 19 12 13 18" />
    </svg>,

  External: ({ size = 14, ...p }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" /><polyline points="15 3 21 3 21 9" /><line x1="10" y1="14" x2="21" y2="3" />
    </svg>,

  Mail: ({ size = 16, ...p }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" /><polyline points="22,6 12,13 2,6" />
    </svg>,

  Phone: ({ size = 16, ...p }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.37 1.9.72 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.35 1.85.59 2.81.72A2 2 0 0 1 22 16.92z" />
    </svg>,

  Pin: ({ size = 16, ...p }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" /><circle cx="12" cy="10" r="3" />
    </svg>,

  Clock: ({ size = 16, ...p }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" />
    </svg>,

  Facebook: ({ size = 18, ...p }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" {...p}>
      <path d="M22 12a10 10 0 10-11.6 9.9V14.9H7.9V12h2.5V9.8c0-2.5 1.5-3.9 3.8-3.9 1.1 0 2.2.2 2.2.2v2.5h-1.3c-1.2 0-1.6.8-1.6 1.6V12h2.7l-.4 2.9h-2.3v7c4.7-.7 8.5-4.8 8.5-9.9z" />
    </svg>,

  Instagram: ({ size = 18, ...p }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" {...p}>
      <rect x="3" y="3" width="18" height="18" rx="5" /><circle cx="12" cy="12" r="4" /><circle cx="17.5" cy="6.5" r="1" fill="currentColor" />
    </svg>,

  WhatsApp: ({ size = 16, ...p }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" {...p}>
      <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51l-.57-.01c-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.71.306 1.263.489 1.694.625.712.227 1.36.195 1.872.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347M12.05 21.785h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.002-5.45 4.437-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z" />
    </svg>,

  Menu: ({ size = 22, ...p }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" {...p}>
      <line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="18" x2="21" y2="18" />
    </svg>,

  Close: ({ size = 22, ...p }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" {...p}>
      <line x1="6" y1="6" x2="18" y2="18" /><line x1="6" y1="18" x2="18" y2="6" />
    </svg>,

  Check: ({ size = 16, ...p }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <polyline points="4 12 10 18 20 6" />
    </svg>

};

/* ====== Logo (donkey mark + wordmark) ====== */
function Logo({ light = false, compact = false }) {
  const fg = light ? "#ffffff" : "var(--navy-900)";
  const accent = "var(--red)";
  return (
    <div className="logo" style={{ display: "flex", alignItems: "center", gap: 12 }}>
      <div className="logo-mark" aria-hidden="true" style={{
        width: 42, height: 42, borderRadius: 4,
        background: light ? "rgba(255,255,255,0.08)" : "var(--navy-900)",
        border: light ? "1.5px solid rgba(255,255,255,0.4)" : "1.5px solid var(--navy-900)",
        display: "grid", placeItems: "center",
        position: "relative", flex: "0 0 42px"
      }}>
        {/* Simple dignified star + initials mark, placeholder for client logo */}
        <svg width="22" height="22" viewBox="0 0 24 24" fill={light ? "#ffffff" : "#ffffff"}>
          <path d="M12 2l2.9 6.6 7.1.6-5.4 4.7 1.7 7-6.3-3.8L5.7 21l1.7-7L2 9.2l7.1-.6L12 2z" />
        </svg>
        <span style={{
          position: "absolute", bottom: -6, right: -6,
          width: 14, height: 14, borderRadius: 2,
          background: accent, border: `1.5px solid ${light ? "var(--navy-900)" : "var(--off-white)"}`
        }}></span>
      </div>
      {!compact &&
      <div className="logo-text" style={{ display: "flex", flexDirection: "column", lineHeight: 1 }}>
          <span style={{
          fontFamily: "var(--font-display)",
          fontWeight: 800,
          fontSize: 15,
          letterSpacing: "-0.005em",
          color: fg
        }}>DCSMV</span>
          <span style={{
          fontFamily: "var(--font-body)",
          fontWeight: 500,
          fontSize: 10,
          letterSpacing: "0.14em",
          textTransform: "uppercase",
          color: light ? "rgba(255,255,255,0.65)" : "var(--ink-500)",
          marginTop: 4
        }}>Santa Maria Valley</span>
        </div>
      }
    </div>);

}

/* ====== Star divider ====== */
function StarDivider({ count = 3, color }) {
  return (
    <div className="star-divider" style={color ? { color } : undefined} aria-hidden="true">
      <span className="rule"></span>
      {Array.from({ length: count }).map((_, i) =>
      <Icon.Star key={i} size={i === Math.floor(count / 2) ? 14 : 10} />
      )}
      <span className="rule"></span>
    </div>);

}

/* ====== Eyebrow heading helper ====== */
function Eyebrow({ children }) {
  return <div className="eyebrow">{children}</div>;
}

/* =====================================================
   SparkleHeading — silver shimmer + canvas sparkles
   Uses the stacked-text approach:
     - back layer renders normally and keeps the existing
       text-shadow (white halo + dark lift) for legibility,
     - front layer overlays it with a silver gradient
       clipped to the glyphs and animates the shimmer,
     - a canvas sibling spawns small 4-point sparkles.
   ===================================================== */
function SparkleHeading({ id, className = "", children }) {
  const wrapRef = useRef(null);
  const canvasRef = useRef(null);

  useEffect(() => {
    const wrap = wrapRef.current;
    const canvas = canvasRef.current;
    if (!wrap || !canvas) return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;

    const ctx = canvas.getContext("2d");
    const dpr = window.devicePixelRatio || 1;
    let raf;

    function resize() {
      const r = wrap.getBoundingClientRect();
      canvas.width = Math.max(1, r.width * dpr);
      canvas.height = Math.max(1, r.height * dpr);
      canvas.style.width = r.width + "px";
      canvas.style.height = r.height + "px";
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.scale(dpr, dpr);
    }
    resize();
    window.addEventListener("resize", resize);

    const sparkles = [];
    const MAX = 22;

    function spawn() {
      const r = wrap.getBoundingClientRect();
      sparkles.push({
        x: Math.random() * r.width,
        y: Math.random() * r.height,
        size: 2 + Math.random() * 4,
        life: 0,
        maxLife: 50 + Math.random() * 60,
        rotation: Math.random() * Math.PI });

    }

    function drawStar(s) {
      const t = s.life / s.maxLife;
      const opacity = t < 0.5 ? t * 2 : (1 - t) * 2;
      const scale = Math.sin(t * Math.PI);
      ctx.save();
      ctx.translate(s.x, s.y);
      ctx.rotate(s.rotation);
      ctx.scale(scale, scale);
      ctx.globalAlpha = opacity;
      ctx.fillStyle = "#fff";
      ctx.shadowColor = "#fff";
      ctx.shadowBlur = 8;
      ctx.beginPath();
      const len = s.size * 2;
      ctx.moveTo(0, -len);
      ctx.lineTo(s.size * 0.4, -s.size * 0.4);
      ctx.lineTo(len, 0);
      ctx.lineTo(s.size * 0.4, s.size * 0.4);
      ctx.lineTo(0, len);
      ctx.lineTo(-s.size * 0.4, s.size * 0.4);
      ctx.lineTo(-len, 0);
      ctx.lineTo(-s.size * 0.4, -s.size * 0.4);
      ctx.closePath();
      ctx.fill();
      ctx.restore();
    }

    function loop() {
      const r = wrap.getBoundingClientRect();
      ctx.clearRect(0, 0, r.width, r.height);
      // Allow up to a couple of spawns per frame so the field fills out
      // faster and sparkles keep coming even at the higher cap.
      if (sparkles.length < MAX && Math.random() < 0.22) spawn();
      if (sparkles.length < MAX && Math.random() < 0.10) spawn();
      for (let i = sparkles.length - 1; i >= 0; i--) {
        sparkles[i].life++;
        if (sparkles[i].life >= sparkles[i].maxLife) {
          sparkles.splice(i, 1);
          continue;
        }
        drawStar(sparkles[i]);
      }
      raf = requestAnimationFrame(loop);
    }
    raf = requestAnimationFrame(loop);

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("resize", resize);
    };
  }, []);

  return (
    <div className="hero-sparkle-wrap" ref={wrapRef}>
      <h1 id={id} className={`hero-sparkle-heading ${className}`}>
        <span className="hero-sparkle-back">{children}</span>
        <span className="hero-sparkle-front" aria-hidden="true">{children}</span>
      </h1>
      <canvas className="sparkle-canvas" aria-hidden="true" ref={canvasRef}></canvas>
    </div>);

}

/* ====== Portrait — star medallion (shared by Reps + Endorsements) ======
   A `photo` renders the real circular portrait inside the red star;
   without one it falls back to the SVG silhouette + initials. An
   optional `photoPos` ("<x%> <y%>") overrides the default
   object-position when a face sits off-center in its source. */
function Portrait({ initials = "—", photo, photoPos, name }) {
  return (
    <div className="portrait-wrap">
      <svg className="portrait-star" viewBox="0 0 100 100" aria-hidden="true">
        <polygon
          fill="var(--accent-main)"
          points="50,2 61.8,35.6 97,35.6 68.6,57.6 80.4,91.2 50,69.4 19.6,91.2 31.4,57.6 3,35.6 38.2,35.6"
        />
      </svg>
      <div className="portrait" aria-hidden={photo ? undefined : "true"}>
        {photo ? (
          <img
            className="portrait-photo"
            src={photo}
            alt={name ? `Portrait of ${name}` : ""}
            loading="lazy"
            style={photoPos ? { objectPosition: photoPos } : undefined}
          />
        ) : (
          <>
            <div className="portrait-fill">
              <svg viewBox="0 0 100 100" width="100%" height="100%" style={{ display: "block" }}>
                <defs>
                  <pattern id="pp-stripes" patternUnits="userSpaceOnUse" width="8" height="8" patternTransform="rotate(135)">
                    <rect width="8" height="8" fill="#f4f2eb"/>
                    <rect width="3" height="8" fill="#e1e8f2"/>
                  </pattern>
                </defs>
                <circle cx="50" cy="50" r="50" fill="url(#pp-stripes)"/>
                <circle cx="50" cy="40" r="15" fill="#b8c8e0"/>
                <path d="M20,90 Q50,60 80,90 L80,100 L20,100 Z" fill="#b8c8e0"/>
              </svg>
            </div>
            <span className="portrait-initials">{initials}</span>
          </>
        )}
      </div>
    </div>
  );
}

/* ====== Mini CTA marquee (2 rows + button) ====== */
function MiniCtaMarquee({ lang }) {
  const t = (en, es) => (lang === "es" ? es : en);
  return (
    <section className="closing-cta closing-flat closing-mini" aria-labelledby="mini-closing-title">
      <div className="cta-marquee cta-marquee-mini" aria-hidden="true">
        <div className="cta-row cta-row-1">
          <div className="cta-track">
            {Array.from({ length: 2 }).map((_, k) =>
              <span key={k} className="cta-track-inner">
                <span>Your Vote Is Your Voice</span><span className="cta-star">★</span>
                <span>Plan To Vote</span><span className="cta-star">★</span>
                <span>The Future Of America Is With Your Vote</span><span className="cta-star">★</span>
                <span>Vote Like Your Life Depends On It</span><span className="cta-star">★</span>
              </span>
            )}
          </div>
        </div>
        <div className="cta-row cta-row-3">
          <div className="cta-track reverse">
            {Array.from({ length: 2 }).map((_, k) =>
              <span key={k} className="cta-track-inner">
                <span>Real Friends Make Real Friends Vote</span><span className="cta-star">★</span>
                <span>Show Up · Speak Up</span><span className="cta-star">★</span>
                <span>Your Vote Will Make You Proud</span><span className="cta-star">★</span>
                <span>Vote Early By Mail</span><span className="cta-star">★</span>
              </span>
            )}
          </div>
        </div>
      </div>
      <div className="container closing-inner on-dark">
        <h2 id="mini-closing-title" className="sr-only">{t("Plan to vote.", "Planea votar.")}</h2>
        <a className="btn btn-primary btn-lg cta-pulse-btn" href="https://registertovote.ca.gov/" target="_blank" rel="noopener noreferrer">
          {t("Register to Vote", "Regístrate")} <Icon.Arrow className="arrow" />
        </a>
      </div>
    </section>);

}

/* =====================================================
   HeroFitText — keep a single-line label on one line by
   shrinking its font-size to fit the parent's width.
   Used for hero subtitle red bands and any other
   "must-fit-on-one-line" hero copy.
   ===================================================== */
function HeroFitText({ outerClassName = "", innerClassName = "", minFontPx = 8, children }) {
  const outerRef = useRef(null);
  const innerRef = useRef(null);

  useEffect(() => {
    const outer = outerRef.current;
    const inner = innerRef.current;
    if (!outer || !inner) return;

    const fit = () => {
      // Reset to the CSS-defined size before measuring so we re-fit
      // whenever the viewport (or container) grows back up.
      inner.style.fontSize = "";
      // Loop: if the inner overflows the outer's content width, scale
      // the font down proportionally and re-measure. The padding on
      // .hero-sub-banner-inner is in px so a couple of iterations is
      // enough; cap attempts to keep this bounded.
      for (let i = 0; i < 12; i++) {
        const innerW = inner.offsetWidth;
        const outerW = outer.clientWidth;
        if (innerW <= outerW - 1) break;
        const current = parseFloat(window.getComputedStyle(inner).fontSize) || 14;
        const ratio = (outerW - 1) / innerW;
        const next = Math.max(minFontPx, current * ratio * 0.985);
        if (next >= current - 0.05) break;
        inner.style.fontSize = next.toFixed(2) + "px";
      }
    };

    fit();
    const ro = new ResizeObserver(fit);
    ro.observe(outer);
    window.addEventListener("resize", fit);
    return () => {
      ro.disconnect();
      window.removeEventListener("resize", fit);
    };
  }, [children]);

  return (
    <p ref={outerRef} className={outerClassName}>
      <span ref={innerRef} className={innerClassName}>{children}</span>
    </p>);

}

/* =====================================================
   Lightbox — modal image viewer
   Caller passes the full image list and the current index;
   the component handles open/close, keyboard, prev/next.
   ===================================================== */
function Lightbox({ images, index, onClose, onIndex }) {
  const open = index != null && index >= 0;

  useEffect(() => {
    if (!open) return;
    const onKey = (e) => {
      if (e.key === "Escape") onClose();
      else if (e.key === "ArrowRight") onIndex((index + 1) % images.length);
      else if (e.key === "ArrowLeft") onIndex((index - 1 + images.length) % images.length);
    };
    window.addEventListener("keydown", onKey);
    // Lock body scroll while the lightbox is open.
    const prevOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => {
      window.removeEventListener("keydown", onKey);
      document.body.style.overflow = prevOverflow;
    };
  }, [open, index, images, onClose, onIndex]);

  if (!open) return null;
  const current = images[index];
  const prev = () => onIndex((index - 1 + images.length) % images.length);
  const next = () => onIndex((index + 1) % images.length);

  return (
    <div
      className="lightbox"
      role="dialog"
      aria-modal="true"
      aria-label={current.alt || "Image viewer"}
      onClick={onClose}>

      <button
        className="lightbox-close"
        type="button"
        aria-label="Close"
        onClick={(e) => {e.stopPropagation();onClose();}}>

        <Icon.Close size={22} />
      </button>
      <button
        className="lightbox-nav lightbox-prev"
        type="button"
        aria-label="Previous image"
        onClick={(e) => {e.stopPropagation();prev();}}>

        <Icon.Arrow size={22} style={{ transform: "rotate(180deg)" }} />
      </button>
      <figure className="lightbox-figure" onClick={(e) => e.stopPropagation()}>
        <img src={current.src} alt={current.alt || ""} />
        {current.alt && <figcaption className="lightbox-caption">{current.alt}</figcaption>}
      </figure>
      <button
        className="lightbox-nav lightbox-next"
        type="button"
        aria-label="Next image"
        onClick={(e) => {e.stopPropagation();next();}}>

        <Icon.Arrow size={22} />
      </button>
      <div className="lightbox-counter" aria-hidden="true">{index + 1} / {images.length}</div>
    </div>);

}

/* expose */
Object.assign(window, { VideoBackground, Icon, Logo, StarDivider, Eyebrow, MiniCtaMarquee, SparkleHeading, Lightbox, HeroFitText, Portrait });