/* IJWestern — shared SVG bits & atoms */
/* Globals: TopoLines, ArrowIcon, StatusPill, PlotOutline, MethodIcons, LandPlaceholder */

const ArrowIcon = ({ size = 11 }) => (
  <svg width={size} height={size} viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.4">
    <path d="M2 10 L10 2 M5 2 L10 2 L10 7" strokeLinecap="square" />
  </svg>
);

const PlusIcon = ({ size = 14 }) => (
  <svg width={size} height={size} viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1">
    <path d="M7 1 V13 M1 7 H13" strokeLinecap="square" />
  </svg>
);

const StatusPill = ({ status, dark = false, className = "" }) => {
  const map = { Completed: "completed", Available: "available", "On-going": "ongoing" };
  const cls = map[status] || "";
  return (
    <span className={`status-stamp ${cls} ${className}`}>
      <span className="dot"></span>{status}
    </span>
  );
};

/* Decorative topographic SVG — used as backdrop accent in a few sections.
   These are hand-drawn-feeling concentric isoline blobs. */
const TopoLines = ({ stroke = "#857F77", opacity = 0.4, density = 18, className = "" }) => {
  // generate concentric oval blobs offset
  const lines = [];
  const cx = 600, cy = 400;
  for (let i = 0; i < density; i++) {
    const r = 60 + i * 26;
    // perturb the ellipse slightly per ring
    const seed = i * 0.7;
    const rx = r + Math.sin(seed) * 18;
    const ry = r * 0.72 + Math.cos(seed * 1.3) * 22;
    lines.push(
      <ellipse
        key={`a${i}`}
        cx={cx + Math.sin(seed) * 14}
        cy={cy + Math.cos(seed) * 9}
        rx={rx}
        ry={ry}
        transform={`rotate(${-18 + Math.sin(seed) * 4} ${cx} ${cy})`}
      />
    );
  }
  // secondary blob
  const cx2 = 200, cy2 = 180;
  for (let i = 0; i < Math.floor(density * 0.7); i++) {
    const r = 40 + i * 22;
    const seed = i * 0.5 + 3;
    const rx = r + Math.sin(seed) * 12;
    const ry = r * 0.8 + Math.cos(seed * 1.1) * 14;
    lines.push(
      <ellipse
        key={`b${i}`}
        cx={cx2 + Math.sin(seed) * 8}
        cy={cy2 + Math.cos(seed) * 6}
        rx={rx}
        ry={ry}
        transform={`rotate(${24 + Math.cos(seed) * 5} ${cx2} ${cy2})`}
      />
    );
  }
  return (
    <svg viewBox="0 0 1200 800" preserveAspectRatio="xMidYMid slice" className={className}
      style={{ width: "100%", height: "100%" }}
      fill="none" stroke={stroke} strokeWidth="0.6" opacity={opacity}>
      {lines}
    </svg>
  );
};

/* A more architectural plot-outline overlay for the New Launch visual:
   site plan with plot lots, road, scale notations. */
