/* eslint-disable */
/* Artifex Heritage — Service Area
   50/50 editorial split: regional index on the left, static SVG map
   on the right. The SVG ships pre-projected from Statistics Canada's
   2021 Census Division Cartographic Boundary File (Lambert Conformal
   Conic, EPSG:3347), with the seven served Census Divisions tagged by
   region_id. No tile-loading, no map controls. Hovering an index row
   inverts the matching region's fill via a single data attribute on
   the SVG wrapper. */

/**
 * MAP VIEWBOX IS LOCKED.
 *
 * The SVG viewBox values for the Service Area map are deliberately set to
 * frame all seven regions with consistent margins. These values must not be
 * changed by layout adjustments, responsive logic, or any code that does not
 * have an explicit, intentional reason to reframe the map.
 *
 * Specifically:
 * - The viewBox attribute on the root <svg> is a fixed string constant
 *   (MAP_VIEWBOX below) — the canvas is 1000 × 767 in projection units.
 * - preserveAspectRatio is "xMidYMid meet" (NOT "slice" — "meet" ensures
 *   the entire map is always visible regardless of container aspect ratio,
 *   letterboxing into vellum if the container is the wrong shape).
 * - The SVG fills its container via width: 100% and height: 100%, but the
 *   viewBox itself never changes.
 * - No code path should modify the viewBox in response to container resize,
 *   row expansion, scroll position, or any other layout signal.
 *
 * If the map appears cropped, the fix is to adjust the CONTAINER (column
 * width, column height, aspect ratio), not the viewBox.
 *
 * The normaliseSvgFrame() step below enforces these values on the fetched
 * markup so that even if src/assets/service-area.svg ever drifts, the
 * rendered map still uses the locked frame.
 */

