/* IJWestern — the Contact page. Step 9 of phase 2 (client, 3 Sep 2026).
   Global: none. Renders into #root.

   The pointers doc asks for four things and this page is those four:

     1. a general contact form,
     2. the necessary information beside it — office hours, location,
        email,
     3. sleek, elegant, modern, the site's own design, good animations,
     4. a card at the foot with a catchy title that leads to the
        projects page.

   **Placeholders are deliberate and they are marked.** The client has
   not supplied an office day range yet (CLAUDE.md, "Waiting on the
   client" 2). Nothing here invents it: the hours row states the hours
   it has and says the days are still to come, and carries
   `data-pending` so `grep data-pending` finds it. The email card is
   visits@ijwestern.com (client, 4 Nov 2026).

   The form is forms.jsx's ContactForm (3 Nov 2026), the general one,
   and this page is the only place it appears. */

const { useState, useEffect, useRef, useMemo } = React;

const CT = IJW_CONTACT;

/* Counted off PROJECT_DATA, not typed, exactly as the About page counts
   its own totals. A figure corrected on a project page corrects the
   closing card with it. */
const CT_PROJECTS = Object.entries(PROJECT_DATA)
  .map(([slug, p]) => ({ slug, ...p }))
  .sort((a, b) => a.number.localeCompare(b.number));

const CT_COUNT_WORDS = ["no", "one", "two", "three", "four", "five",
                        "six", "seven", "eight", "nine", "ten"];

const CT_TOTALS = (() => {
  const num = (v) => {
    const n = parseFloat(String(v).replace(/[^0-9.]/g, ""));
    return Number.isFinite(n) ? n : 0;
  };
  const fig = (p, k) => (p.figures || []).find((f) => f.k === k);
  return {
    count: CT_PROJECTS.length,
    plots: CT_PROJECTS.reduce((n, p) => n + num(fig(p, "Plots") && fig(p, "Plots").v), 0),
  };
})();

/* ── Hooks (the site's own, as about.jsx and projects.jsx carry them) ── */

function useReveals() {
  useEffect(() => {
    const SEL = ".reveal, .reveal-x, .reveal-fade";
    const io = new IntersectionObserver(
      (entries) => entries.forEach((e) => {
        if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); }
      }),
      { threshold: 0.12, rootMargin: "0px 0px -6% 0px" }
    );
    document.querySelectorAll(SEL).forEach((el) => {
      if (!el.classList.contains("in")) io.observe(el);
    });
    return () => io.disconnect();
  }, []);
}

function useReducedMotion() {
  const [reduced, setReduced] = useState(
    () => !!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches)
  );
  useEffect(() => {
    if (!window.matchMedia) return;
    const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
    const on = (e) => setReduced(e.matches);
    mq.addEventListener("change", on);
    return () => mq.removeEventListener("change", on);
  }, []);
  return reduced;
}

function useFinePointer() {
  const [fine, setFine] = useState(
    () => !!(window.matchMedia && window.matchMedia("(hover: hover) and (pointer: fine)").matches)
  );
  useEffect(() => {
    if (!window.matchMedia) return;
    const mq = window.matchMedia("(hover: hover) and (pointer: fine)");
    const on = (e) => setFine(e.matches);
    mq.addEventListener("change", on);
    return () => mq.removeEventListener("change", on);
  }, []);
  return fine;
}

/* ── Hero: what we are, and the form beside it ────────────────────── */
/* The phone and WhatsApp buttons that sat under the subtext came out at
   the client's request after sign-off. The number still reaches a reader
   from the footer, which carries it on every page; nothing on this page
   states it any more. */
function ContactHero() {
  return (
    <section className="ct-hero" data-screen-label="Contact">
      <div className="shell ct-hero-grid">
        <div className="ct-intro">

          {/* The client's own title and subtext, supplied after the
              first cut of this page. Short, one sentence, two lines,
              set at the About page's smaller display size rather than
              the 68px the other pages use — his standing note from step
              8: a title is short on the page, not only in its word
              count. The subtext arrived as one block and is split at
              its natural seam so the lead and the body keep the
              typographic step this layout is built around. */}
          <h1 className="t-display ct-title reveal" style={{ "--reveal-delay": "90ms" }}>
            One step closer<br />to <em>home</em>
          </h1>

          <p className="ct-lead reveal" style={{ "--reveal-delay": "170ms" }}>
            Talk to the people who know the land. No call centres. No brokers. Just
            our verified team of professionals answering all your questions.
          </p>

          <p className="ct-body reveal" style={{ "--reveal-delay": "240ms" }}>
            Ask us about the plot, price, documents or the land itself. Whatever we
            tell you, we’ll put in writing.
          </p>
        </div>

        <div className="ct-form-wrap">
          <ContactForm className="register-form ct-form reveal-x"
                       style={{ "--reveal-delay": "160ms" }} />
        </div>
      </div>
    </section>
  );
}

/* ── The information beside the form ────────────────────────────────
   Hours, location, email, and where else we are. The hours have no
   day range in the source document, so that card says so rather than
   inventing one. */