const PlotOutline = ({ stroke = "#F7F4EF", accent = "#B8832A" }) => (
  <svg viewBox="0 0 800 600" preserveAspectRatio="xMidYMid meet" style={{ width: "100%", height: "100%" }}
    fill="none">
    <defs>
      <pattern id="grid-tiny" width="20" height="20" patternUnits="userSpaceOnUse">
        <path d="M20 0 L0 0 0 20" fill="none" stroke={stroke} strokeWidth="0.3" opacity="0.18" />
      </pattern>
    </defs>
    <rect width="800" height="600" fill="url(#grid-tiny)" />

    {/* Site boundary */}
    <path d="M80 80 L720 92 L728 480 L92 504 Z"
      stroke={stroke} strokeOpacity="0.85" strokeWidth="1.4" strokeDasharray="6 4" />

    {/* Central road */}
    <path d="M70 300 L730 286" stroke={stroke} strokeOpacity="0.6" strokeWidth="14" />
    <path d="M70 300 L730 286" stroke={accent} strokeOpacity="0.9" strokeWidth="1" strokeDasharray="8 8" />

    {/* Top row plots */}
    {[0,1,2,3,4,5].map(i => (
      <g key={`t${i}`}>
        <rect x={100 + i * 100} y={108 + i * 1.5} width="92" height="170"
          stroke={stroke} strokeOpacity="0.7" strokeWidth="0.8" />
        <text x={146 + i * 100} y={200 + i * 1.5} textAnchor="middle"
          fill={stroke} fillOpacity="0.85" fontFamily="JetBrains Mono, monospace" fontSize="10" letterSpacing="0.1em">
          P-{String(i + 1).padStart(2, "0")}
        </text>
      </g>
    ))}
    {/* Bottom row plots */}
    {[0,1,2,3,4,5].map(i => (
      <g key={`b${i}`}>
        <rect x={100 + i * 100} y={316 + i * 1.5} width="92" height="170"
          stroke={stroke} strokeOpacity="0.7" strokeWidth="0.8" />
        <text x={146 + i * 100} y={408 + i * 1.5} textAnchor="middle"
          fill={stroke} fillOpacity="0.85" fontFamily="JetBrains Mono, monospace" fontSize="10" letterSpacing="0.1em">
          P-{String(i + 7).padStart(2, "0")}
        </text>
      </g>
    ))}

    {/* North arrow */}
    <g transform="translate(700 540)">
      <circle cx="0" cy="0" r="22" stroke={stroke} strokeOpacity="0.7" strokeWidth="0.8" />
      <path d="M0 -16 L6 8 L0 4 L-6 8 Z" fill={accent} />
      <text x="0" y="-28" textAnchor="middle" fill={stroke} fillOpacity="0.85"
        fontFamily="JetBrains Mono, monospace" fontSize="9" letterSpacing="0.15em">N</text>
    </g>

    {/* Coords */}
    <text x="80" y="68" fill={stroke} fillOpacity="0.75"
      fontFamily="JetBrains Mono, monospace" fontSize="9" letterSpacing="0.15em">
      10.7867° N  ·  76.6557° E
    </text>
    <text x="80" y="544" fill={stroke} fillOpacity="0.75"
      fontFamily="JetBrains Mono, monospace" fontSize="9" letterSpacing="0.15em">
      SITE-PLAN / WEST MOUNT / PH-01
    </text>
    <text x="728" y="68" textAnchor="end" fill={stroke} fillOpacity="0.75"
      fontFamily="JetBrains Mono, monospace" fontSize="9" letterSpacing="0.15em">
      24 PLOTS / 4–8 CENTS
    </text>
  </svg>
);

/* Small abstract iconography for the Method section — line drawings only */
const MethodIcons = {
  see: () => (
    <svg width="56" height="56" viewBox="0 0 56 56" fill="none" stroke="currentColor" strokeWidth="1">
      <circle cx="28" cy="28" r="22" />
      <circle cx="28" cy="28" r="14" />
      <circle cx="28" cy="28" r="6" />
      <path d="M28 6 V14 M28 42 V50 M6 28 H14 M42 28 H50" />
      <circle cx="28" cy="28" r="2" fill="currentColor" />
    </svg>
  ),
  walk: () => (
    <svg width="56" height="56" viewBox="0 0 56 56" fill="none" stroke="currentColor" strokeWidth="1">
      <path d="M6 44 C 14 30, 22 36, 28 28 C 34 20, 42 26, 50 14" />
      <circle cx="6" cy="44" r="2.5" fill="currentColor" />
      <circle cx="28" cy="28" r="2.5" fill="currentColor" />
      <circle cx="50" cy="14" r="2.5" fill="currentColor" />
      <path d="M14 18 L20 12 L26 18 L20 24 Z" />
    </svg>
  ),
  verify: () => (
    <svg width="56" height="56" viewBox="0 0 56 56" fill="none" stroke="currentColor" strokeWidth="1">
      <rect x="14" y="8" width="32" height="40" />
      <path d="M20 18 H40 M20 24 H40 M20 30 H34 M20 36 H30" />
      <circle cx="14" cy="42" r="8" fill="var(--ground-white, #F7F4EF)" stroke="currentColor" />
      <path d="M10 42 L13 45 L18 39" strokeWidth="1.2" />
    </svg>
  ),
  own: () => (
    <svg width="56" height="56" viewBox="0 0 56 56" fill="none" stroke="currentColor" strokeWidth="1">
      <path d="M8 26 L28 8 L48 26 V48 H8 Z" />
      <path d="M22 48 V32 H34 V48" />
      <circle cx="28" cy="40" r="1.5" fill="currentColor" />
    </svg>
  ),
};

