// ═══════════════════════════════════════════════════════════════
// COACH MARK — residency filter
// ═══════════════════════════════════════════════════════════════
// A one-time pointer at the residency filter, decided by Chris 2026-09-08.
// Visitors read the matrix as "what Reurbano offers" when it is really "what
// is open to you", and tax residency is the dimension that changes the answer
// most.
//
// NOTHING HERE GEOLOCATES, and it must stay that way. Two reasons, either one
// sufficient: `cf-ipcountry` is a Worker request header that never reaches the
// browser, and the filter asks where the visitor is a TAX resident, which an
// IP address cannot tell you even when it is accurate about the country.
//
// Mounted inside DesktopApp and MobileApp rather than App, because it needs
// the live `profile` object. That matters: the desktop app persists to
// `reurbano-profile-v2` and the mobile app to `r-profile-v3`, and a `#p=`
// share link overrides both. Reading the prop instead of storage means one
// code path covers all three, and setting residency dismisses the mark
// immediately instead of on the next reload.

const _CM_KEY = "r-coach-residency";
const _CM_GATE_KEY = "r-gate-state";

// Once per story revision, like the story's own "seen" mark: bumping content/story.json's revision
// sends returning visitors back to the cover and also shows this tip once more to anyone who
// dismissed it before (Chris, 2026-09-24). Visitors who have set a residency still never see it.
function _cmMark() {
  return window.StoryRules ? window.StoryRules.seenMark(window.STORY) : "1";
}
function _cmSeen() {
  try { return localStorage.getItem(_CM_KEY) === _cmMark(); } catch (_) { return true; }
}
function _cmMarkSeen() {
  try { localStorage.setItem(_CM_KEY, _cmMark()); } catch (_) {}
}

// The gate dispatches `reurbano:gate-resolved` from a mount effect, which can
// fire before this component mounts. A return visitor whose email is already
// stored never sees the gate at all, so no event is coming. Read the same
// storage key the gate writes, synchronously, and treat a stored state as
// "already resolved".
function _cmGateResolved() {
  try { return !!localStorage.getItem(_CM_GATE_KEY); } catch (_) { return false; }
}

// Anchor per breakpoint. Between 601 and 900px the sidebar is a drawer behind
// the hamburger, so the residency row is not on screen and the button that
// opens it is the honest target.
function _cmSelector(bp) {
  if (bp === "mobile" || bp === "narrow") return '[data-filter-key="residency"]';
  if (bp === "drawer") return ".r-hamburger";
  return '.r-filter-row[data-filter-key="residency"]';
}

function CoachMark({ profile, lang, dark, bp, t }) {
  const [resolved, setResolved] = React.useState(_cmGateResolved);
  const [dismissed, setDismissed] = React.useState(_cmSeen);
  const [rect, setRect] = React.useState(null);

  const residencySet = !!(profile && profile.residency);

  // Setting residency is the mark's whole purpose, so treat it as read.
  React.useEffect(() => {
    if (residencySet && !dismissed) { _cmMarkSeen(); setDismissed(true); }
  }, [residencySet, dismissed]);

  React.useEffect(() => {
    if (resolved) return;
    const onResolved = () => setResolved(true);
    window.addEventListener("reurbano:gate-resolved", onResolved);
    return () => window.removeEventListener("reurbano:gate-resolved", onResolved);
  }, [resolved]);

  const active = resolved && !dismissed && !residencySet;

  // Track the anchor. Polling rather than a ResizeObserver on one node because
  // the anchor itself comes and goes: the filter rows render after FILTERS
  // loads, and the hamburger and the mobile chip are different elements at
  // different widths. 300ms is invisible to a reader and cheap, and the
  // interval only runs while the mark is up.
  React.useEffect(() => {
    if (!active) { setRect(null); return; }
    const sel = _cmSelector(bp);
    const measure = () => {
      const el = document.querySelector(sel);
      if (!el) { setRect(null); return; }
      const r = el.getBoundingClientRect();
      if (r.width === 0 && r.height === 0) { setRect(null); return; }
      setRect({ top: r.top, left: r.left, width: r.width, height: r.height });
    };
    measure();
    const id = setInterval(measure, 300);
    window.addEventListener("resize", measure);
    window.addEventListener("scroll", measure, true);
    return () => {
      clearInterval(id);
      window.removeEventListener("resize", measure);
      window.removeEventListener("scroll", measure, true);
    };
  }, [active, bp]);

  const dismiss = React.useCallback(() => { _cmMarkSeen(); setDismissed(true); }, []);

  React.useEffect(() => {
    if (!active) return;
    const onKey = (e) => { if (e.key === "Escape") dismiss(); };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [active, dismiss]);

  if (!active || !rect || !t) return null;

  const BUBBLE_W = 268;
  const GAP = 10;
  const pad = 12;

  // Sit below the anchor, nudged back inside the viewport if that would
  // overflow either edge.
  let left = rect.left;
  if (left + BUBBLE_W > window.innerWidth - pad) left = window.innerWidth - BUBBLE_W - pad;
  if (left < pad) left = pad;
  const top = rect.top + rect.height + GAP;

  const surface = dark ? "#FAF7F3" : "#393433";
  const ink = dark ? "#393433" : "#FAF7F3";
  const caretLeft = Math.min(
    Math.max(rect.left + Math.min(rect.width, 40) / 2 - left, 14),
    BUBBLE_W - 14
  );

  return (
    <div
      role="status"
      className="r-coach"
      onClick={dismiss}
      style={{
        position: "fixed", top, left, width: BUBBLE_W, zIndex: 60,
        background: surface, color: ink,
        border: `1px solid ${surface}`,
        padding: "12px 14px",
        fontFamily: "var(--font-body)", fontSize: 12.5, lineHeight: 1.45,
        fontWeight: 400, cursor: "pointer",
        boxShadow: dark ? "none" : "0 2px 0 rgba(57,52,51,0.18)",
      }}
    >
      {/* Caret, drawn as a rotated square so it inherits the squared brand
          geometry instead of introducing a border-radius. */}
      <span
        aria-hidden="true"
        style={{
          position: "absolute", top: -5, left: caretLeft - 5,
          width: 9, height: 9, background: surface,
          transform: "rotate(45deg)",
        }}
      />
      {t.coachResidency}
      <span
        style={{
          display: "block", marginTop: 9,
          fontFamily: "var(--font-mono)", fontSize: 10,
          letterSpacing: "0.16em", textTransform: "uppercase",
          color: "#4866B2",
        }}
      >
        {t.coachDismiss}
      </span>
    </div>
  );
}

window.CoachMark = CoachMark;