(function () {
  const SVG_URL = "src/assets/service-area.svg?v=1779257900000";
  const MAP_VIEWBOX = "0 0 1000 767";
  const MAP_PRESERVE_ASPECT_RATIO = "xMidYMin meet";

  /* Rewrite the root <svg> tag's viewBox and preserveAspectRatio to the
     locked values. This runs once after fetch — no runtime observers,
     no resize-driven viewBox writes. */
  function normaliseSvgFrame(markup) {
    return markup.replace(/<svg\b([^>]*)>/i, (match, attrs) => {
      let next = attrs;
      next = /\bviewBox=/.test(next)
        ? next.replace(/\bviewBox="[^"]*"/, `viewBox="${MAP_VIEWBOX}"`)
        : `${next} viewBox="${MAP_VIEWBOX}"`;
      next = /\bpreserveAspectRatio=/.test(next)
        ? next.replace(/\bpreserveAspectRatio="[^"]*"/, `preserveAspectRatio="${MAP_PRESERVE_ASPECT_RATIO}"`)
        : `${next} preserveAspectRatio="${MAP_PRESERVE_ASPECT_RATIO}"`;
      return `<svg${next}>`;
    });
  }

  const REGIONS = [
    { id: "region-city-of-ottawa",            name: "City of Ottawa",                places: "Sandy Hill, Lowertown, Centretown, New Edinburgh, Rockcliffe Park, The Glebe, Old Ottawa South, Hintonburg, Westboro, Greely, Metcalfe, Cumberland, Carp, Manotick, Richmond" },
    { id: "region-ottawa-valley",             name: "Ottawa Valley \u2014 Renfrew County", places: "Arnprior, Barry\u2019s Bay, Beachburg, Braeside, Cobden, Eganville, Killaloe, Pembroke, Petawawa, Renfrew, Wilno" },
    { id: "region-lanark",                    name: "Lanark County",                 places: "Almonte, Carleton Place, Lanark, Mississippi Mills, Pakenham, Perth, Smiths Falls" },
    { id: "region-leeds-grenville",           name: "Leeds & Grenville",             places: "Athens, Brockville, Burritt\u2019s Rapids, Cardinal, Delta, Gananoque, Kemptville, Lyndhurst, Maitland, Merrickville, Prescott, Spencerville, Thousand Islands, Westport" },
    { id: "region-frontenac-kingston",        name: "Frontenac County & Kingston",   places: "Battersea, Harrowsmith, Hartington, Inverary, Kingston, Parham, Sharbot Lake, Sydenham, Verona, Wolfe Island" },
    { id: "region-stormont-dundas-glengarry", name: "Stormont, Dundas & Glengarry",  places: "Alexandria, Cornwall, Iroquois, Lancaster, Long Sault, Martintown, Maxville, Morrisburg, Williamstown, Winchester Springs" },
    { id: "region-prescott-russell",          name: "Prescott & Russell",            places: "Casselman, Clarence-Rockland, Embrun, Hawkesbury, L\u2019Orignal, Plantagenet, Rockland, Russell, Vankleek Hill" }
  ];

  function isHoverCapable() {
    if (typeof window === "undefined") return true;
    return matchMedia("(hover: hover) and (pointer: fine)").matches;
  }

  /* Smooth-scroll a row into the viewport if it sits above or below
     the visible area. Only triggers on map clicks (not on hover).
     Avoids Element.scrollIntoView — we compute the page offset by hand
     and use window.scrollTo, which doesn't interfere with the sticky
     map column. */
  function scrollRowIntoViewIfNeeded(regionId) {
    const row = document.querySelector(
      `.ah-region-row[data-region="${regionId}"]`);
    if (!row) return;
    const rect = row.getBoundingClientRect();
    const vh = window.innerHeight;
    const NAV_H = 41;
    const TOP_PAD = 80;     // breathing room below the nav
    const BOTTOM_PAD = 24;
    const above = rect.top < NAV_H + 16;
    const below = rect.bottom > vh - BOTTOM_PAD;
    if (!above && !below) return;
    const targetY = window.scrollY + rect.top - (NAV_H + TOP_PAD);
    window.scrollTo({ top: targetY, behavior: "smooth" });
  }

  function RegionIndex({ active, setActive }) {
    const hoverCapable = useMemo(isHoverCapable, []);

    const onEnter = (id) => { if (hoverCapable) setActive(id); };
    const onLeaveList = () => { if (hoverCapable) setActive(null); };
    const onClick = (id) => {
      // On touch devices, tap-to-toggle. Desktop click acts as a no-op
      // (hover already controls state).
      if (hoverCapable) return;
      setActive((curr) => (curr === id ? null : id));
    };

    return (
      <div className="ah-region-col">
        <div className="ah-region-inner">
          <ul
            className="ah-region-list"
            onMouseLeave={onLeaveList}>

            {REGIONS.map((r) => {
              const isOpen = active === r.id;
              return (
                <li
                  key={r.id}
                  data-region={r.id}
                  className={"ah-region-row" + (isOpen ? " is-open" : "")}
                  onMouseEnter={() => onEnter(r.id)}
                  onClick={() => onClick(r.id)}
                  aria-expanded={isOpen}>

                  <div className="ah-region-headrow">
                    <h3 className="ah-region-heading">{r.name}</h3>
                  </div>
                  <div className="ah-region-reveal">
                    <div className="ah-region-reveal-inner">
                      <p className="ah-region-places">{r.places}</p>
                    </div>
                  </div>
                </li>);
            })}
          </ul>
        </div>
      </div>);
  }

  function ServiceMap() {
    const [active, setActive] = useState(null);
    const [svgMarkup, setSvgMarkup] = useState(null);
    const [svgError, setSvgError] = useState(false);
    const wrapRef = useRef(null);
    const sectionRef = useRef(null);
    const hoverCapable = useMemo(isHoverCapable, []);

    /* Fetch the static SVG once. dangerouslySetInnerHTML inlines it
       so the region <path> nodes become real DOM and we can address
       them by id. */
    useEffect(() => {
      let cancelled = false;
      fetch(SVG_URL)
        .then((r) => {
          if (!r.ok) throw new Error("svg fetch " + r.status);
          return r.text();
        })
        .then((txt) => { if (!cancelled) setSvgMarkup(normaliseSvgFrame(txt)); })
        .catch(() => { if (!cancelled) setSvgError(true); });
      return () => { cancelled = true; };
    }, []);

    /* ---- Height reservation: pre-size to the TALLEST open state ----
       Hold the accordion column at a constant height equal to its tallest
       open state (sum of every row's collapsed height + the single tallest
       reveal). With the rows top-aligned (align-items:flex-start on
       .ah-region-col, see site.css), opening any row never changes the
       column's total height: rows below the open one expand DOWN into the
       reserved room, the section height is unchanged, and "Affiliations" and
       everything below stay completely still. Measured live because reveals
       grow (wrap to more lines) as the column narrows. Never depends on
       `active`. */
    useEffect(() => {
      const root = sectionRef.current;
      if (!root) return;
      let frame = 0;
      const measure = () => {
        const col = root.querySelector('.ah-region-col');
        if (!col) return;
        const rows = Array.from(root.querySelectorAll('.ah-region-row'));
        if (!rows.length) { col.style.minHeight = ''; return; }
        let collapsed = 0;
        let maxReveal = 0;
        rows.forEach((r) => {
          const inner = r.querySelector('.ah-region-reveal-inner');
          const reveal = r.querySelector('.ah-region-reveal');
          // Full collapsed height of the row = its current border-box height
          // MINUS whatever its reveal panel currently occupies. This captures
          // the head row, the row's OWN padding, AND its borders in one shot,
          // and is robust to the row's open/closed state (rowH - revealH is the
          // constant non-reveal part even mid-animation). The previous
          // head-height + border-only sum omitted the row's padding-bottom
          // (added with the title->body gap fix), undercounting the reservation
          // by ~14px per row. At full width the taller map masks this, but at
          // narrow widths the accordion column drives the grid height and a
          // panel — which wraps to MORE lines as the column narrows — then
          // exceeds the reservation, so the section grows on open and scroll
          // anchoring jumps the map. Measuring the rendered box closes that gap
          // at every width.
          const rowH = r.getBoundingClientRect().height;
          const revealH = reveal ? reveal.getBoundingClientRect().height : 0;
          collapsed += rowH - revealH;
          if (inner) maxReveal = Math.max(maxReveal, inner.scrollHeight);
        });
        const colCs = getComputedStyle(col);
        const colPad = (parseFloat(colCs.paddingTop) || 0)
          + (parseFloat(colCs.paddingBottom) || 0);
        col.style.minHeight = Math.ceil(collapsed + maxReveal + colPad) + 'px';
      };
      const schedule = () => {
        cancelAnimationFrame(frame);
        frame = requestAnimationFrame(measure);
      };
      schedule();
      window.addEventListener('resize', schedule);
      if (document.fonts && document.fonts.ready) {
        document.fonts.ready.then(schedule).catch(() => {});
      }
      return () => {
        cancelAnimationFrame(frame);
        window.removeEventListener('resize', schedule);
      };
    }, []);

    /* ---- Map vertical position ----
       The map sits in normal grid flow (desktop) so it is always fully
       visible (never clipped) and contributes its full height to the grid.
       Because the accordion column is held at a constant (tallest-open)
       height, the grid height never changes when a row opens/closes, so the
       map never moves. No JS drives the map position. (See site.css.) */

    /* Map region interactivity. The SVG ships static (via
       dangerouslySetInnerHTML), so we delegate pointer events from the
       wrap div and look up the target via .closest('.region').
       Region paths and index rows share `active` as their single
       source of truth — hovering either updates both. */
    const onSvgMouseOver = (e) => {
      if (!hoverCapable) return;
      const path = e.target && e.target.closest && e.target.closest(".region");
      if (path && path.id) setActive(path.id);
    };
    const onSvgMouseOut = (e) => {
      if (!hoverCapable) return;
      const next = e.relatedTarget;
      // Stay active if the pointer is moving to another region path.
      if (next && next.closest && next.closest(".region")) return;
      setActive(null);
    };
    const onSvgClick = (e) => {
      const path = e.target && e.target.closest && e.target.closest(".region");
      if (!path || !path.id) return;
      const id = path.id;
      setActive((curr) => {
        // Touch: tap-to-toggle. Mouse: tap activates (idempotent).
        const nextActive = hoverCapable ? id : (curr === id ? null : id);
        if (nextActive) scrollRowIntoViewIfNeeded(nextActive);
        return nextActive;
      });
    };

    return (
      <section
        className="ah-map-section"
        ref={sectionRef}
        aria-labelledby="ah-service-area-title">

        <div className="ah-statement ah-statement--credo">
          <p className="ah-credo__line">As much of the original as possible.<br />As little of the new as necessary.</p>
          <div className="ah-hero-intro--columns ah-credo-spread">
            <div className="ah-intro-aside"></div>
            <div className="ah-prose ah-prose--news">
              <p>The greenest building is the one already standing. Repairing old woodwork keeps good material in use and out of landfill, and it almost always outlasts replacement. Most of what Artifex restores was made from slow-grown timber that cannot be bought today. Therefore, responsibly sustaining it is what keeps the costs low and the building itself lasting longer.</p>
              <div className="ah-prose-linkrow">
                <a href="craftsmanship.html" className="ah-tlink ah-intro-link">READ CRAFTSMANSHIP</a>
              </div>
            </div>
          </div>
        </div>

        <div className="ah-map-head">
          <h2 className="ah-h2 ah-map-head__title" id="ah-service-area-title">Service Area</h2>
        </div>

        <div className="ah-map-grid">
          <RegionIndex active={active} setActive={setActive} />

          {/* Screen-reader-only place list (mirrors the regional index) */}
          <ul className="ah-sr-only">
            {REGIONS.map((r) => <li key={r.id}>{r.name}: {r.places}</li>)}
          </ul>

          <div className="ah-map-col">
            <div
              ref={wrapRef}
              className="ah-map-svg-wrap"
              data-active-region={active || ""}
              onMouseOver={onSvgMouseOver}
              onMouseOut={onSvgMouseOut}
              onClick={onSvgClick}>
            {svgMarkup ?
              <div
                className="ah-map-svg-mount"
                dangerouslySetInnerHTML={{ __html: svgMarkup }} /> :
              svgError ?
              <div className="ah-map-fallback">
                Service area covers eastern Ontario, from Pembroke and Renfrew County in the northwest to Cornwall on the Quebec border, south to Kingston on Lake Ontario.
              </div> :
              null}
            </div>
          </div>
        </div>
      </section>);
  }

  window.ServiceMap = ServiceMap;
})();