/* ── LandPlaceholder ────────────────────────────────────────────
   Rich on-brand SVG standing in for a photograph that does not exist yet.
   Themes paint very different moods so each card reads as a unique landscape.
   Pass a seed to get deterministic variation across cards. */
const LP_THEMES = {
  fields:  { from: "#7a8a4a", to: "#3a4c20", line: "#f7f4ef", glow: "#c8b06a" },
  hills:   { from: "#4d5d54", to: "#1f2c25", line: "#e9e1cf", glow: "#b8832a" },
  laterite:{ from: "#a6532a", to: "#3e1f0e", line: "#f7e9d7", glow: "#e0b06e" },
  paddy:   { from: "#9bb46a", to: "#3f5826", line: "#f7f4ef", glow: "#d8c478" },
  dawn:    { from: "#c98a4a", to: "#4a2b1c", line: "#f7e6cf", glow: "#f0c878" },
  forest:  { from: "#3b5a3e", to: "#0e1d18", line: "#d8e8d4", glow: "#7ea882" },
  mist:    { from: "#8a9aa2", to: "#384148", line: "#f7f4ef", glow: "#c7b487" },
  earth:   { from: "#7a6a4a", to: "#2a2218", line: "#e9d9b8", glow: "#b8832a" },
};

/* Generates concentric ring patterns offset around two centres,
   with a horizon-style gradient + topo isolines. */
const LandPlaceholder = ({ theme = "hills", seed = 1, label = "" }) => {
  const T = LP_THEMES[theme] || LP_THEMES.hills;
  // pseudo-random based on seed
  const r = (n) => {
    const x = Math.sin(seed * 999 + n * 13.37) * 10000;
    return x - Math.floor(x);
  };
  const blob = (cx, cy, k) => {
    const rings = [];
    for (let i = 0; i < 18; i++) {
      const base = 28 + i * 22;
      const rx = base + r(k + i) * 30;
      const ry = base * (0.55 + r(k + i + 1) * 0.4);
      const rot = (r(k + i + 2) - 0.5) * 40;
      const dx = cx + (r(k + i + 3) - 0.5) * 24;
      const dy = cy + (r(k + i + 4) - 0.5) * 14;
      rings.push(
        <ellipse key={`${k}-${i}`} cx={dx} cy={dy} rx={rx} ry={ry}
          transform={`rotate(${rot} ${dx} ${dy})`} />
      );
    }
    return rings;
  };
  const cx1 = 200 + r(1) * 80, cy1 = 280 + r(2) * 60;
  const cx2 = 520 + r(3) * 80, cy2 = 200 + r(4) * 50;
  const gid = `lp-${seed}-${theme}`;
  return (
    <svg viewBox="0 0 800 600" preserveAspectRatio="xMidYMid slice"
      style={{ position: "absolute", inset: 0, width: "100%", height: "100%" }} aria-hidden="true">
      <defs>
        <linearGradient id={`${gid}-bg`} x1="0%" y1="0%" x2="0%" y2="100%">
          <stop offset="0%" stopColor={T.from} />
          <stop offset="100%" stopColor={T.to} />
        </linearGradient>
        <radialGradient id={`${gid}-sun`} cx={cx1 / 800} cy={cy1 / 600} r="0.5">
          <stop offset="0%" stopColor={T.glow} stopOpacity="0.42" />
          <stop offset="60%" stopColor={T.glow} stopOpacity="0.06" />
          <stop offset="100%" stopColor={T.glow} stopOpacity="0" />
        </radialGradient>
        <filter id={`${gid}-grain`}>
          <feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" seed={seed} />
          <feColorMatrix values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.15 0" />
          <feComposite in2="SourceGraphic" operator="in" />
        </filter>
      </defs>

      <rect width="800" height="600" fill={`url(#${gid}-bg)`} />
      <rect width="800" height="600" fill={`url(#${gid}-sun)`} />

      <g stroke={T.line} strokeWidth="0.55" fill="none" opacity="0.55">
        {blob(cx1, cy1, 10)}
      </g>
      <g stroke={T.line} strokeWidth="0.45" fill="none" opacity="0.32">
        {blob(cx2, cy2, 50)}
      </g>

      {/* horizon scratch */}
      <line x1="0" y1="420" x2="800" y2={420 + (r(7) - 0.5) * 30} stroke={T.line} strokeWidth="0.4" opacity="0.22" />
      <line x1="0" y1="450" x2="800" y2={450 + (r(8) - 0.5) * 30} stroke={T.line} strokeWidth="0.4" opacity="0.18" />

      {/* coord + label */}
      {label && (
        <text x="24" y="38" fill={T.line} fillOpacity="0.55"
          fontFamily="JetBrains Mono, monospace" fontSize="9" letterSpacing="0.18em">
          {label}
        </text>
      )}
      <text x="776" y="38" textAnchor="end" fill={T.line} fillOpacity="0.45"
        fontFamily="JetBrains Mono, monospace" fontSize="8" letterSpacing="0.18em">
        TOPO-{String(seed).padStart(3,"0")}
      </text>

      {/* grain */}
      <rect width="800" height="600" fill={T.line} filter={`url(#${gid}-grain)`} opacity="0.5" />
    </svg>
  );
};