function InfoCard({ k, children, note = null, pending = null, i = 0 }) {
  return (
    <div className={`ct-card reveal${pending ? " is-pending" : ""}`}
         data-pending={pending || undefined}
         style={{ "--reveal-delay": `${i * 70}ms` }}>
      <span className="ct-card-k">{k}</span>
      <div className="ct-card-v">{children}</div>
      {note && <p className="ct-card-note">{note}</p>}
    </div>
  );
}

function ContactInfo() {
  return (
    <section className="ct-info rule-top" data-screen-label="Contact info">
      <div className="shell">
        <div className="ct-info-grid">
          <InfoCard k="The office" i={0}
                    note={CT.coords}>
            {CT.address1}<br />{CT.address2}
          </InfoCard>

          <InfoCard k="Working hours" i={1}
                    pending="office-days"
                    note="Open days still to be confirmed. Call ahead and we will tell you who is in.">
            {CT.hours}
          </InfoCard>

          <InfoCard k="Email" i={2}
                    note="It reaches the same desk as the form on this page.">
            <a href={`mailto:${CT.email}`}>{CT.email}</a>
          </InfoCard>

          <InfoCard k="Elsewhere" i={3}
                    note="Layouts, ground and the occasional handover.">
            <a href={CT.instagram} target="_blank" rel="noopener">Instagram ↗</a>
          </InfoCard>
        </div>
      </div>
    </section>
  );
}

/* ── The closing card ───────────────────────────────────────────────
   The pointers doc: "At the end of the page you can have a card with a
   catchy title about the projects that leads to the project page." One
   card, one link, the whole surface clickable.

   The glow follows the pointer, written straight to the node inside a
   rAF rather than through state, for the same reason the About page's
   tilt is: this fires on every pointer move and a re-render per move
   would restart the card's own transitions. It runs only where there is
   a real pointer to follow, and not at all under reduced motion. */
function ProjectsCard() {
  const reduced = useReducedMotion();
  const fine = useFinePointer();
  const cardRef = useRef(null);

  useEffect(() => {
    if (reduced || !fine) return;
    const el = cardRef.current;
    if (!el) return;
    let raf = null, x = 50, y = 50;
    const apply = () => {
      raf = null;
      el.style.setProperty("--ct-gx", x.toFixed(1) + "%");
      el.style.setProperty("--ct-gy", y.toFixed(1) + "%");
    };
    const move = (e) => {
      const r = el.getBoundingClientRect();
      x = ((e.clientX - r.left) / r.width) * 100;
      y = ((e.clientY - r.top) / r.height) * 100;
      if (raf === null) raf = requestAnimationFrame(apply);
    };
    const leave = () => {
      if (raf !== null) { cancelAnimationFrame(raf); raf = null; }
      el.style.removeProperty("--ct-gx");
      el.style.removeProperty("--ct-gy");
    };
    el.addEventListener("pointermove", move);
    el.addEventListener("pointerleave", leave);
    return () => {
      el.removeEventListener("pointermove", move);
      el.removeEventListener("pointerleave", leave);
      if (raf !== null) cancelAnimationFrame(raf);
    };
  }, [reduced, fine]);

  // Opens the client's sentence now, so it is capitalised rather than
  // sitting mid-line as it did in the copy this replaced.
  const n = CT_COUNT_WORDS[CT_TOTALS.count];
  const word = n ? n[0].toUpperCase() + n.slice(1) : CT_TOTALS.count;

  return (
    <section className="ct-next" data-screen-label="To the projects">
      <div className="shell">
        {/* The whole card is the link. Everything inside it is inert
            markup, including the thing that looks like a button, so
            there is no interactive element nested inside another. */}
        <a className="ct-next-card reveal-fade"
           ref={cardRef}
           href="projects.html"
           data-ground="dark"
           aria-label="Explore all projects">
          <span className="ct-next-glow" aria-hidden="true" />
          <span className="ct-next-inner">
            <span className="ct-next-eyebrow">The portfolio</span>
            <span className="ct-next-title t-display">
              Places worth <em>owning</em>.
            </span>
            {/* The client's copy, with its two figures still counted off
                PROJECT_DATA rather than typed, so a correction on a
                project page corrects this card with it. */}
            <span className="ct-next-lead">
              {word} thoughtfully planned communities across Palakkad, with{" "}
              {CT_TOTALS.plots} surveyed and documented plots ready to explore. More on
              the way.
            </span>
            <span className="ct-next-names" aria-hidden="true">
              {CT_PROJECTS.map((p) => (
                <span className="ct-next-name" key={p.slug}>
                  <span className="ct-next-num">{p.number}</span>
                  {projectNameInline(p.name)}
                </span>
              ))}
            </span>
            <span className="btn btn-primary ct-next-cta">
              Explore all<span className="arrow"><ArrowIcon /></span>
            </span>
          </span>
        </a>
      </div>
    </section>
  );
}

/* ── Page ───────────────────────────────────────────────────────── */
function ContactPage() {
  const [visit, setVisit] = useState(false);
  useReveals();

  return (
    <>
      <TopBar page="contact" onSchedule={() => setVisit(true)} />
      <main className="ct-page" id="top">
        <ContactHero />
        <ContactInfo />
        <ProjectsCard />
      </main>
      <Footer onSchedule={() => setVisit(true)} />
      <VisitModal open={visit} onClose={() => setVisit(false)} />
    </>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<ContactPage />);