/* ── Project names with a phase ────────────────────────────────────
   "Blue Hills Phase 2" is one community with a qualifier, not a
   four-word name, and set at one size the qualifier eats the title.
   Two renderings, and the split is derived from the name itself so a
   Phase 3 needs no data change:

     <ProjectName>      titles — "Phase 2" small and regular after it
     projectNameInline  sentences and plain-text slots — "(Phase 2)",
                        because a size step mid-sentence reads as a typo

   Leave the raw `name` in aria-labels, alt text and form values. */
const splitProjectName = (name = "") => {
  const m = /^(.*?)\s+(Phase\s+\d+)$/.exec(name.trim());
  return m ? { main: m[1], phase: m[2] } : { main: name, phase: null };
};

const ProjectName = ({ name }) => {
  const { main, phase } = splitProjectName(name);
  return phase
    ? <>{main}<span className="pname-phase">{phase}</span></>
    : <>{name}</>;
};

const projectNameInline = (name = "") => {
  const { main, phase } = splitProjectName(name);
  return phase ? `${main} (${phase})` : name;
};

/* Hover video for a card. Sits on top of the card's still image, which is
   the video's own first frame. Plays only on hover (and keyboard focus) on
   devices that can hover, fades in once frames are actually playing, and on
   leave fades out, pauses and rewinds to frame 0 so the next hover starts
   from the still. The video file is fetched once the card is near view. */
/* `src` takes one file; `sources` takes a list of { src, type } and lets the
   browser pick the first it can play (the home cards list MP4 first, as the
   smaller file and the one Safari always plays, then WebM). */
const HoverVideo = ({ src, sources }) => {
  const key = src || (sources && sources.map(s => s.src).join("|"));
  const ref = React.useRef(null);
  React.useEffect(() => {
    const v = ref.current;
    const card = v && v.closest("a");
    if (!v || !card) return;
    const canHover = window.matchMedia("(hover: hover) and (pointer: fine)");
    const reduced = window.matchMedia("(prefers-reduced-motion: reduce)");
    if (!canHover.matches || reduced.matches) return;

    let wanted = false, stopTimer = null;
    /* Never call load() once a hover has begun — it would reset playback. */
    const io = new IntersectionObserver((es) => {
      if (es.some(e => e.isIntersecting)) {
        v.preload = "auto";
        if (!wanted && v.readyState === 0) v.load();
        io.disconnect();
      }
    }, { rootMargin: "200px" });
    io.observe(card);

    const isHovered = () => card.matches(":hover") || document.activeElement === card;
    const play = () => { const p = v.play(); if (p && p.catch) p.catch(() => {}); };
    const onPlaying = () => { if (wanted) v.classList.add("is-playing"); };
    const start = () => {
      wanted = true;
      clearTimeout(stopTimer);
      play();
      if (!v.paused && v.readyState >= 3) v.classList.add("is-playing");
    };
    const stop = () => {
      wanted = false;
      v.classList.remove("is-playing");
      clearTimeout(stopTimer);
      stopTimer = setTimeout(() => {
        // The card lifts on hover, so a stray mouseleave can fire while the
        // pointer is still over it. If it is, carry on instead of stopping.
        if (isHovered()) { start(); return; }
        v.pause(); try { v.currentTime = 0; } catch (e) {}
      }, 450);
    };
    // Keep looping for as long as the card is hovered, even if the browser's
    // native loop stalls or the video gets paused underneath us.
    const onEnded = () => { if (wanted || isHovered()) { wanted = true; v.currentTime = 0; play(); } };
    const onPause = () => { if (wanted && !v.ended) play(); };
    v.addEventListener("playing", onPlaying);
    v.addEventListener("ended", onEnded);
    v.addEventListener("pause", onPause);
    card.addEventListener("mouseenter", start);
    card.addEventListener("mouseleave", stop);
    card.addEventListener("focus", start);
    card.addEventListener("blur", stop);
    return () => {
      io.disconnect(); clearTimeout(stopTimer);
      v.removeEventListener("playing", onPlaying);
      v.removeEventListener("ended", onEnded);
      v.removeEventListener("pause", onPause);
      card.removeEventListener("mouseenter", start);
      card.removeEventListener("mouseleave", stop);
      card.removeEventListener("focus", start);
      card.removeEventListener("blur", stop);
    };
  }, [key]);
  return (
    <video ref={ref} className="hover-video" src={sources ? undefined : src} muted loop playsInline preload="none" aria-hidden="true" tabIndex={-1}>
      {sources && sources.map(s => <source key={s.src} src={s.src} type={s.type} />)}
    </video>
  );
};

/* ── Buy now ──────────────────────────────────────────────────────
   One CTA in three places (client, 3 Nov): the project hero, the home
   page's project cards and the projects index cards. `BuyNowInner` is
   the markup, so the hero can put it in a <button> and the cards can
   put it in a <span> inside their own link (a link inside a link is
   not allowed, and on the cards the whole card is the link anyway).
   The hover animation lives in styles.css under "Buy now". */
const BuyNowInner = () => (
  <>
    <span className="buy-fill" aria-hidden="true" />
    <span className="buy-sheen" aria-hidden="true" />
    <span className="buy-ic" aria-hidden="true">
      <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor"
           strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
        <path d="M8.6 1.8H13.4a.8.8 0 0 1 .8.8v4.8L7.6 14a1.1 1.1 0 0 1-1.6 0L2 10a1.1 1.1 0 0 1 0-1.6z" />
        <circle cx="10.9" cy="5.1" r="1.1" />
      </svg>
    </span>
    <span className="buy-label"><span data-t="Buy now">Buy now</span></span>
    <span className="buy-coin" aria-hidden="true"><ArrowIcon size={11} /></span>
  </>
);

/* Decorative: inside a card that is already a labelled link. */
const BuyNow = ({ className = "" }) => (
  <span className={`buy${className ? ` ${className}` : ""}`} aria-hidden="true">
    <BuyNowInner />
  </span>
);

Object.assign(window, { HoverVideo, ArrowIcon, PlusIcon, StatusPill, TopoLines, PlotOutline, MethodIcons, LandPlaceholder,
                        splitProjectName, ProjectName, projectNameInline,
                        BuyNow, BuyNowInner });
