// payout-flow.jsx
// Interactive payout guidance sub-flow — three screens:
//   1. Confirm what we know (V3-style facts)
//   2. Path choice (build me a draft / walk me through it)
//   3. Payout plan (per-beneficiary tabs · Plan/Flow/Power views)
//
// Designed to slot into the main PO Activation prototype between
// the beneficiary contact screen and the coverage map screen. Reuses
// `Nav`, `StepBreadcrumb`, `Button` from components.jsx and `ChatGuide`
// from chat-guide.jsx so the right rail looks identical when wired in.

const { useState: pfU, useMemo: pfM, useRef: pfR, useEffect: pfE } = React;

// Used everywhere
const PF_STEPS = ["Add policies", "Review", "Beneficiaries", "Payout guidance", "Coverage map", "Last wishes"];
const PF_CURRENT_STEP = 3;

// ── The guide-path question order ─────────────────────────────────
// Built dynamically by pgApplyContext() from PG_EXPENSES — so the
// walk-through goes through the spouse first, then each child, in
// whatever order Suzie's beneficiaries were confirmed upstream.
// We delegate to the live PG_GUIDE_ORDER binding via a getter so
// any reassignment in pgApplyContext is reflected on next read.
const PF_GUIDE_ORDER_proxy = new Proxy([], {
  get(_, prop) {
    const live = window.PG_GUIDE_ORDER || [];
    if (prop === "length") return live.length;
    if (prop === Symbol.iterator) return live[Symbol.iterator].bind(live);
    if (typeof prop === "string" && /^\d+$/.test(prop)) return live[Number(prop)];
    const v = live[prop];
    return typeof v === "function" ? v.bind(live) : v;
  },
});
const PF_GUIDE_ORDER = PF_GUIDE_ORDER_proxy;

// Initial seed answers for the guide path — start from scratch so the
// user walks through every decision themselves.
const PF_GUIDE_SEED = [];

// Synthesize three pick options for a guide-path question
function pfOptionsFor(exp) {
  if (exp.swap && exp.swap.length) {
    return exp.swap.map((s, i) => ({
      label: s.label, note: s.note, amount: s.amount, primary: i === 0,
    }));
  }
  // Without explicit swaps, offer suggestion + smaller/larger alternatives
  const round = (v) => {
    if (v < 10000)   return Math.round(v / 500)   * 500;
    if (v < 100000)  return Math.round(v / 1000)  * 1000;
    return Math.round(v / 5000) * 5000;
  };
  return [
    { label: "Helm's suggestion", note: exp.source, amount: exp.amount, primary: true },
    { label: "Less coverage",     note: "half this",  amount: round(exp.amount * 0.5) },
    { label: "More coverage",     note: "1.5× this",  amount: round(exp.amount * 1.5) },
  ];
}

// ════════════════════════════════════════════════════════════════════
// SCREEN 1 — Confirm what we know
// ════════════════════════════════════════════════════════════════════

// Defaults for "Add to <category>" — sensible per-category seed.
// `label` and `value` start blank so the input shows ghosted placeholder
// text that vanishes the moment the user starts typing.
const PF_NEW_FACT_DEFAULTS = {
  "People":           { icon: "user",  label: "", value: "", sub: "" },
  "Debts & home":     { icon: "home",  label: "", value: "", sub: "" },
  "Income & spend":   { icon: "heart", label: "", value: "", sub: "" },
  "Milestones ahead": { icon: "clock", label: "", value: "", sub: "" },
};
// Icons users can cycle through when editing
const PF_ICON_CYCLE = ["user", "home", "heart", "clock", "book", "bag", "sparkle", "plane"];

let pfFactCounter = 0;
const pfNewFactId = () => `ff-new-${Date.now()}-${++pfFactCounter}`;

// Cycle order for the confidence tag pill
const PF_CONF_CYCLE = ["self", "hard", "estimate"];

// Relationships used in the People-category combobox
const PF_RELATIONSHIPS = [
  "Spouse", "Partner",
  "Son", "Daughter", "Child",
  "Mother", "Father", "Parent",
  "Sister", "Brother", "Sibling",
  "Grandchild", "Niece", "Nephew",
  "Friend", "Other",
];

// Parse the legacy "Relationship · age" People value into parts.
// Returns { rel, age } using explicit fields when present.
function pfParsePeopleValue(fact) {
  if (fact.relationship != null || fact.age != null) {
    return { rel: fact.relationship || "", age: fact.age || "" };
  }
  const v = (fact.value || "").trim();
  if (!v || v === "—") return { rel: "", age: "" };
  const parts = v.split("·").map(s => s.trim());
  const rel = parts[0] || "";
  // Strip a leading non-digit (e.g. "43") from second part if present
  const age = (parts[1] || "").replace(/[^\d]/g, "");
  return { rel, age };
}
function pfFormatPeopleValue(rel, age) {
  if (rel && age) return `${rel} · ${age}`;
  if (rel)        return rel;
  if (age)        return age;
  return "—";
}

const FactsConfirmScreen = ({ density, onStepClick, onContinue, initialFacts }) => {
  const pad = density === "compact" ? "32px 56px" : "44px 64px";
  const [facts, setFacts] = pfU(initialFacts || PG_FAMILY_FACTS);
  // Resync if context changes underneath us (e.g. Suzie added a policy in
  // another tab of the flow then came back).
  pfE(() => {
    if (initialFacts && initialFacts !== facts && facts.length === 0) {
      setFacts(initialFacts);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [initialFacts]);
  const [justAddedId, setJustAddedId] = pfU(null);
  const [pendingDelete, setPendingDelete] = pfU(null); // fact obj or null
  const [exitingId, setExitingId] = pfU(null);

  const updateFact = (id, patch) => setFacts(fs =>
    fs.map(f => f.id === id ? { ...f, ...patch } : f));
  const requestDelete = (fact) => setPendingDelete(fact);
  // After the user confirms, close the modal, run the exit animation on the
  // tile (~320ms), then actually drop it from state.
  const confirmDelete = () => {
    if (!pendingDelete) return;
    const id = pendingDelete.id;
    setPendingDelete(null);
    setExitingId(id);
    setTimeout(() => {
      setFacts(fs => fs.filter(f => f.id !== id));
      setExitingId(curr => curr === id ? null : curr);
    }, 320);
  };
  const addFact = (category) => {
    const id = pfNewFactId();
    const seed = PF_NEW_FACT_DEFAULTS[category] || PF_NEW_FACT_DEFAULTS["Income & spend"];
    setFacts(fs => [...fs, { id, category, confidence: "self", ...seed }]);
    setJustAddedId(id);
  };

  const estimateCount = facts.filter(f => f.confidence === "estimate").length;

  return (
    <main style={{ flex: 1, padding: pad, background: "var(--helm-white)", overflow: "auto" }}>
      <div style={{ maxWidth: 920 }}>
        <StepBreadcrumb steps={PF_STEPS} current={PF_CURRENT_STEP} onStepClick={onStepClick} />

        <div style={{ margin: "32px 0 8px" }}>
          <h1 style={{
            fontFamily: "var(--font-serif)", fontSize: 48, lineHeight: "52px",
            fontWeight: 600, letterSpacing: "-0.015em", margin: 0,
            textWrap: "balance",
          }}>
            First, let's confirm what I know about your family.
          </h1>
        </div>

        <p style={{
          fontFamily: "var(--font-sans)", fontSize: 18, lineHeight: "28px",
          opacity: 0.8, margin: "0 0 28px", maxWidth: 720,
        }}>
          Every number in the plan I'm about to draft hangs off these facts. Fix anything wrong,
          fill in anything blank, add anything I missed. Tap any field to edit it.
        </p>

        {/* Legend */}
        <div style={{ display: "flex", gap: 18, marginBottom: 24, flexWrap: "wrap" }}>
          {[
            ["Synced from accounts", "var(--helm-soft-chartreuse)"],
            ["You told us", "var(--helm-ecru)"],
            ["My estimate — please review", "var(--helm-orange)"],
          ].map(([label, color]) => (
            <span key={label} style={{ display: "flex", alignItems: "center", gap: 8,
              fontSize: 12, opacity: 0.7 }}>
              <span style={{ width: 10, height: 10, borderRadius: 999, background: color,
                border: color === "var(--helm-ecru)" ? "1px solid var(--helm-dark-ecru)" : "none" }} />
              {label}
            </span>
          ))}
        </div>

        {/* Facts by category */}
        <div style={{ display: "flex", flexDirection: "column", gap: 22 }}>
          {PG_FACT_CATEGORIES.map(cat => (
            <PFFactCategory key={cat}
              category={cat}
              facts={facts.filter(f => f.category === cat)}
              justAddedId={justAddedId}
              exitingId={exitingId}
              onUpdate={updateFact}
              onDelete={requestDelete}
              onAdd={() => addFact(cat)}
            />
          ))}
        </div>

        <div style={{ marginTop: 32, paddingTop: 22, borderTop: "1px solid var(--helm-dark-ecru)",
          display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }}>
          <span style={{ fontSize: 13, opacity: 0.6 }}>
            <b>{facts.length}</b> {facts.length === 1 ? "fact" : "facts"}
            {estimateCount > 0 && <> · <b>{estimateCount}</b> {estimateCount === 1 ? "estimate" : "estimates"} need your review</>}
          </span>
          <Button variant="primary" onClick={onContinue}>
            Continue →
          </Button>
        </div>
      </div>
      {pendingDelete && (
        <PFConfirmModal
          fact={pendingDelete}
          onCancel={() => setPendingDelete(null)}
          onConfirm={confirmDelete}
        />
      )}
    </main>
  );
};

// Centered confirmation modal for fact deletion
const PFConfirmModal = ({ fact, onCancel, onConfirm }) => {
  // Esc to cancel, Enter to confirm — standard a11y
  pfE(() => {
    const onKey = (e) => {
      if (e.key === "Escape") onCancel();
      if (e.key === "Enter") onConfirm();
    };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [onCancel, onConfirm]);

  return (
    <div onClick={onCancel}
      style={{
        position: "fixed", inset: 0, zIndex: 100,
        background: "rgba(0, 29, 0, 0.38)",
        display: "flex", alignItems: "center", justifyContent: "center",
        animation: "helmFadeIn .14s ease",
        padding: 24,
      }}>
      <div onClick={e => e.stopPropagation()}
        role="dialog" aria-modal="true" aria-labelledby="pf-confirm-title"
        style={{
          width: "min(420px, 100%)",
          background: "var(--helm-white)",
          borderRadius: 18,
          boxShadow: "8px 8px 0 0 var(--helm-dark-green)",
          border: "1px solid var(--helm-dark-green)",
          padding: 26,
          display: "flex", flexDirection: "column", gap: 14,
          fontFamily: "var(--font-sans)",
        }}>
        <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
          <div style={{
            width: 44, height: 44, borderRadius: 12,
            background: "var(--helm-orange)",
            display: "flex", alignItems: "center", justifyContent: "center",
            flexShrink: 0,
          }}>
            <PGIcon name={fact.icon} size={20} stroke={1.6} />
          </div>
          <div style={{ minWidth: 0 }}>
            <h3 id="pf-confirm-title" style={{
              fontFamily: "var(--font-serif)", fontSize: 22, lineHeight: "26px",
              fontWeight: 600, letterSpacing: "-0.01em", margin: 0,
            }}>
              Delete this fact?
            </h3>
            <div style={{ fontSize: 11, fontWeight: 600,
              letterSpacing: "0.06em", textTransform: "uppercase",
              opacity: 0.55, marginTop: 4 }}>
              {fact.category}
            </div>
          </div>
        </div>
        <div style={{
          padding: 14, background: "var(--helm-ecru)", borderRadius: 12,
          display: "flex", flexDirection: "column", gap: 4,
        }}>
          <div style={{ fontSize: 12, fontWeight: 600, opacity: 0.7 }}>{fact.label}</div>
          <div style={{ fontFamily: "var(--font-sans)", fontSize: 20, fontWeight: 700,
            letterSpacing: "-0.01em" }}>{fact.value || "—"}</div>
          {fact.sub && (
            <div style={{ fontSize: 11, opacity: 0.6 }}>{fact.sub}</div>
          )}
        </div>
        <p style={{ fontSize: 13, lineHeight: "20px", opacity: 0.78, margin: 0 }}>
          I'll stop using this when I draft your plan. You can always add it back, but anything in the plan that hung off it will need a fresh estimate.
        </p>
        <div style={{ display: "flex", gap: 10, justifyContent: "flex-end",
          marginTop: 4 }}>
          <Button variant="secondary" onClick={onCancel}>Keep it</Button>
          <Button variant="primary" onClick={onConfirm}>Delete fact</Button>
        </div>
      </div>
    </div>
  );
};

// One category block of facts with its own header + edit affordance
const PFFactCategory = ({ category, facts, justAddedId, exitingId, onUpdate, onDelete, onAdd }) => {
  return (
    <section>
      <div style={{ display: "flex", alignItems: "baseline", gap: 10, marginBottom: 10 }}>
        <h3 style={{
          fontFamily: "var(--font-sans)", fontSize: 11, fontWeight: 700,
          letterSpacing: "0.1em", textTransform: "uppercase",
          margin: 0, opacity: 0.55,
        }}>{category}</h3>
        <span style={{ flex: 1, height: 1, background: "var(--helm-dark-ecru)" }} />
        <span style={{ fontSize: 11, opacity: 0.45 }}>
          {facts.length} {facts.length === 1 ? "item" : "items"}
        </span>
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))", gap: 10 }}>
        {facts.map(f => (
          <PFFactTile key={f.id} fact={f}
            autoFocusLabel={f.id === justAddedId}
            exiting={f.id === exitingId}
            onChange={patch => onUpdate(f.id, patch)}
            onDelete={() => onDelete(f)}
          />
        ))}
        <PFAddFactTile category={category} onAdd={onAdd} />
      </div>
    </section>
  );
};

// Relationship combobox — type-to-filter dropdown that lives inline.
// Renders as a text input that looks like the other editable fields.
// Uses `fill` so it shrinks under flex pressure and doesn't run over
// its sibling Age field on the People fact tile.
const PFRelationshipField = ({ value, onChange, autoFocus }) => (
  <div style={{ flex: 1, minWidth: 0 }}>
    <PFComboField
      value={value} onChange={onChange} autoFocus={autoFocus}
      options={PF_RELATIONSHIPS}
      placeholder="Relationship"
      fontSize={17} fontWeight={700}
      fill
    />
  </div>
);

// Generic combo field — type-to-filter dropdown + free-text allowed.
// Used for both relationship and expense category. Renders as inline
// text until focused, at which point it gains a border and shows a
// filtered dropdown of options.
//
// `fill` — make the field fill its flex parent and let its input shrink
//   below `size`, instead of sizing to placeholder/value length. Use
//   this when the field shares a row with siblings that need their
//   own width (e.g. the relationship + age row on a People fact tile).
const PFComboField = ({
  value, onChange, options, autoFocus, placeholder,
  fontSize = 14, fontWeight = 600, opacity = 1,
  italic, color, dense, fill,
}) => {
  const [open, setOpen] = pfU(false);
  const [draft, setDraft] = pfU(value || "");
  const wrapRef = pfR(null);
  const inputRef = pfR(null);

  pfE(() => { setDraft(value || ""); }, [value]);

  pfE(() => {
    if (!open) return;
    const onDoc = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
    };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [open]);

  pfE(() => {
    if (autoFocus && inputRef.current) {
      inputRef.current.focus();
      const v = inputRef.current.value;
      inputRef.current.setSelectionRange(v.length, v.length);
    }
  }, [autoFocus]);

  const matches = (o) => !!draft && o.toLowerCase().includes(draft.toLowerCase());
  const hasExactMatch = options.some(o => o.toLowerCase() === (draft || "").toLowerCase());

  const commit = (v) => { onChange(v); setDraft(v); setOpen(false); };

  const padV = dense ? 1 : 2;

  return (
    <div ref={wrapRef} style={{
      position: "relative",
      display: fill ? "flex" : "inline-flex",
      flex: fill ? "1 1 0" : undefined,
      width: fill ? "100%" : undefined,
      minWidth: 0,
    }}>
      <div style={{
        display: "flex", alignItems: "center", gap: 3,
        flex: fill ? 1 : undefined,
        minWidth: 0,
        background: open ? "var(--helm-white)" : "transparent",
        border: `1px solid ${open ? "var(--helm-dark-green)" : "transparent"}`,
        borderRadius: 5, padding: `${padV}px 5px`, margin: `-${padV}px -5px`,
        transition: "background .12s, border-color .12s",
        cursor: "text",
        overflow: "hidden",
      }}
        onMouseEnter={e => { if (!open) e.currentTarget.style.background = "var(--helm-ecru)"; }}
        onMouseLeave={e => { if (!open) e.currentTarget.style.background = "transparent"; }}>
        <input ref={inputRef}
          value={draft}
          onChange={e => { setDraft(e.target.value); setOpen(true); }}
          onFocus={() => setOpen(true)}
          onKeyDown={e => {
            if (e.key === "Enter") {
              e.preventDefault();
              const exact = options.find(o => o.toLowerCase() === draft.toLowerCase());
              commit(exact || draft);
            } else if (e.key === "Escape") {
              setDraft(value || ""); setOpen(false);
            }
          }}
          onBlur={() => {
            // Commit free-text on blur (combobox allows novel values)
            if (draft !== value) onChange(draft);
          }}
          placeholder={placeholder}
          // In fill mode, let flex own the width; otherwise size to content.
          size={fill ? 1 : Math.max(1, Math.min(28, (draft || placeholder || "").length))}
          style={{
            minWidth: 0,
            width: fill ? "100%" : undefined,
            flex: fill ? "1 1 0" : undefined,
            background: "transparent", border: "none",
            outline: "none", padding: 0,
            fontFamily: "var(--font-sans)",
            fontSize, fontWeight, opacity,
            fontStyle: italic ? "italic" : "normal",
            color: color || "var(--helm-dark-green)",
            letterSpacing: "-0.01em",
            textOverflow: "ellipsis",
          }}
        />
        <button onMouseDown={e => { e.preventDefault(); setOpen(o => !o); inputRef.current?.focus(); }}
          aria-label="Open options"
          style={{ background: "transparent", border: "none", padding: 0,
            cursor: "pointer", color: "var(--helm-dark-grey)",
            display: "flex", alignItems: "center", justifyContent: "center",
            opacity: 0.55,
          }}>
          <PGIcon name="chevron" size={Math.max(10, fontSize - 4)} stroke={2} />
        </button>
      </div>
      {open && (
        <div style={{
          position: "absolute", top: "calc(100% + 4px)", left: 0,
          minWidth: 180, maxHeight: 240, overflow: "auto",
          background: "var(--helm-white)", borderRadius: 10,
          border: "1px solid var(--helm-dark-ecru)",
          boxShadow: "0 8px 24px -8px rgba(0,29,0,0.18)",
          zIndex: 20, padding: 4,
        }}>
          {options.map(o => {
            const isMatch    = matches(o);
            const isSelected = o === value;
            return (
              <button key={o}
                onMouseDown={e => { e.preventDefault(); commit(o); }}
                style={{
                  display: "flex", alignItems: "center", gap: 6,
                  width: "100%", textAlign: "left",
                  padding: "7px 10px",
                  background: isSelected ? "var(--helm-ecru)" : "transparent",
                  border: "none", borderRadius: 6, cursor: "pointer",
                  fontFamily: "var(--font-sans)",
                  fontSize: 13, fontWeight: isMatch || isSelected ? 700 : 500,
                  color: isMatch ? "var(--helm-dark-green)"
                                : draft ? "var(--helm-dark-grey)"
                                : "var(--helm-dark-green)",
                  opacity: draft && !isMatch && !isSelected ? 0.75 : 1,
                }}
                onMouseEnter={e => e.currentTarget.style.background = "var(--helm-ecru)"}
                onMouseLeave={e => e.currentTarget.style.background = isSelected ? "var(--helm-ecru)" : "transparent"}>
                <span style={{ flex: 1, minWidth: 0 }}>
                  {pfHighlightMatch(o, draft)}
                </span>
                {isSelected && <PGIcon name="check" size={12} stroke={2.5} />}
              </button>
            );
          })}
          {draft && !hasExactMatch && (
            <button
              onMouseDown={e => { e.preventDefault(); commit(draft); }}
              style={{
                display: "flex", alignItems: "center", gap: 6,
                width: "100%", textAlign: "left",
                padding: "7px 10px",
                background: "transparent", border: "none", borderRadius: 6,
                borderTop: "1px solid var(--helm-dark-ecru)",
                marginTop: 4, paddingTop: 9,
                cursor: "pointer",
                fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 600,
                color: "var(--helm-chartreuse-2)",
              }}
              onMouseEnter={e => e.currentTarget.style.background = "var(--helm-ecru)"}
              onMouseLeave={e => e.currentTarget.style.background = "transparent"}>
              <PGIcon name="plus" size={11} stroke={2.2} />
              <span>Use "<b>{draft}</b>"</span>
            </button>
          )}
        </div>
      )}
    </div>
  );
};

// Bold-highlight the substring of `text` matching `query`. Returns React children.
function pfHighlightMatch(text, query) {
  if (!query) return text;
  const lower = text.toLowerCase();
  const q = query.toLowerCase();
  const i = lower.indexOf(q);
  if (i < 0) return text;
  return (
    <>
      {text.slice(0, i)}
      <mark style={{
        background: "var(--helm-lemon)", color: "inherit",
        padding: "0 1px", borderRadius: 2,
      }}>{text.slice(i, i + q.length)}</mark>
      {text.slice(i + q.length)}
    </>
  );
}

// The set of canonical expense categories — keys of PG_CATEGORY_TINTS.
const PF_CATEGORIES = Object.keys(PG_CATEGORY_TINTS);

// Inline horizon picker — small clickable chip that opens a tiny menu.
// Used on plan rows so changing horizon re-groups the row immediately.
// When value is empty/falsy, shows an "Add horizon" placeholder chip.
const PFHorizonPicker = ({ value, onChange, size = "sm" }) => {
  const [open, setOpen] = pfU(false);
  const wrapRef = pfR(null);

  pfE(() => {
    if (!open) return;
    const onDoc = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
    };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [open]);

  const hasValue = !!value;

  return (
    <span ref={wrapRef} style={{ position: "relative", display: "inline-flex" }}>
      <button
        onClick={() => setOpen(o => !o)}
        title="Change horizon"
        style={{
          background: "transparent", border: "none", padding: 0,
          cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 2,
        }}>
        {hasValue ? (
          <PGHorizonChip horizon={value} size={size} />
        ) : (
          <span style={{
            display: "inline-flex", alignItems: "center",
            height: size === "sm" ? 18 : 22, padding: "0 8px",
            borderRadius: 999,
            background: "transparent",
            border: "1px dashed var(--helm-dark-ecru)",
            color: "var(--helm-dark-grey)",
            fontFamily: "var(--font-sans)", fontSize: size === "sm" ? 10 : 11,
            fontWeight: 600, letterSpacing: "0.04em", textTransform: "uppercase",
          }}>Set horizon</span>
        )}
        <PGIcon name="chevron" size={9} stroke={2}
          style={{ marginLeft: 1, opacity: 0.55, color: "var(--helm-dark-green)" }} />
      </button>
      {open && (
        <div style={{
          position: "absolute", top: "calc(100% + 4px)", left: 0,
          minWidth: 200,
          background: "var(--helm-white)", borderRadius: 10,
          border: "1px solid var(--helm-dark-ecru)",
          boxShadow: "0 8px 24px -8px rgba(0,29,0,0.18)",
          zIndex: 20, padding: 4,
        }}>
          {PG_HORIZONS.map(h => (
            <button key={h.id}
              onClick={() => { onChange(h.id); setOpen(false); }}
              style={{
                display: "flex", alignItems: "center", gap: 8,
                width: "100%", textAlign: "left",
                padding: "7px 8px",
                background: value === h.id ? "var(--helm-ecru)" : "transparent",
                border: "none", borderRadius: 6, cursor: "pointer",
                fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 500,
                color: "var(--helm-dark-green)",
              }}
              onMouseEnter={e => e.currentTarget.style.background = "var(--helm-ecru)"}
              onMouseLeave={e => e.currentTarget.style.background = value === h.id ? "var(--helm-ecru)" : "transparent"}>
              <PGHorizonChip horizon={h.id} />
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontWeight: 600 }}>{h.label}</div>
                <div style={{ fontSize: 10, opacity: 0.6 }}>{h.desc}</div>
              </div>
              {value === h.id && <PGIcon name="check" size={12} stroke={2.5} />}
            </button>
          ))}
        </div>
      )}
    </span>
  );
};

// Full-dollar formatter: $385,000 (no abbreviation). Used everywhere on
// the payout plan & rail per the design direction.
function pfDollar(v) {
  if (v == null || isNaN(v)) return "—";
  const n = Math.round(v);
  return `$${n.toLocaleString()}`;
}

// Inline-editable dollar amount. Shows "$385,000"; on focus becomes a
// plain digit input (strips commas/$), commits on blur or Enter.
const PFAmountInline = ({ value, onChange, fontSize = 14, fontWeight = 700, align = "right", onFocus: onFocusProp }) => {
  const [focused, setFocused] = pfU(false);
  const [draft, setDraft]     = pfU(String(value || 0));
  pfE(() => { if (!focused) setDraft(String(value || 0)); }, [value, focused]);

  return (
    <div style={{
      display: "inline-flex", alignItems: "center",
      padding: "2px 6px", margin: "-2px -6px", borderRadius: 5,
      background: focused ? "var(--helm-white)" : "transparent",
      border: `1px solid ${focused ? "var(--helm-dark-green)" : "transparent"}`,
      transition: "background .12s, border-color .12s",
    }}
      onMouseEnter={e => { if (!focused) e.currentTarget.style.background = "var(--helm-ecru)"; }}
      onMouseLeave={e => { if (!focused) e.currentTarget.style.background = "transparent"; }}>
      <span style={{ fontFamily: "var(--font-sans)", fontSize, fontWeight,
        opacity: focused ? 0.6 : 1, marginRight: 0,
        color: "var(--helm-dark-green)",
      }}>$</span>
      <input
        value={focused ? draft : Number(value || 0).toLocaleString()}
        onChange={e => setDraft(e.target.value.replace(/[^\d]/g, ""))}
        onFocus={() => { setDraft(String(value || 0)); setFocused(true); onFocusProp && onFocusProp(); }}
        onBlur={() => {
          const n = parseInt(draft || "0", 10);
          if (n !== value) onChange(n);
          setFocused(false);
        }}
        onKeyDown={e => {
          if (e.key === "Enter") e.target.blur();
          if (e.key === "Escape") { setDraft(String(value || 0)); e.target.blur(); }
        }}
        style={{
          minWidth: 0, width: "auto", maxWidth: 110,
          background: "transparent", border: "none", outline: "none", padding: 0,
          fontFamily: "var(--font-sans)", fontSize, fontWeight,
          color: "var(--helm-dark-green)",
          fontVariantNumeric: "tabular-nums",
          letterSpacing: "-0.01em",
          textAlign: align,
        }}
        // Auto-size input to its content
        size={Math.max(1, Math.min(10, (focused ? draft : Number(value || 0).toLocaleString()).length))}
      />
    </div>
  );
};
const PFAgeField = ({ value, onChange }) => (
  <div style={{ display: "flex", alignItems: "baseline", gap: 4, flexShrink: 0 }}>
    <input
      type="text" inputMode="numeric"
      value={value || ""}
      onChange={e => onChange(e.target.value.replace(/[^\d]/g, "").slice(0, 3))}
      placeholder="Age"
      onFocus={e => {
        e.target.style.background = "var(--helm-white)";
        e.target.style.borderColor = "var(--helm-dark-green)";
      }}
      onBlur={e => {
        e.target.style.background = "transparent";
        e.target.style.borderColor = "transparent";
      }}
      onMouseEnter={e => {
        if (document.activeElement !== e.target)
          e.target.style.background = "var(--helm-ecru)";
      }}
      onMouseLeave={e => {
        if (document.activeElement !== e.target)
          e.target.style.background = "transparent";
      }}
      style={{
        width: 50, background: "transparent",
        border: "1px solid transparent", borderRadius: 5,
        outline: "none", padding: "2px 5px", margin: "-2px -5px",
        fontFamily: "var(--font-sans)",
        fontSize: 17, fontWeight: 700,
        color: "var(--helm-dark-green)",
        letterSpacing: "-0.01em",
        fontVariantNumeric: "tabular-nums",
        textAlign: "left",
        transition: "background .12s, border-color .12s",
      }}
    />
    <span style={{ fontSize: 11, opacity: 0.55, fontWeight: 500 }}>yrs</span>
  </div>
);
const PFEditableText = ({ value, onChange, fontSize, fontWeight = 500, opacity = 1,
  placeholder, lineHeight, multiline, wrap, autoFocus, monospaceNum, color, family }) => {
  const ref = pfR(null);
  // Auto-grow textarea so wrapping titles aren't clipped to one row.
  const autoSize = () => {
    const el = ref.current;
    if (!el || el.tagName !== "TEXTAREA") return;
    el.style.height = "auto";
    el.style.height = el.scrollHeight + "px";
  };
  pfE(() => {
    if (autoFocus && ref.current) {
      ref.current.focus();
      // Move cursor to end
      const v = ref.current.value;
      ref.current.setSelectionRange(v.length, v.length);
    }
  }, [autoFocus]);
  pfE(() => { autoSize(); }, [value, wrap]);
  const common = {
    value, onChange: (e) => { onChange(e.target.value); if (wrap) autoSize(); },
    placeholder, ref,
    style: {
      background: "transparent",
      border: "1px solid transparent",
      borderRadius: 5,
      outline: "none",
      padding: "2px 5px",
      margin: "-2px -5px",
      fontFamily: family || "var(--font-sans)",
      fontSize, fontWeight,
      opacity,
      lineHeight: lineHeight || "1.25",
      color: color || "var(--helm-dark-green)",
      letterSpacing: "-0.01em",
      fontVariantNumeric: monospaceNum ? "tabular-nums" : "normal",
      width: "100%",
      minWidth: 0,
      boxSizing: "border-box",
      resize: (multiline || wrap) ? "none" : "initial",
      cursor: "text",
      transition: "background .12s, border-color .12s",
      ...(wrap ? {
        whiteSpace: "pre-wrap",
        wordBreak: "break-word",
        overflow: "hidden",
        display: "block",
      } : null),
    },
    onFocus: (e) => {
      e.target.style.background = "var(--helm-white)";
      e.target.style.borderColor = "var(--helm-dark-green)";
    },
    onBlur: (e) => {
      e.target.style.background = "transparent";
      e.target.style.borderColor = "transparent";
    },
    onMouseEnter: (e) => {
      if (document.activeElement !== e.target) {
        e.target.style.background = "var(--helm-ecru)";
      }
    },
    onMouseLeave: (e) => {
      if (document.activeElement !== e.target) {
        e.target.style.background = "transparent";
      }
    },
  };
  return multiline
    ? <textarea {...common} rows={2} />
    : wrap
    ? <textarea {...common} rows={1}
        onKeyDown={(e) => { if (e.key === "Enter") e.preventDefault(); }} />
    : <input type="text" {...common} />;
};

const PFFactTile = ({ fact, autoFocusLabel, exiting, onChange, onDelete }) => {
  const [hover, setHover] = pfU(false);
  const tag = fact.confidence === "hard"     ? { label: "Synced",   bg: "var(--helm-soft-chartreuse)", fg: "var(--helm-white)" }
           : fact.confidence === "estimate" ? { label: "Estimate", bg: "var(--helm-orange)",          fg: "var(--helm-dark-green)" }
           :                                  { label: "You said", bg: "var(--helm-ecru)",            fg: "var(--helm-dark-green)" };
  const cycleConfidence = () => {
    const i = PF_CONF_CYCLE.indexOf(fact.confidence);
    onChange({ confidence: PF_CONF_CYCLE[(i + 1) % PF_CONF_CYCLE.length] });
  };
  const cycleIcon = () => {
    const i = PF_ICON_CYCLE.indexOf(fact.icon);
    onChange({ icon: PF_ICON_CYCLE[(i + 1) % PF_ICON_CYCLE.length] });
  };

  return (
    <div
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        position: "relative", padding: "14px 16px 14px 20px",
        background: "var(--helm-white)", borderRadius: 12,
        border: "1px solid var(--helm-dark-ecru)",
        display: "flex", flexDirection: "column", gap: 4,
        minHeight: 110,
        // Only clip during the exit animation — otherwise the combo
        // dropdown (and any future popovers anchored inside) gets cut
        // off at the tile's edge.
        overflow: exiting ? "hidden" : "visible",
        animation: exiting ? "pfTileExit .32s ease-in forwards" : undefined,
        pointerEvents: exiting ? "none" : "auto",
        transformOrigin: "center",
      }}>
      {/* Left-edge confidence stripe — wrapped so it clips to the rounded corner */}
      <div style={{
        position: "absolute", top: 0, left: 0, bottom: 0, width: 4,
        borderTopLeftRadius: 12, borderBottomLeftRadius: 12,
        overflow: "hidden", pointerEvents: "none",
      }}>
        <div style={{ width: "100%", height: "100%", background: tag.bg }} />
      </div>

      {/* Meta row: icon (left) + caption (right). Title sits below at full width. */}
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, marginBottom: 2 }}>
        <button onClick={cycleIcon} title="Cycle icon"
          style={{
            width: 26, height: 26, borderRadius: 7,
            background: "var(--helm-ecru)", border: "none",
            display: "flex", alignItems: "center", justifyContent: "center",
            flexShrink: 0, cursor: "pointer", color: "var(--helm-dark-green)",
            padding: 0,
          }}>
          <PGIcon name={fact.icon} size={13} stroke={1.6} />
        </button>
        <button onClick={cycleConfidence} title="Click to change source"
          style={{
            background: "transparent", border: "none", padding: 0,
            cursor: "pointer",
            color: "var(--helm-dark-green)",
            fontFamily: "var(--font-sans)",
            fontSize: 8.5, fontWeight: 700,
            letterSpacing: "0.08em", textTransform: "uppercase",
            opacity: 0.55,
          }}>{tag.label}</button>
      </div>

      {/* Title — full card width, wraps naturally */}
      <div style={{ marginBottom: 2 }}>
        <PFEditableText
          value={fact.label}
          onChange={v => onChange({ label: v })}
          fontSize={12} fontWeight={600} opacity={0.85}
          placeholder="New item"
          autoFocus={autoFocusLabel}
          wrap
        />
      </div>

      {/* value — split for People, single field otherwise */}
      {fact.category === "People" ? (
        (() => {
          const { rel, age } = pfParsePeopleValue(fact);
          // Always write BOTH explicit fields so the parser doesn't half-flip
          // into the explicit-field branch and forget whichever side wasn't
          // touched yet (e.g. editing age would clear the relationship).
          const commitRel = (v) => onChange({
            relationship: v,
            age,
            value: pfFormatPeopleValue(v, age),
          });
          const commitAge = (v) => onChange({
            relationship: rel,
            age: v,
            value: pfFormatPeopleValue(rel, v),
          });
          return (
            <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
              <PFRelationshipField value={rel} onChange={commitRel} />
              <PFAgeField value={age} onChange={commitAge} />
            </div>
          );
        })()
      ) : (
        <PFEditableText
          value={fact.value}
          onChange={v => onChange({ value: v })}
          fontSize={22} fontWeight={700} lineHeight="1.1"
          placeholder="Value"
          monospaceNum
        />
      )}

      {/* sub — full-width, no chip squeezing it */}
      <PFEditableText
        value={fact.sub}
        onChange={v => onChange({ sub: v })}
        fontSize={11} opacity={0.65} lineHeight="1.35"
        placeholder="Add details"
      />

      {/* Hover delete — bottom-right corner, in line with the sub text */}
      {hover && (
        <button onClick={onDelete} aria-label="Delete fact"
          style={{
            position: "absolute", bottom: 8, right: 8,
            width: 20, height: 20, borderRadius: 999,
            background: "var(--helm-white)",
            border: "1px solid var(--helm-dark-ecru)",
            cursor: "pointer", padding: 0,
            display: "flex", alignItems: "center", justifyContent: "center",
            color: "var(--helm-dark-grey)",
            fontSize: 13, lineHeight: 1, fontWeight: 600,
            boxShadow: "0 1px 3px rgba(0,0,0,0.06)",
          }}>×</button>
      )}
    </div>
  );
};

const PFAddFactTile = ({ category, onAdd }) => (
  <button onClick={onAdd} style={{
    background: "transparent",
    border: "1.5px dashed var(--helm-dark-ecru)", borderRadius: 12,
    color: "var(--helm-dark-grey)",
    fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 600,
    cursor: "pointer", display: "flex", flexDirection: "column",
    alignItems: "center", justifyContent: "center", gap: 6,
    padding: 14, minHeight: 110,
    transition: "border-color .15s, color .15s, background .15s",
  }}
    onMouseEnter={e => {
      e.currentTarget.style.borderColor = "var(--helm-dark-green)";
      e.currentTarget.style.color = "var(--helm-dark-green)";
      e.currentTarget.style.background = "var(--helm-ecru)";
    }}
    onMouseLeave={e => {
      e.currentTarget.style.borderColor = "var(--helm-dark-ecru)";
      e.currentTarget.style.color = "var(--helm-dark-grey)";
      e.currentTarget.style.background = "transparent";
    }}>
    <PGIcon name="plus" size={16} stroke={1.8} />
    Add to {category.toLowerCase()}
  </button>
);

// ════════════════════════════════════════════════════════════════════
// SCREEN 2 — Path choice
// ════════════════════════════════════════════════════════════════════
const PathChoiceScreen = ({ density, onStepClick, onPick, onBack }) => {
  const pad = density === "compact" ? "32px 56px" : "44px 64px";
  return (
    <main style={{ flex: 1, padding: pad, background: "var(--helm-white)", overflow: "auto" }}>
      <div style={{ maxWidth: 920 }}>
        <StepBreadcrumb steps={PF_STEPS} current={PF_CURRENT_STEP} onStepClick={onStepClick} />

        <div style={{ margin: "32px 0 14px" }}>
          <h1 style={{
            fontFamily: "var(--font-serif)", fontSize: 48, lineHeight: "52px",
            fontWeight: 600, letterSpacing: "-0.015em", margin: 0, textWrap: "balance",
          }}>
            How should we build your plan?
          </h1>
        </div>
        <p style={{
          fontFamily: "var(--font-sans)", fontSize: 18, lineHeight: "28px",
          opacity: 0.8, margin: "0 0 28px", maxWidth: 720,
        }}>
          Either way, you'll end up at the same place — a payout plan you can edit anytime.
          Two ways to get there.
        </p>

        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
          <PFPathCard
            kind="draft"
            iconBg="var(--helm-lemon)"
            icon="sparkle"
            tag="Recommended"
            title="Build me a draft."
            blurb={<>I'll write the full plan from what we just confirmed — about <b>{PG_EXPENSES.length} line items</b> across your {pfDollar(PG_GRAND_TOTAL)}, sourced and explained. You review, swap, accept.</>}
            bullets={["About 5 minutes to review", "Every number sourced (NFDA, College Board, your accounts)", "Swap or skip anything that doesn't fit"]}
            cta="Build my draft →"
            onClick={() => onPick("draft")}
          />
          <PFPathCard
            kind="guide"
            iconBg="var(--helm-soft-chartreuse)"
            icon="list"
            tag="More hands-on"
            title="Walk me through it."
            blurb={<>We'll do this together, one decision at a time. I'll suggest amounts grounded in your data, and you say yes, no, or different.</>}
            bullets={["About 15–20 minutes, ~21 questions", "Better when you have strong opinions", "Save and resume anytime"]}
            cta="Start the walkthrough →"
            onClick={() => onPick("guide")}
          />
        </div>

        <div style={{ marginTop: 24, display: "flex", gap: 12, alignItems: "center" }}>
          <Button variant="secondary" onClick={onBack}>← Back to facts</Button>
          <span style={{ fontSize: 12, opacity: 0.55 }}>
            You can switch between the two anytime from inside the plan.
          </span>
        </div>
      </div>
    </main>
  );
};

const PFPathCard = ({ iconBg, icon, tag, title, blurb, bullets, cta, onClick }) => (
  <button onClick={onClick} style={{
    display: "flex", flexDirection: "column", gap: 14,
    padding: 28, textAlign: "left",
    background: "var(--helm-white)",
    border: "1px solid var(--helm-dark-green)", borderRadius: 20,
    boxShadow: "var(--shadow-card)",
    cursor: "pointer", transition: "transform .14s ease, box-shadow .14s ease",
    fontFamily: "var(--font-sans)", color: "var(--helm-dark-green)",
  }}
    onMouseEnter={e => { e.currentTarget.style.transform = "translate(-2px, -2px)";
      e.currentTarget.style.boxShadow = "6px 6px 0 0 var(--helm-dark-green)"; }}
    onMouseLeave={e => { e.currentTarget.style.transform = "none";
      e.currentTarget.style.boxShadow = "var(--shadow-card)"; }}>
    <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
      <div style={{
        width: 44, height: 44, borderRadius: 12, background: iconBg,
        display: "flex", alignItems: "center", justifyContent: "center",
      }}>
        <PGIcon name={icon} size={22} stroke={1.8} />
      </div>
      <span style={{
        padding: "4px 10px", borderRadius: 999, background: "var(--helm-ecru)",
        fontSize: 10, fontWeight: 700, letterSpacing: "0.08em", textTransform: "uppercase",
      }}>{tag}</span>
    </div>
    <h2 style={{ fontFamily: "var(--font-serif)", fontSize: 28, lineHeight: "32px",
      fontWeight: 600, letterSpacing: "-0.01em", margin: 0 }}>{title}</h2>
    <p style={{ fontSize: 14, lineHeight: "22px", opacity: 0.78, margin: 0 }}>{blurb}</p>
    <ul style={{ margin: 0, padding: 0, listStyle: "none",
      display: "flex", flexDirection: "column", gap: 8 }}>
      {bullets.map((b, i) => (
        <li key={i} style={{ display: "flex", gap: 8, fontSize: 13, lineHeight: "20px" }}>
          <PGIcon name="check" size={14} stroke={2.2} style={{ marginTop: 2, opacity: 0.55, flexShrink: 0 }} />
          <span>{b}</span>
        </li>
      ))}
    </ul>
    <div style={{ marginTop: 6, display: "inline-flex", alignItems: "center", gap: 8,
      padding: "12px 18px", borderRadius: 999,
      background: "var(--helm-chartreuse-2)", color: "var(--helm-white)",
      fontSize: 14, fontWeight: 700, alignSelf: "flex-start",
    }}>{cta}</div>
  </button>
);

// ════════════════════════════════════════════════════════════════════
// SCREEN 3 — Payout plan (tabs + Plan/Flow/Power)
// ════════════════════════════════════════════════════════════════════
const PayoutPlanScreen = ({
  density, onStepClick, path, onBack, onAdvance,
  expenses, setExpenses, guideAnswers, onAdd,
  editingExpId, setEditingExpId, reviewingFlagged, setReviewingFlagged,
}) => {
  const [tab, setTab] = pfU(() => (window.PG_PEOPLE && window.PG_PEOPLE[0]?.id) || null);
  const [view, setView] = pfU("plan");
  const [flashId, setFlashId] = pfU(null);
  const [exitingId, setExitingId] = pfU(null);
  const exitTimerRef = pfR(null);
  const pad = density === "compact" ? "20px 56px 32px" : "28px 64px 36px";

  // Keep `tab` pointing at a real person — repick if PG_PEOPLE changes
  // (e.g. Suzie went back and edited beneficiaries).
  pfE(() => {
    if (!PG_PEOPLE.length) return;
    if (!PG_PEOPLE.find(p => p.id === tab)) setTab(PG_PEOPLE[0].id);
  }, [PG_PEOPLE.length]);

  const person = PG_PEOPLE.find(p => p.id === tab) || PG_PEOPLE[0];
  const isDraft = path === "draft";
  const editingExp = editingExpId ? expenses.find(e => e.id === editingExpId) : null;

  // List of flagged expenses (drives the "Walk me through flagged" review mode)
  const flaggedExpenses = expenses.filter(e => e.flagged);
  const flaggedIdx = editingExp && reviewingFlagged
    ? flaggedExpenses.findIndex(e => e.id === editingExp.id)
    : -1;

  // Editing helpers (lifted from PFPlanRow / PFPowerView)
  // When horizon changes, flash the row so the move reads as motion.
  const updateExpense = (id, patch) => {
    setExpenses(xs => xs.map(e => e.id === id ? { ...e, ...patch } : e));
    if ('horizon' in patch) {
      setFlashId(id);
      setTimeout(() => setFlashId(curr => curr === id ? null : curr), 700);
    }
  };
  // Animate the row out, then commit the delete. ~280ms matches the keyframe.
  const deleteExpense = (id) => {
    if (exitingId) return;
    setExitingId(id);
    if (exitTimerRef.current) clearTimeout(exitTimerRef.current);
    exitTimerRef.current = setTimeout(() => {
      setExpenses(xs => xs.filter(e => e.id !== id));
      setExitingId(null);
    }, 280);
  };

  // Auto-flash newly added rows (e.g. from a guide answer) so they
  // visibly arrive in the main panel rather than just appearing.
  const prevIdsRef = pfR(new Set(expenses.map(e => e.id)));
  pfE(() => {
    const newIds = expenses.map(e => e.id).filter(id => !prevIdsRef.current.has(id));
    prevIdsRef.current = new Set(expenses.map(e => e.id));
    if (newIds.length === 0) return;
    const last = newIds[newIds.length - 1];
    setFlashId(last);
    const t = setTimeout(() => setFlashId(curr => curr === last ? null : curr), 700);
    return () => clearTimeout(t);
  }, [expenses]);

  // Current guide question (the next un-answered one)
  const currentExp = (!isDraft && guideAnswers && guideAnswers.length < PF_GUIDE_ORDER.length)
    ? PG_EXPENSES.find(e => e.id === PF_GUIDE_ORDER[guideAnswers.length])
    : null;
  const guideDoneCount = guideAnswers ? guideAnswers.length : 0;

  // In walk-through mode, sync the active tab to the beneficiary we're
  // currently asking about — so the main panel mirrors the conversation.
  pfE(() => {
    if (!isDraft && currentExp && currentExp.to !== tab) {
      setTab(currentExp.to);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isDraft, currentExp?.to]);

  // In flagged-review mode, also sync the tab to whichever beneficiary
  // the currently-open flagged expense belongs to.
  pfE(() => {
    if (reviewingFlagged && editingExp && editingExp.to !== tab) {
      setTab(editingExp.to);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [reviewingFlagged, editingExp?.to]);

  const totalAllocated = expenses.reduce((s, e) => s + (e.amount || 0), 0);

  // ── Smooth "anchored-to-bottom" behavior for walk-through commits ──
  // The user wants the scroll position to settle at the bottom BEFORE the
  // new row appears, so the row reads as pushing prior content up rather
  // than the page chasing the row afterwards. We do this in two phases:
  //
  //   1. PFGuideQuestionCard fires a `pf-pre-commit` window event the
  //      instant the user clicks confirm. We smooth-scroll the main
  //      canvas to its CURRENT bottom over the next ~250ms while the
  //      card runs its exit animation in parallel.
  //   2. When the row finally commits (~320ms later), a layout effect
  //      synchronously pins scrollTop to the NEW bottom — before paint
  //      — so the row's height gets absorbed as a tiny upward shift of
  //      the prior content instead of a visible scroll catching up.
  const mainScrollRef = pfR(null);
  const lastExpCountRef = pfR(expenses.length);

  pfE(() => {
    if (isDraft) return;
    const handler = () => {
      const el = mainScrollRef.current;
      if (!el) return;
      el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
    };
    window.addEventListener("pf-pre-commit", handler);
    return () => window.removeEventListener("pf-pre-commit", handler);
  }, [isDraft]);

  React.useLayoutEffect(() => {
    if (isDraft) return;
    const prev = lastExpCountRef.current;
    const cur = expenses.length;
    lastExpCountRef.current = cur;
    if (cur > prev) {
      const el = mainScrollRef.current;
      if (el) el.scrollTop = el.scrollHeight;
    }
  }, [isDraft, expenses.length]);

  return (
    <main ref={mainScrollRef} style={{ flex: 1, padding: pad, background: "var(--helm-white)",
      overflow: "auto", display: "flex", flexDirection: "column" }}>
      <div style={{ maxWidth: 920, width: "100%", display: "flex", flexDirection: "column", gap: 18 }}>
        <StepBreadcrumb steps={PF_STEPS} current={PF_CURRENT_STEP} onStepClick={onStepClick} />

        {/* Header */}
        <div style={{ marginTop: 14, display: "flex", alignItems: "flex-end",
          justifyContent: "space-between", gap: 24 }}>
          <div>
            <div style={{ fontSize: 12, fontWeight: 500, opacity: 0.6,
              textTransform: "uppercase", letterSpacing: "0.08em", marginBottom: 8 }}>
              {isDraft ? "Your draft plan" : "Building together"}
            </div>
            <h1 style={{ fontFamily: "var(--font-serif)", fontSize: 44, lineHeight: "48px",
              fontWeight: 600, letterSpacing: "-0.015em", margin: 0, textWrap: "balance" }}>
              {isDraft
                ? <>Your {pfDollar(PG_GRAND_TOTAL)}, mapped to the family.</>
                : <>Building your plan, one piece at a time.</>}
            </h1>
          </div>
          <div style={{ textAlign: "right", flexShrink: 0 }}>
            <div style={{ fontSize: 11, fontWeight: 600, opacity: 0.6,
              textTransform: "uppercase", letterSpacing: "0.08em" }}>
              {isDraft ? "Drafted" : "Allocated so far"}
            </div>
            <div style={{ fontFamily: "var(--font-sans)", fontSize: 28, fontWeight: 700,
              letterSpacing: "-0.02em", fontVariantNumeric: "tabular-nums" }}>
              {pfDollar(totalAllocated)}
              <span style={{ opacity: 0.45, fontSize: 16, fontWeight: 600 }}> / {pfDollar(PG_GRAND_TOTAL)}</span>
            </div>
          </div>
        </div>

        {/* Tabs */}
        <PFPersonTabs tab={tab} setTab={setTab} expenses={expenses} />

        {/* View toggle + active person summary */}
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between",
          gap: 16, padding: "4px 0" }}>
          <div style={{ fontSize: 13, opacity: 0.7 }}>
            {(() => {
              const p = PG_PEOPLE.find(x => x.id === tab);
              if (!p) return "";
              const rel = p.relationship || (p.kind === "spouse" ? "Spouse" : p.kind === "child" ? "Child" : "Beneficiary");
              const ageBit = p.age != null ? `, ${p.age}` : "";
              if (p.kind === "spouse") return `${rel}${ageBit} · everything that keeps the household running.`;
              if (p.kind === "child")  return `${rel}${ageBit} · the years between now and their launch.`;
              return `${rel}${ageBit}`;
            })()}
          </div>
          <PGViewToggle value={view} onChange={setView} options={[
            { value: "plan",  label: "Plan",  icon: "list" },
            { value: "flow",  label: "Flow",  icon: "sparkle" },
          ]} />
        </div>

        {/* Active view */}
        <div style={{ minHeight: 460 }}>
          {view === "plan"  && <PFPlanView  person={person} expenses={expenses} isDraft={isDraft}
            currentExp={currentExp} onUpdate={updateExpense} onDelete={deleteExpense}
            onAdd={() => onAdd && onAdd(tab)} flashId={flashId} exitingId={exitingId}
            onOpenDetails={id => setEditingExpId(id)} />}
          {view === "flow"  && <PFFlowView  person={person} expenses={expenses} isDraft={isDraft}
            doneCount={guideDoneCount}
            onPurposeClick={id => setEditingExpId(id)} />}
        </div>

        {/* Footer actions */}
        <div style={{ marginTop: 8, display: "flex", gap: 12, alignItems: "center" }}>
          <Button variant="primary" onClick={onAdvance}
            disabled={!isDraft && guideDoneCount < Math.min(PG_EXPENSES.length, Math.max(1, Math.ceil(PG_EXPENSES.length / 2)))}>
            {isDraft || guideDoneCount >= PF_GUIDE_ORDER.length
              ? "Save plan & continue →"
              : `Continue building (${guideDoneCount} of ${PG_EXPENSES.length} done)`}
          </Button>
          <Button variant="secondary" onClick={onBack}>← Switch approach</Button>
        </div>
      </div>

      {/* Click-to-edit modal for purpose nodes in the Sankey */}
      {editingExp && (
        <PFPurposeModal
          exp={editingExp}
          onChange={patch => updateExpense(editingExpId, patch)}
          onDelete={() => deleteExpense(editingExpId)}
          onClose={() => { setEditingExpId(null); setReviewingFlagged(false); }}
          reviewing={reviewingFlagged}
          reviewIdx={flaggedIdx}
          reviewTotal={flaggedExpenses.length}
          onNextFlagged={() => {
            if (!reviewingFlagged) return;
            const next = flaggedExpenses[flaggedIdx + 1];
            if (next) setEditingExpId(next.id);
            else { setEditingExpId(null); setReviewingFlagged(false); }
          }}
          onPrevFlagged={() => {
            if (!reviewingFlagged || flaggedIdx <= 0) return;
            setEditingExpId(flaggedExpenses[flaggedIdx - 1].id);
          }}
        />
      )}
    </main>
  );
};

// ── Person tabs strip ──────────────────────────────────────────────
const PFPersonTabs = ({ tab, setTab, expenses }) => (
  <div style={{
    display: "flex", gap: 0, borderBottom: "1px solid var(--helm-dark-ecru)",
  }}>
    {PG_PEOPLE.map(p => {
      const isActive = tab === p.id;
      const mine = expenses.filter(e => e.to === p.id);
      const allocated = mine.reduce((s, e) => s + e.amount, 0);
      const tint = PG_PERSON_TINTS[p.id];
      return (
        <button key={p.id} onClick={() => setTab(p.id)} style={{
          flex: 1,
          padding: "14px 18px 16px",
          background: isActive ? "var(--helm-ecru)" : "transparent",
          borderTop: isActive ? `3px solid ${tint.deep}` : "3px solid transparent",
          borderLeft: "none", borderRight: "none", borderBottom: "none",
          cursor: "pointer",
          display: "flex", alignItems: "center", gap: 12,
          fontFamily: "var(--font-sans)", color: "var(--helm-dark-green)",
          opacity: isActive ? 1 : 0.62,
          borderRadius: isActive ? "10px 10px 0 0" : 0,
          textAlign: "left",
          transition: "background .15s, opacity .15s",
        }}>
          <PGAvatar id={p.id} size={36} ring={isActive ? tint.deep : null} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 14, fontWeight: 700 }}>{p.name.split(" ")[0]}</div>
            <div style={{ fontSize: 11, opacity: 0.7 }}>
              {p.relationship} · age {p.age}
            </div>
          </div>
          <div style={{ textAlign: "right" }}>
            <div style={{ fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 700,
              letterSpacing: "-0.01em", fontVariantNumeric: "tabular-nums" }}>
              {pfDollar(allocated)}
            </div>
            <div style={{ fontSize: 10, opacity: 0.6, fontVariantNumeric: "tabular-nums" }}>
              of {pfDollar(PG_TOTAL_BY[p.id])}
            </div>
          </div>
        </button>
      );
    })}
  </div>
);

// ── PLAN VIEW — V5 card structure, single beneficiary, expanded ─────
const PFPlanView = ({ person, expenses, isDraft, currentExp, onUpdate, onDelete, onAdd, flashId, exitingId, onOpenDetails }) => {
  const items = expenses.filter(e => e.to === person.id);
  const allocated = items.reduce((s, e) => s + e.amount, 0);
  const share = PG_TOTAL_BY[person.id];
  const over = allocated > share;
  const shortfall = Math.max(0, allocated - share);
  const growth = Math.max(0, share - allocated);
  const tint = PG_PERSON_TINTS[person.id];
  const personFacts = pfFactsForPerson(person.id);

  // Items grouped by horizon for readability. Items without a horizon
  // (typically user-added blanks) get their own "Not yet placed" group
  // at the top so they're easy to find and complete.
  const unassignedItems = items.filter(e => !e.horizon);
  const byHorizon = [
    ...(unassignedItems.length > 0
      ? [{ horizon: { id: "__unassigned", label: "Not yet placed",
            desc: "Pick a horizon to slot these into the plan." }, items: unassignedItems }]
      : []),
    ...PG_HORIZONS.map(h => ({
      horizon: h,
      items: items.filter(e => e.horizon === h.id),
    })).filter(g => g.items.length > 0),
  ];

  // For guide mode: surface the next question as a ghost row in the right horizon
  const ghostHorizon = !isDraft && currentExp?.to === person.id ? currentExp.horizon : null;

  return (
    <div style={{
      display: "flex", flexDirection: "column",
      background: "var(--helm-white)",
      border: "1px solid var(--helm-dark-green)", borderRadius: 20,
      boxShadow: "var(--shadow-card)",
      overflow: "hidden",
    }}>
      {/* Card header */}
      <div style={{
        padding: "20px 24px",
        background: tint.base,
        display: "flex", alignItems: "center", gap: 16,
      }}>
        <PGAvatar id={person.id} size={56} />
        <div style={{ flex: 1 }}>
          <div style={{ fontFamily: "var(--font-serif)", fontSize: 22,
            fontWeight: 600, letterSpacing: "-0.01em" }}>
            For {person.name}
          </div>
          <div style={{ fontSize: 13, opacity: 0.75 }}>
            {person.relationship} · age {person.age}
          </div>
        </div>
        <div style={{ textAlign: "right" }}>
          <div style={{ fontFamily: "var(--font-sans)", fontSize: 24, fontWeight: 700,
            letterSpacing: "-0.02em", fontVariantNumeric: "tabular-nums" }}>
            {pfDollar(share)}
          </div>
          <div style={{ fontSize: 11, opacity: 0.75 }}>their share from your policies</div>
        </div>
      </div>

      {/* Mini-facts strip */}
      <div style={{
        padding: "12px 24px", background: "var(--helm-ecru)",
        borderBottom: "1px solid var(--helm-dark-ecru)",
        display: "flex", gap: 14, flexWrap: "wrap",
      }}>
        {personFacts.map(f => (
          <div key={f.id} style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <PGIcon name={f.icon} size={14} stroke={1.6} style={{ opacity: 0.55 }} />
            <span style={{ fontSize: 12 }}>
              <b>{f.label}</b> <span style={{ opacity: 0.7 }}>{f.value}</span>
            </span>
          </div>
        ))}
      </div>

      {/* Shortfall banner — only when over-allocated */}
      {over && (
        <div style={{ padding: "12px 24px 0" }}>
          <PFShortfallBanner person={person} allocated={allocated} share={share} />
        </div>
      )}

      {/* Add line — top of allocation area so new items land near where the user clicked */}
      <div style={{ padding: "12px 24px 0", display: "flex" }}>
        <button onClick={onAdd} style={{
          marginLeft: "auto",
          padding: "8px 14px",
          background: "transparent",
          border: "1.5px dashed var(--helm-dark-ecru)",
          borderRadius: 999,
          fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 600,
          color: "var(--helm-dark-grey)", cursor: "pointer",
          display: "inline-flex", alignItems: "center", gap: 6,
          transition: "border-color .15s, color .15s, background .15s",
        }}
          onMouseEnter={e => {
            e.currentTarget.style.borderColor = "var(--helm-dark-green)";
            e.currentTarget.style.color = "var(--helm-dark-green)";
            e.currentTarget.style.background = "var(--helm-ecru)";
          }}
          onMouseLeave={e => {
            e.currentTarget.style.borderColor = "var(--helm-dark-ecru)";
            e.currentTarget.style.color = "var(--helm-dark-grey)";
            e.currentTarget.style.background = "transparent";
          }}>
          <PGIcon name="plus" size={12} stroke={2} /> Add a line for {person.name.split(" ")[0]}
        </button>
      </div>

      {/* Allocation rows grouped by horizon */}
      <div style={{ padding: "8px 24px 18px" }}>
        {byHorizon.map(({ horizon, items: rows }) => (
          <div key={horizon.id} style={{ marginTop: 12 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 4 }}>
              {horizon.id === "__unassigned" ? (
                <span style={{
                  display: "inline-flex", alignItems: "center", height: 18,
                  padding: "0 8px", borderRadius: 999,
                  background: "transparent",
                  border: "1px dashed var(--helm-dark-ecru)",
                  color: "var(--helm-dark-grey)",
                  fontFamily: "var(--font-sans)", fontSize: 10,
                  fontWeight: 600, letterSpacing: "0.04em", textTransform: "uppercase",
                }}>To place</span>
              ) : (
                <PGHorizonChip horizon={horizon.id} />
              )}
              <span style={{ fontSize: 12, fontWeight: 600, opacity: 0.8 }}>{horizon.label}</span>
              <span style={{ fontSize: 11, opacity: 0.5 }}>· {horizon.desc}</span>
            </div>
            {rows.map(e => <PFPlanRow key={e.id} exp={e}
              flash={e.id === flashId}
              exiting={e.id === exitingId}
              onChange={p => onUpdate(e.id, p)}
              onDelete={() => onDelete(e.id)}
              onOpenDetails={() => onOpenDetails && onOpenDetails(e.id)} />)}
            {ghostHorizon === horizon.id && <PFGhostRow exp={currentExp} />}
          </div>
        ))}
        {!isDraft && byHorizon.length === 0 && (
          <div style={{ padding: "24px 0", textAlign: "center", opacity: 0.55 }}>
            <PGIcon name="sparkle" size={20} stroke={1.6} />
            <div style={{ marginTop: 6, fontSize: 13, fontStyle: "italic" }}>
              Nothing for {person.name.split(" ")[0]} yet — answer the question on the right and it'll start filling in here.
            </div>
            {ghostHorizon === null && currentExp && (
              <div style={{ marginTop: 12, opacity: 0.7, fontSize: 12 }}>
                Up next for someone else: <b>{currentExp.label}</b>
              </div>
            )}
          </div>
        )}
        {!isDraft && byHorizon.length > 0 && ghostHorizon === null && currentExp && (
          <div style={{ marginTop: 14, padding: "10px 12px", borderRadius: 10,
            background: "var(--helm-ecru)", fontSize: 12, opacity: 0.7,
            display: "flex", alignItems: "center", gap: 8 }}>
            <PGIcon name="sparkle" size={12} stroke={2} />
            <span>Currently asking about <b>{currentExp.label}</b> for {currentExp.to} — answer on the right.</span>
          </div>
        )}
      </div>

      {/* Footer — allocation bar + growth/shortfall pill */}
      <div style={{ padding: "16px 24px", background: "var(--helm-ecru)",
        borderTop: "1px solid var(--helm-dark-ecru)" }}>
        <div style={{ display: "flex", justifyContent: "space-between",
          alignItems: "baseline", marginBottom: 6 }}>
          <span style={{ fontSize: 11, fontWeight: 700, letterSpacing: "0.08em",
            textTransform: "uppercase", opacity: 0.55 }}>Allocated</span>
          <span style={{ fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 700,
            fontVariantNumeric: "tabular-nums",
            color: over ? "var(--helm-dark-orange)" : "var(--helm-dark-green)" }}>
            {pfDollar(allocated)} <span style={{ opacity: 0.55, fontWeight: 600,
              color: "var(--helm-dark-green)" }}>of {pfDollar(share)}</span>
          </span>
        </div>
        <PGAllocBar allocated={Math.min(allocated, share)} share={share}
          color={over ? "var(--helm-dark-orange)" : tint.deep} height={6} />
        <div style={{ marginTop: 12 }}>
          {over
            ? <PFShortfallPill amount={shortfall} person={person.name.split(" ")[0]}
                onExploreCoverage={() => alert("Helm would walk you through adding coverage here — flow lives in a separate skill.")} />
            : <PGGrowthPill amount={growth} person={person.name.split(" ")[0]} size="md" />}
        </div>
      </div>
    </div>
  );
};

// ── Shortfall warning ──────────────────────────────────────────────
// Two surfaces: a prominent banner at the top of the Plan card, and a
// pill at the bottom that replaces the "Invest for growth" pill when a
// beneficiary's allocation exceeds their share.

const PFShortfallBanner = ({ person, allocated, share, compact }) => {
  const [showModal, setShowModal] = pfU(false);
  const shortfall = allocated - share;
  return (
    <>
      <div style={{
        display: "flex", alignItems: compact ? "center" : "flex-start", gap: 12,
        padding: compact ? "10px 14px" : "14px 16px",
        background: "var(--helm-orange)",
        borderRadius: 12,
        border: "1px solid var(--helm-dark-orange)",
        fontFamily: "var(--font-sans)", color: "var(--helm-dark-green)",
      }}>
        <div style={{
          width: compact ? 26 : 32, height: compact ? 26 : 32, borderRadius: "50%",
          background: "var(--helm-dark-green)", color: "var(--helm-lemon)",
          display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
          fontWeight: 700, fontSize: compact ? 16 : 18, lineHeight: 1,
        }}>!</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: compact ? 12 : 13, fontWeight: 700, lineHeight: 1.3 }}>
            {person.name.split(" ")[0]}'s needs run <span style={{ fontVariantNumeric: "tabular-nums" }}>{pfDollar(shortfall)}</span> over the share from your policies.
          </div>
          <div style={{ fontSize: compact ? 11 : 12, opacity: 0.78, marginTop: 3, lineHeight: 1.4 }}>
            Trim a line below, or look at adding coverage so this plan actually clears.
          </div>
        </div>
        <button
          onClick={() => setShowModal(true)}
          style={{
            padding: compact ? "7px 12px" : "9px 14px", borderRadius: 999, border: "none",
            background: "var(--helm-dark-green)", color: "var(--helm-white)",
            fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 700, cursor: "pointer",
            whiteSpace: "nowrap", flexShrink: 0,
          }}>
          Explore coverage →
        </button>
      </div>
      
      {showModal && (
        <PFCoverageModal
          person={person}
          shortfall={shortfall}
          onClose={() => setShowModal(false)}
        />
      )}
    </>
  );
};

const PFShortfallPill = ({ amount, person, onExploreCoverage }) => {
  const [showModal, setShowModal] = pfU(false);
  return (
    <>
      <div style={{
        display: "flex", alignItems: "center", gap: 10,
        padding: "12px 14px",
        background: "var(--helm-dark-orange)", color: "var(--helm-white)",
        borderRadius: 10,
        fontFamily: "var(--font-sans)",
      }}>
        <div style={{
          width: 24, height: 24, borderRadius: "50%",
          background: "var(--helm-white)", color: "var(--helm-dark-orange)",
          display: "flex", alignItems: "center", justifyContent: "center",
          fontWeight: 700, fontSize: 14, lineHeight: 1, flexShrink: 0,
        }}>!</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 11, fontWeight: 700,
            letterSpacing: "0.06em", textTransform: "uppercase", opacity: 0.92 }}>
            Coverage gap{person ? ` · ${person}` : ""}
          </div>
          <div style={{ fontSize: 12, opacity: 0.92, marginTop: 2, lineHeight: 1.35 }}>
            Their plan exceeds the policies on file. Want to add coverage? Helm can help you shop carriers.
          </div>
        </div>
        <div style={{
          display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 6, flexShrink: 0,
        }}>
          <div style={{
            fontFamily: "var(--font-sans)", fontWeight: 700,
            fontSize: 16, letterSpacing: "-0.01em",
            fontVariantNumeric: "tabular-nums",
          }}>−{pfDollar(amount)}</div>
          <button onClick={() => setShowModal(true)}
            style={{
              padding: "5px 10px", borderRadius: 999, border: "none",
              background: "var(--helm-white)", color: "var(--helm-dark-orange)",
              fontFamily: "var(--font-sans)", fontSize: 11, fontWeight: 700,
              cursor: "pointer", whiteSpace: "nowrap",
            }}>
            Add coverage →
          </button>
        </div>
      </div>
      
      {showModal && (
        <PFCoverageModal
          person={person}
          shortfall={amount}
          onClose={() => setShowModal(false)}
        />
      )}
    </>
  );
};

const PFPlanRow = ({ exp, onChange, onDelete, onOpenDetails, flash, exiting }) => {
  const [hover, setHover] = pfU(false);
  const tint = PG_CATEGORY_TINTS[exp.category] || { base: "var(--helm-dark-ecru)" };
  return (
    <div
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        position: "relative",
        display: "grid", gridTemplateColumns: "5px 1fr auto auto auto 28px",
        gap: 12, alignItems: "center",
        padding: "10px 0",
        borderTop: "1px solid var(--helm-dark-ecru)",
        // Only clip during exit — at rest the category dropdown and other
        // popovers must be free to overflow below the row.
        overflow: exiting ? "hidden" : "visible",
        animation: exiting ? "pfRowExit .28s ease-in forwards"
                  : flash   ? "pfRowFlash .65s ease-out"
                            : undefined,
        pointerEvents: exiting ? "none" : "auto",
      }}>
      <div style={{ width: 5, height: 28, borderRadius: 2, background: tint.base }} />
      <div style={{ minWidth: 0 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <PFEditableText
            value={exp.label}
            onChange={v => onChange({ label: v })}
            fontSize={13} fontWeight={600}
            placeholder="New line item"
          />
          {exp.flagged && <PGEstimateFlag />}
        </div>
        <div style={{ marginTop: 1, display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
          <PFComboField
            value={exp.category}
            onChange={v => onChange({ category: v })}
            options={PF_CATEGORIES}
            placeholder="Category"
            fontSize={10} fontWeight={500} opacity={0.7}
            dense
          />
          <PFHorizonPicker value={exp.horizon}
            onChange={v => onChange({ horizon: v })} />
        </div>
      </div>
      <span style={{ fontSize: 10, opacity: 0.55, fontStyle: "italic", maxWidth: 180,
        overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
        {exp.source}
      </span>
      <PGWhyGlyph source={`${exp.rationale} (${exp.source})`}
        onClick={onOpenDetails} />
      <PFAmountInline value={exp.amount} onChange={v => onChange({ amount: v })} fontSize={14} />
      <button onClick={onDelete} aria-label={`Delete ${exp.label}`}
        style={{
          width: 22, height: 22, borderRadius: 999,
          background: hover ? "var(--helm-white)" : "transparent",
          border: hover ? "1px solid var(--helm-dark-ecru)" : "1px solid transparent",
          cursor: "pointer", padding: 0,
          display: "flex", alignItems: "center", justifyContent: "center",
          color: "var(--helm-dark-grey)",
          fontSize: 14, lineHeight: 1, fontWeight: 600,
          opacity: hover ? 1 : 0,
          transition: "opacity .12s",
        }}>×</button>
    </div>
  );
};

const PFGhostRow = ({ exp }) => (
  <div style={{
    display: "grid", gridTemplateColumns: "5px 1fr auto auto 28px",
    gap: 12, alignItems: "center",
    padding: "12px 12px",
    marginTop: 4, marginBottom: 2,
    border: "1.5px dashed var(--helm-dark-green)", borderRadius: 10,
    background: "var(--helm-ecru)",
  }}>
    <div style={{ width: 5, height: 28, borderRadius: 2,
      background: PG_CATEGORY_TINTS[exp.category].base, opacity: 0.6 }} />
    <div style={{ minWidth: 0 }}>
      <div style={{ fontSize: 13, fontWeight: 600, opacity: 0.85 }}>{exp.label}</div>
      <div style={{ fontSize: 10, fontStyle: "italic", opacity: 0.6 }}>
        Helm is asking about this on the right →
      </div>
    </div>
    <span style={{ fontSize: 10, fontWeight: 700,
      letterSpacing: "0.06em", textTransform: "uppercase",
      opacity: 0.65 }}>Asking now</span>
    <span style={{ fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 700,
      fontVariantNumeric: "tabular-nums", textAlign: "right", opacity: 0.55 }}>
      ~{pfDollar(exp.amount)}
    </span>
    <span />
  </div>
);

// Per-person mini facts shown in Plan view — derived dynamically from
// the live person record + household, so this stays accurate no matter
// who Suzie's beneficiaries are.
function pfFactsForPerson(personId) {
  const p = (window.PG_PEOPLE || []).find(x => x.id === personId);
  if (!p) return [];
  const h = window.PG_HOUSEHOLD || {};
  const milestones = window.suzieMilestonesFor ? window.suzieMilestonesFor(p) : {};
  const fmtK = (v) => v >= 1000 ? `$${Math.round(v / 1000).toLocaleString()}K` : `$${(v || 0).toLocaleString()}`;
  if (p.kind === "spouse") {
    const facts = [];
    if (h.mortgageBalance) facts.push({
      id: `${p.id}-mortgage`, icon: "home", label: "Mortgage",
      value: `${fmtK(h.mortgageBalance)} · ${h.mortgageYearsLeft || "?"} yrs left`,
    });
    if (h.annualSpend) facts.push({
      id: `${p.id}-spend`, icon: "heart", label: "Spend",
      value: `${fmtK(h.annualSpend)}/yr · ~${fmtK(h.monthlySpend || h.annualSpend / 12)}/mo`,
    });
    if (milestones.retirementInYears != null) facts.push({
      id: `${p.id}-retire`, icon: "clock", label: "Retires",
      value: `in ~${milestones.retirementInYears} yrs`,
    });
    return facts;
  }
  if (p.kind === "child") {
    const facts = [];
    if (milestones.collegeInYears != null) facts.push({
      id: `${p.id}-college`, icon: "book", label: "College",
      value: `in ~${milestones.collegeInYears} yrs · school TBD`,
    });
    facts.push({
      id: `${p.id}-now`, icon: "user", label: "Now",
      value: p.age != null ? `${p.age} · ${p.relationship || "child"}` : (p.relationship || "child"),
    });
    return facts;
  }
  return [{
    id: `${p.id}-rel`, icon: "user", label: "Relationship",
    value: `${p.relationship || "Beneficiary"}${p.age != null ? ` · ${p.age}` : ""}`,
  }];
}

// ── Click-to-edit modal for a purpose node ─────────────────────────
const PFPurposeModal = ({
  exp, onChange, onDelete, onClose,
  reviewing, reviewIdx, reviewTotal, onNextFlagged, onPrevFlagged,
}) => {
  // Esc to close
  pfE(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [onClose]);

  const person = PG_PEOPLE.find(p => p.id === exp.to);
  const tint = PG_PERSON_TINTS[exp.to] || { base: "var(--helm-ecru)" };
  const isLast  = reviewing && reviewIdx >= reviewTotal - 1;
  const isFirst = reviewing && reviewIdx <= 0;

  // Snapshot the original expense data when the modal opens OR when exp.id changes.
  // This ensures that pfOptionsFor() sees the correct source expense for each
  // item — critical for items with explicit .swap arrays (college, weddings, etc).
  const seedExpRef = pfR(null);
  const lastExpIdRef = pfR(null);
  if (seedExpRef.current === null || lastExpIdRef.current !== exp.id) {
    seedExpRef.current = { ...exp };
    lastExpIdRef.current = exp.id;
  }
  const options = pfOptionsFor(seedExpRef.current);
  // Track whether the user is customizing the amount manually. Reset when
  // a suggestion is picked; set when the amount field is touched. While
  // customizing, no suggestion stays highlighted even if it happens to
  // match by coincidence.
  const [customizing, setCustomizing] = pfU(false);
  const naturalSelectedIdx = options.findIndex(o => o.amount === exp.amount);
  const selectedIdx = customizing ? -1 : naturalSelectedIdx;

  // Flash the amount field briefly when a suggestion is picked, so the
  // change in the modal reads as motion rather than a silent swap.
  const [amountFlash, setAmountFlash] = pfU(false);
  const flashTimerRef = pfR(null);
  const pickSuggestion = (amount) => {
    onChange({ amount });
    setCustomizing(false);
    setAmountFlash(true);
    if (flashTimerRef.current) clearTimeout(flashTimerRef.current);
    flashTimerRef.current = setTimeout(() => setAmountFlash(false), 700);
  };
  pfE(() => () => { if (flashTimerRef.current) clearTimeout(flashTimerRef.current); }, []);

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, zIndex: 100,
      background: "rgba(0, 29, 0, 0.38)",
      display: "flex", alignItems: "center", justifyContent: "center",
      animation: "helmFadeIn .14s ease",
      padding: 24,
    }}>
      <div onClick={e => e.stopPropagation()}
        role="dialog" aria-modal="true"
        style={{
          width: "min(520px, 100%)",
          maxHeight: "calc(100vh - 48px)",
          background: "var(--helm-white)",
          borderRadius: 16,
          boxShadow: "6px 6px 0 0 var(--helm-dark-green)",
          border: "1px solid var(--helm-dark-green)",
          overflow: "hidden",
          fontFamily: "var(--font-sans)",
          display: "flex", flexDirection: "column",
        }}>
        {/* Slim header — avatar + context line + close */}
        <div style={{ padding: "12px 18px", background: tint.base, flexShrink: 0,
          display: "flex", alignItems: "center", gap: 10 }}>
          <PGAvatar id={exp.to} size={28} />
          <div style={{ flex: 1, minWidth: 0,
            fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 600,
            color: "var(--helm-dark-green)" }}>
            {reviewing
              ? <>Reviewing flagged <span style={{ opacity: 0.55 }}>· {reviewIdx + 1} of {reviewTotal}</span></>
              : <>For {person?.name.split(" ")[0]} <span style={{ opacity: 0.55 }}>· {person?.relationship}</span></>}
          </div>
          <button onClick={onClose} aria-label="Close"
            style={{
              width: 24, height: 24, borderRadius: 999, border: "none",
              background: "rgba(0,29,0,0.12)", color: "var(--helm-dark-green)",
              cursor: "pointer", padding: 0,
              display: "flex", alignItems: "center", justifyContent: "center",
              fontSize: 14, lineHeight: 1, fontWeight: 700,
            }}>×</button>
        </div>

        {/* Body — tight, no oversized form labels */}
        <div style={{ padding: "16px 20px 14px", display: "flex", flexDirection: "column", gap: 12,
          overflow: "auto" }}>
          {/* Title row: editable label (left) + big editable amount (right) */}
          <div style={{ display: "flex", alignItems: "baseline", gap: 12 }}>
            <div style={{ flex: 1, minWidth: 0 }}>
              <PFEditableText value={exp.label}
                onChange={v => onChange({ label: v })}
                fontSize={20} fontWeight={700} lineHeight="1.15"
                family="var(--font-serif)"
                placeholder="What this covers" />
            </div>
            <div style={{
              display: "inline-flex", borderRadius: 8, padding: "2px 4px", margin: "-2px -4px",
              background: amountFlash ? "var(--helm-lemon)" : "transparent",
              transition: "background .5s ease-out",
              flexShrink: 0,
            }}>
              <PFAmountInline value={exp.amount}
                key={`amt-${amountFlash}`}
                onChange={v => onChange({ amount: v })}
                onFocus={() => setCustomizing(true)}
                fontSize={24} fontWeight={700} align="right" />
            </div>
          </div>

          {/* Meta row: category + horizon, inline pickers (no label scaffolding) */}
          <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
            <PFComboField value={exp.category}
              onChange={v => onChange({ category: v })}
              options={PF_CATEGORIES}
              placeholder="Category"
              fontSize={12} fontWeight={600} dense />
            <PFHorizonPicker value={exp.horizon}
              onChange={v => onChange({ horizon: v })} />
          </div>

          {/* Why this number — compact, sourced */}
          {(exp.rationale || exp.source) && (
            <div style={{
              padding: "10px 12px", borderRadius: 10,
              background: "var(--helm-ecru)",
              fontSize: 12, lineHeight: 1.5, color: "var(--helm-dark-green)",
              display: "flex", gap: 8, alignItems: "flex-start",
            }}>
              <PGIcon name="sparkle" size={13} stroke={2}
                style={{ marginTop: 2, opacity: 0.55, flexShrink: 0 }} />
              <div style={{ minWidth: 0 }}>
                <div>{exp.rationale}</div>
                {exp.source && (
                  <div style={{ marginTop: 3, opacity: 0.55, fontSize: 10.5, fontWeight: 500,
                    letterSpacing: "0.04em", textTransform: "uppercase" }}>
                    Source · {exp.source}
                  </div>
                )}
              </div>
            </div>
          )}

          {/* Suggestion picks — three slim horizontal cards */}
          {options.length > 0 && (
            <div>
              <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.08em",
                textTransform: "uppercase", opacity: 0.5, marginBottom: 6 }}>
                {selectedIdx >= 0 ? "Suggestions"
                  : customizing ? "Custom amount · suggestions"
                  : "Try a suggestion"}
              </div>
              <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 6 }}>
                {options.map((s, i) => {
                  const sel = i === selectedIdx;
                  return (
                    <button key={i} onClick={() => pickSuggestion(s.amount)} style={{
                      display: "flex", flexDirection: "column", alignItems: "flex-start", gap: 3,
                      padding: "9px 10px", borderRadius: 10,
                      border: sel
                        ? "1.5px solid var(--helm-dark-green)"
                        : s.primary
                          ? "1.5px solid var(--helm-dark-ecru)"
                          : "1px solid var(--helm-dark-ecru)",
                      background: sel ? "var(--helm-ecru)" : "var(--helm-white)",
                      fontFamily: "var(--font-sans)",
                      color: "var(--helm-dark-green)", cursor: "pointer", textAlign: "left",
                      transition: "transform .1s, border-color .15s, background .15s",
                      minWidth: 0,
                    }}
                      onMouseDown={e => e.currentTarget.style.transform = "translateY(1px)"}
                      onMouseUp={e => e.currentTarget.style.transform = "none"}
                      onMouseLeave={e => e.currentTarget.style.transform = "none"}>
                      <div style={{ display: "flex", alignItems: "center", gap: 4,
                        fontSize: 11, fontWeight: 700, opacity: sel ? 1 : 0.85 }}>
                        {sel && <PGIcon name="check" size={11} stroke={2.6} />}
                        <span style={{
                          overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                          maxWidth: "100%",
                        }}>{s.label}</span>
                      </div>
                      <div style={{ fontVariantNumeric: "tabular-nums", fontWeight: 700, fontSize: 14,
                        letterSpacing: "-0.01em" }}>
                        {pfDollar(s.amount)}
                      </div>
                      {s.note && (
                        <div style={{ fontSize: 10, fontWeight: 500, opacity: 0.55,
                          overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                          maxWidth: "100%" }}>
                          {s.note}
                        </div>
                      )}
                    </button>
                  );
                })}
              </div>
            </div>
          )}
        </div>

        {/* Slim footer */}
        <div style={{ padding: "10px 18px 12px", background: "var(--helm-ecru)",
          borderTop: "1px solid var(--helm-dark-ecru)", flexShrink: 0,
          display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10 }}>
          <button onClick={() => { onDelete(); onClose(); }}
            style={{ background: "transparent", border: "none",
              padding: "6px 4px", cursor: "pointer",
              color: "var(--helm-dark-orange)",
              fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 700,
              display: "inline-flex", alignItems: "center", gap: 4 }}>
            <span style={{ fontSize: 14, lineHeight: 1 }}>×</span> Delete
          </button>
          {reviewing ? (
            <div style={{ display: "flex", gap: 4, alignItems: "center" }}>
              <button onClick={onPrevFlagged} disabled={isFirst}
                style={{ background: "transparent", border: "none",
                  padding: "6px 10px", cursor: isFirst ? "not-allowed" : "pointer",
                  opacity: isFirst ? 0.4 : 1, color: "var(--helm-dark-green)",
                  fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 600 }}>
                ← Prev
              </button>
              <Button variant="primary" onClick={onNextFlagged}>
                {isLast ? "Done →" : "Next flagged →"}
              </Button>
            </div>
          ) : (
            <Button variant="primary" onClick={onClose}>Done</Button>
          )}
        </div>
      </div>
    </div>
  );
};

const PFFormField = ({ label, children }) => (
  <div>
    <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.08em",
      textTransform: "uppercase", opacity: 0.55, marginBottom: 4 }}>
      {label}
    </div>
    <div style={{ padding: "4px 0", minHeight: 28, display: "flex", alignItems: "center" }}>
      {children}
    </div>
  </div>
);

// Coverage exploration modal — appears when user clicks "Explore coverage" or "Add coverage"
const PFCoverageModal = ({ person, shortfall, onClose }) => {
  // Esc to close
  pfE(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [onClose]);

  const personName = person ? (typeof person === 'string' ? person : person.name.split(" ")[0]) : "this beneficiary";

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, zIndex: 100,
      background: "rgba(0, 29, 0, 0.38)",
      display: "flex", alignItems: "center", justifyContent: "center",
      animation: "helmFadeIn .14s ease",
      padding: 24,
    }}>
      <div onClick={e => e.stopPropagation()}
        role="dialog" aria-modal="true"
        style={{
          width: "min(580px, 100%)",
          maxHeight: "calc(100vh - 48px)",
          background: "var(--helm-white)",
          borderRadius: 16,
          boxShadow: "6px 6px 0 0 var(--helm-dark-green)",
          border: "1px solid var(--helm-dark-green)",
          overflow: "hidden",
          fontFamily: "var(--font-sans)",
          display: "flex", flexDirection: "column",
        }}>
        {/* Header */}
        <div style={{ padding: "16px 20px", background: "var(--helm-orange)", flexShrink: 0,
          display: "flex", alignItems: "center", gap: 10, borderBottom: "1px solid var(--helm-dark-orange)" }}>
          <div style={{
            width: 32, height: 32, borderRadius: "50%",
            background: "var(--helm-dark-green)", color: "var(--helm-lemon)",
            display: "flex", alignItems: "center", justifyContent: "center",
            fontWeight: 700, fontSize: 18, lineHeight: 1,
          }}>!</div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 15, fontWeight: 700, color: "var(--helm-dark-green)" }}>
              Coverage gap
            </div>
            <div style={{ fontSize: 12, opacity: 0.75, color: "var(--helm-dark-green)" }}>
              {personName}'s plan runs <span style={{ fontVariantNumeric: "tabular-nums", fontWeight: 700 }}>{pfDollar(shortfall)}</span> over
            </div>
          </div>
          <button onClick={onClose} aria-label="Close"
            style={{
              width: 24, height: 24, borderRadius: 999, border: "none",
              background: "rgba(0,29,0,0.12)", color: "var(--helm-dark-green)",
              cursor: "pointer", padding: 0,
              display: "flex", alignItems: "center", justifyContent: "center",
              fontSize: 14, lineHeight: 1, fontWeight: 700,
            }}>×</button>
        </div>

        {/* Body */}
        <div style={{ padding: "20px 24px", display: "flex", flexDirection: "column", gap: 16,
          overflow: "auto", flex: 1 }}>
          {/* AI explanation */}
          <div style={{ display: "flex", gap: 10, alignItems: "flex-start" }}>
            <div style={{
              width: 24, height: 24, flexShrink: 0, borderRadius: "50%",
              background: "var(--helm-dark-green)", color: "var(--helm-lemon)",
              display: "flex", alignItems: "center", justifyContent: "center",
            }}>
              <PGIcon name="sparkle" size={14} stroke={2} />
            </div>
            <div style={{ flex: 1, minWidth: 0, fontFamily: "var(--font-serif)", fontSize: 14, lineHeight: "21px" }}>
              The current plan for {personName} totals more than the policies on file can cover. You have a few options:
            </div>
          </div>

          {/* Options */}
          <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            {/* Option 1: Trim the plan */}
            <div style={{
              padding: "14px 16px", borderRadius: 12,
              border: "1px solid var(--helm-dark-ecru)", background: "var(--helm-ecru)",
            }}>
              <div style={{ fontSize: 13, fontWeight: 700, marginBottom: 4 }}>
                1. Trim the plan below
              </div>
              <div style={{ fontSize: 12, lineHeight: 1.4, opacity: 0.75 }}>
                Reduce some line items or remove expenses that aren't essential right now. Close this and edit the plan.
              </div>
            </div>

            {/* Option 2: Add coverage */}
            <div style={{
              padding: "14px 16px", borderRadius: 12,
              border: "1.5px solid var(--helm-chartreuse-2)", background: "var(--helm-soft-chartreuse)",
            }}>
              <div style={{ fontSize: 13, fontWeight: 700, marginBottom: 4, color: "var(--helm-white)" }}>
                2. Add more life insurance coverage
              </div>
              <div style={{ fontSize: 12, lineHeight: 1.4, color: "var(--helm-white)", opacity: 0.92, marginBottom: 10 }}>
                Helm can help you shop carriers and find the right policy to fill this gap. We'll walk you through term, whole life, and other options that fit your family's needs.
              </div>
              <div style={{ display: "flex", gap: 8 }}>
                <Button variant="inverse" style={{ flex: 1, padding: "8px 12px", fontSize: 12 }}
                  onClick={() => alert("This would launch the coverage shopping flow — lives in a separate skill.")}>
                  Shop for coverage →
                </Button>
                <Button variant="inverse" style={{ flex: 1, padding: "8px 12px", fontSize: 12 }}
                  onClick={() => alert("This would connect you with your agent.")}>
                  Speak to my agent →
                </Button>
              </div>
            </div>

            {/* Option 3: Keep as-is */}
            <div style={{
              padding: "14px 16px", borderRadius: 12,
              border: "1px solid var(--helm-dark-ecru)", background: "var(--helm-white)",
            }}>
              <div style={{ fontSize: 13, fontWeight: 700, marginBottom: 4 }}>
                3. Keep the plan as-is (flag for later)
              </div>
              <div style={{ fontSize: 12, lineHeight: 1.4, opacity: 0.75 }}>
                You can save this plan and come back to resolve the gap when you're ready. We'll remind you.
              </div>
            </div>
          </div>
        </div>

        {/* Footer */}
        <div style={{ padding: "12px 20px", background: "var(--helm-ecru)",
          borderTop: "1px solid var(--helm-dark-ecru)", flexShrink: 0,
          display: "flex", alignItems: "center", justifyContent: "flex-end" }}>
          <Button variant="primary" onClick={onClose}>Got it</Button>
        </div>
      </div>
    </div>
  );
};

// ── FLOW VIEW — Sankey filtered to active person (V4 style) ────────
const PFFlowView = ({ person, expenses, isDraft, doneCount, onPurposeClick }) => {
  const items = expenses.filter(e => e.to === person.id);
  const allocated = items.reduce((s, e) => s + e.amount, 0);
  const share = PG_TOTAL_BY[person.id];
  const over = allocated > share;
  const tint = PG_PERSON_TINTS[person.id];

  return (
    <div style={{
      padding: 16, background: "var(--helm-ecru)",
      border: "1px solid var(--helm-dark-ecru)", borderRadius: 16,
      display: "flex", flexDirection: "column", gap: 12,
    }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
          <PGAvatar id={person.id} size={28} />
          <div>
            <div style={{ fontSize: 13, fontWeight: 700 }}>{person.name.split(" ")[0]}'s flow</div>
            <div style={{ fontSize: 11, opacity: 0.65 }}>
              policies → {person.name.split(" ")[0]} → what they cover
            </div>
          </div>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 14, fontSize: 11, opacity: 0.7 }}>
          <span style={{ color: over ? "var(--helm-dark-orange)" : "inherit" }}>
            <b style={{ fontVariantNumeric: "tabular-nums" }}>{pfDollar(allocated)}</b> allocated
          </span>
          <span style={{ width: 1, height: 12, background: "var(--helm-dark-ecru)" }} />
          {over ? (
            <span style={{ color: "var(--helm-dark-orange)" }}>
              <b style={{ fontVariantNumeric: "tabular-nums" }}>−{pfDollar(allocated - share)}</b> short
            </span>
          ) : (
            <span><b style={{ fontVariantNumeric: "tabular-nums" }}>{pfDollar(share - allocated)}</b> to grow</span>
          )}
        </div>
      </div>

      {over && <PFShortfallBanner person={person} allocated={allocated} share={share} compact />}

      <div style={{ background: "var(--helm-white)", borderRadius: 12, padding: 12 }}>
        <PGSankey
          policies={PG_POLICIES}
          expenses={expenses}
          focus={person.id}
          width={760}
          height={420}
          showHeaders
          onExpenseClick={onPurposeClick}
        />
      </div>

      {!isDraft && (
        <div style={{ display: "flex", alignItems: "center", gap: 8,
          padding: "10px 12px", borderRadius: 10, background: "var(--helm-white)",
          fontSize: 12, opacity: 0.75 }}>
          <PGIcon name="sparkle" size={13} stroke={2} />
          <span>The flow keeps growing as you answer questions on the right. Right now: {doneCount} of {PG_EXPENSES.length} confirmed.</span>
        </div>
      )}
    </div>
  );
};

// ── POWER VIEW — option 12 needs-form, filtered to active person ───
const PFPowerView = ({ person, expenses, isDraft, onUpdate, onDelete, onAdd, flashId, exitingId }) => {
  const items = expenses.filter(e => e.to === person.id);
  const total = items.reduce((s, e) => s + e.amount, 0);
  const tint = PG_PERSON_TINTS[person.id];
  const unassignedItems = items.filter(e => !e.horizon);
  const groups = [
    ...(unassignedItems.length > 0
      ? [{ horizon: { id: "__unassigned", label: "Not yet placed",
            desc: "Pick a horizon to slot these into the plan." }, items: unassignedItems }]
      : []),
    ...PG_HORIZONS.map(h => ({
      horizon: h,
      items: items.filter(e => e.horizon === h.id),
    })).filter(g => g.items.length > 0),
  ];

  return (
    <div style={{ display: "flex", gap: 18 }}>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: "flex", marginBottom: 12 }}>
          <button onClick={onAdd} style={{
            marginLeft: "auto",
            background: "transparent", border: "1.5px dashed var(--helm-dark-ecru)",
            borderRadius: 999, padding: "8px 14px",
            fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 600,
            color: "var(--helm-dark-grey)", cursor: "pointer",
            display: "inline-flex", alignItems: "center", gap: 6,
            transition: "border-color .15s, color .15s, background .15s",
          }}
            onMouseEnter={e => {
              e.currentTarget.style.borderColor = "var(--helm-dark-green)";
              e.currentTarget.style.color = "var(--helm-dark-green)";
              e.currentTarget.style.background = "var(--helm-ecru)";
            }}
            onMouseLeave={e => {
              e.currentTarget.style.borderColor = "var(--helm-dark-ecru)";
              e.currentTarget.style.color = "var(--helm-dark-grey)";
              e.currentTarget.style.background = "transparent";
            }}>
            <PGIcon name="plus" size={12} stroke={2} /> Add a custom line for {person.name.split(" ")[0]}
          </button>
        </div>
        {groups.map(({ horizon: h, items: here }) => (
            <section key={h.id} style={{ marginBottom: 18 }}>
              <div style={{ display: "flex", alignItems: "baseline", gap: 10, marginBottom: 8 }}>
                {h.id === "__unassigned" ? (
                  <span style={{
                    display: "inline-flex", alignItems: "center", height: 22,
                    padding: "0 10px", borderRadius: 999,
                    background: "transparent",
                    border: "1px dashed var(--helm-dark-ecru)",
                    color: "var(--helm-dark-grey)",
                    fontFamily: "var(--font-sans)", fontSize: 11,
                    fontWeight: 700, letterSpacing: "0.04em", textTransform: "uppercase",
                  }}>To place</span>
                ) : (
                  <PGHorizonChip horizon={h.id} size="md" />
                )}
                <span style={{ fontSize: 15, fontWeight: 700 }}>{h.label}</span>
                <span style={{ fontSize: 11, opacity: 0.55 }}>{h.desc}</span>
              </div>
              <div style={{
                background: "var(--helm-white)", borderRadius: 12,
                border: "1px solid var(--helm-dark-ecru)", overflow: "hidden",
              }}>
                {here.map((e, i) => (
                  <PFPowerRow key={e.id} exp={e} first={i === 0}
                    flash={e.id === flashId}
                    exiting={e.id === exitingId}
                    onChange={p => onUpdate(e.id, p)}
                    onDelete={() => onDelete(e.id)}
                  />
                ))}
              </div>
            </section>
        ))}
        {!isDraft && items.length === 0 && (
          <div style={{ padding: 30, textAlign: "center", opacity: 0.55,
            border: "1px dashed var(--helm-dark-ecru)", borderRadius: 12 }}>
            <PGIcon name="list" size={20} stroke={1.6} />
            <div style={{ marginTop: 8, fontSize: 13 }}>
              Nothing yet for {person.name.split(" ")[0]}. Answer the next questions on the right and they'll land here as editable rows.
            </div>
          </div>
        )}
      </div>

      {/* Side summary */}
      <div style={{ width: 240, flexShrink: 0 }}>
        <div style={{
          padding: 18, background: tint.light, borderRadius: 14,
          border: "1px solid var(--helm-dark-ecru)",
        }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 12 }}>
            <PGAvatar id={person.id} size={40} />
            <div>
              <div style={{ fontSize: 12, fontWeight: 700 }}>{person.name}</div>
              <div style={{ fontSize: 11, opacity: 0.65 }}>{person.relationship} · age {person.age}</div>
            </div>
          </div>
          <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.08em",
            textTransform: "uppercase", opacity: 0.55, marginBottom: 4 }}>Allocated</div>
          <div style={{ display: "flex", alignItems: "baseline", gap: 6, marginBottom: 10 }}>
            <span style={{ fontFamily: "var(--font-sans)", fontSize: 24, fontWeight: 700,
              letterSpacing: "-0.02em", fontVariantNumeric: "tabular-nums" }}>
              {pfDollar(total)}
            </span>
            <span style={{ fontSize: 12, opacity: 0.65 }}>of {pfDollar(PG_TOTAL_BY[person.id])}</span>
          </div>
          <PGAllocBar allocated={total} share={PG_TOTAL_BY[person.id]} color={tint.deep} height={8} showLabel />
          <div style={{ marginTop: 14, fontSize: 11, lineHeight: 1.4, opacity: 0.75 }}>
            <PGIcon name="sparkle" size={12} stroke={2}
              style={{ marginRight: 4, verticalAlign: "middle" }} />
            Every suggested amount is grounded in your accounts or a public dataset. Tap "why this number" on any row to see the source.
          </div>
        </div>
      </div>
    </div>
  );
};

// One Power-view row — full editing surface
const PFPowerRow = ({ exp, first, onChange, onDelete, flash, exiting }) => {
  const [hover, setHover] = pfU(false);
  const cat = PG_CATEGORY_TINTS[exp.category] || { base: "var(--helm-dark-ecru)" };
  return (
    <div
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        display: "grid", gridTemplateColumns: "6px 1fr auto 130px 24px",
        gap: 14, alignItems: "center", padding: "13px 14px",
        borderTop: first ? "none" : "1px solid var(--helm-dark-ecru)",
        // Only clip during exit; dropdowns inside the row need to overflow.
        overflow: exiting ? "hidden" : "visible",
        animation: exiting ? "pfPowerRowExit .28s ease-in forwards"
                  : flash   ? "pfRowFlash .65s ease-out"
                            : undefined,
        pointerEvents: exiting ? "none" : "auto",
      }}>
      <div style={{ width: 6, height: 30, borderRadius: 2, background: cat.base }} />
      <div>
        <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <PFEditableText
            value={exp.label}
            onChange={v => onChange({ label: v })}
            fontSize={13} fontWeight={600}
            placeholder="New line item"
          />
          {exp.flagged && <PGEstimateFlag />}
        </div>
        <div style={{ marginTop: 3 }}>
          <PGSourcePill source={`${exp.source} · why this number`} />
        </div>
      </div>
      <span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
        <PFComboField
          value={exp.category}
          onChange={v => onChange({ category: v })}
          options={PF_CATEGORIES}
          placeholder="Category"
          fontSize={11} fontWeight={500} opacity={0.65} italic
          dense
        />
        <PFHorizonPicker value={exp.horizon}
          onChange={v => onChange({ horizon: v })} />
      </span>
      <PFAmountInline value={exp.amount} onChange={v => onChange({ amount: v })} fontSize={14} />
      <button onClick={onDelete} aria-label={`Delete ${exp.label}`}
        style={{
          width: 22, height: 22, borderRadius: 999,
          background: hover ? "var(--helm-white)" : "transparent",
          border: hover ? "1px solid var(--helm-dark-ecru)" : "1px solid transparent",
          cursor: "pointer", padding: 0,
          display: "flex", alignItems: "center", justifyContent: "center",
          color: "var(--helm-dark-grey)",
          fontSize: 14, lineHeight: 1, fontWeight: 600,
          opacity: hover ? 1 : 0,
          transition: "opacity .12s",
        }}>×</button>
    </div>
  );
};

// ════════════════════════════════════════════════════════════════════
// CHAT RAIL CONTENT — one entry per screen + path
// ════════════════════════════════════════════════════════════════════
function pfChatForScreen(screen, ctx) {
  const { setScreen, setPath, path } = ctx;
  if (screen === "facts") {
    // Build a personalized example from the actual beneficiaries instead
    // of the hardcoded "Eli". Prefer a child (private-school example
    // makes sense). Otherwise mention any non-anchor bene. Otherwise omit.
    const _people = (window.PG_PEOPLE || []);
    const _child = _people.find(p => p.kind === "child");
    let _exampleClause = "";
    if (_child) {
      _exampleClause = `, or that ${_child.firstName}'s already talking about a private school,`;
    } else {
      const _other = _people.find(p => p.kind !== "anchor");
      if (_other) _exampleClause = `, or that ${_other.firstName}'s plans have changed,`;
    }
    const _factsLine = `If you tell me you're moving in two years${_exampleClause} the plan I draft will look different. So this part really matters.`;
    return {
      messages: [
        { from: "ai", prose: true, text: "Before I draft anything, let's make sure I understand your family." },
        { from: "ai", prose: true, text: "What's on the left is pulled from your connected accounts and what you've told us already. The synced numbers are locked to your real data. The estimates are my best guesses — please fix them if I'm off." },
        { from: "ai", prose: true, text: _factsLine },
      ],
      replies: [
        { label: "Looks right — continue", onClick: () => setScreen("path") },
        { label: "Help me add something", onClick: () => {}, soft: true },
      ],
    };
  }
  if (screen === "path") {
    return {
      messages: [
        { from: "ai", prose: true, text: "There's no wrong answer here. Most people pick the draft — it's faster, and you can edit anything afterward. But if you want to feel every decision, we can walk through them together." },
      ],
      replies: [
        { label: "Build me a draft (recommended)", onClick: () => { setPath("draft"); setScreen("plan"); } },
        { label: "Walk me through it", onClick: () => { setPath("guide"); setScreen("plan"); }, soft: true },
      ],
    };
  }
  // screen === "plan"
  // Friendly Oxford-list of beneficiary first-names: "John", "John and Sarah", "John, Sarah, and Alex".
  const _names = PG_PEOPLE.map(p => p.firstName);
  const _list = _names.length === 0 ? "the family"
    : _names.length === 1 ? _names[0]
    : _names.length === 2 ? `${_names[0]} and ${_names[1]}`
    : `${_names.slice(0, -1).join(", ")}, and ${_names[_names.length - 1]}`;
  if (path === "draft") {
    return {
      messages: [
        { from: "ai", prose: true, text: <>I've drafted <b>{PG_EXPENSES.length} line items</b> across {_list} — grounded in the facts we just confirmed.</> },
        { from: "ai", prose: true, text: <>A few are flagged as estimates: long-horizon things like college choice and long-term care. Anything not assigned lives in <b>Invest for growth</b> — the seed money that compounds.</> },
        { from: "ai", prose: true, text: "Tap a tab to focus on one person. Switch between Plan, Flow, and Power views to see the allocation different ways. Click any number or label to edit it; hover a row to delete it." },
      ],
      replies: [
        { label: "Walk me through the flagged estimates",
          onClick: () => ctx.onStartFlaggedReview && ctx.onStartFlaggedReview() },
        { label: "This looks right — save & continue", onClick: () => ctx.onAdvance && ctx.onAdvance(), soft: true },
      ],
    };
  }
  // path === "guide"
  const remaining = PF_GUIDE_ORDER.length - (ctx.guideAnswers?.length || 0);
  return {
    messages: [
      { from: "ai", prose: true, text: <>We've set aside <b>{pfDollar(PG_GRAND_TOTAL)}</b> for {_list}. Let's walk through it, one piece at a time.</> },
      { from: "ai", prose: true, text: remaining > 0
          ? <>About {remaining} {remaining === 1 ? "decision" : "decisions"} to go. Pick an option, type your own, or skip anything that doesn't fit.</>
          : <>You've made all the decisions — the plan on the left is your draft.</> },
    ],
    replies: [],
  };
}

// Beneficiary transition card — sits between sections in the chat rail
const PFGuideSectionHeader = ({ personId, firstOfAll, prevPersonName }) => {
  const person = PG_PEOPLE.find(p => p.id === personId);
  if (!person) return null;
  const tint = PG_PERSON_TINTS[personId];
  return (
    <>
      {!firstOfAll && (
        <PGAIBubble>
          That's everything for <b>{prevPersonName}</b>. Now let's set aside what <b>{person.name.split(" ")[0]}</b> needs.
        </PGAIBubble>
      )}
      <div style={{
        display: "flex", alignItems: "center", gap: 10,
        padding: "10px 14px", borderRadius: 12,
        background: tint.light,
        border: `1px solid ${tint.base}`,
      }}>
        <PGAvatar id={personId} size={32} ring={tint.deep} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: "0.08em",
            textTransform: "uppercase", opacity: 0.6 }}>
            Now planning for
          </div>
          <div style={{ fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 700 }}>
            {person.name} <span style={{ opacity: 0.65, fontWeight: 500 }}>· {person.relationship}, age {person.age}</span>
          </div>
        </div>
      </div>
    </>
  );
};

// Guide-path custom card — fully interactive.
// Props: guideAnswers, currentExp, expenses, onAnswer(amount), onSkip(),
//        onUndo(), onContinue() — fires when the user clicks Save & continue
//        in the completion state, parallel to the draft path's primary CTA.
const PFGuideQuestionCard = ({ guideAnswers = [], currentExp, expenses = [], onAnswer, onSkip, onUndo, onContinue }) => {
  const [customMode, setCustomMode] = pfU(false);
  const [customAmount, setCustomAmount] = pfU("");
  const [exiting, setExiting] = pfU(false);
  const exitTimerRef = pfR(null);

  pfE(() => {
    // Reset transient state whenever the question changes
    setCustomMode(false);
    setCustomAmount("");
    setExiting(false);
    return () => { if (exitTimerRef.current) clearTimeout(exitTimerRef.current); };
  }, [currentExp?.id, guideAnswers.length]);

  // Animate the card out, then commit. ~320ms matches the keyframe.
  // We also fire `pf-pre-commit` immediately so the main canvas can
  // smooth-scroll to its current bottom DURING the exit animation — by
  // the time the new row mounts, the user is already anchored there.
  const animateAndAnswer = (amount) => {
    if (exiting) return;
    setExiting(true);
    window.dispatchEvent(new CustomEvent("pf-pre-commit"));
    exitTimerRef.current = setTimeout(() => { onAnswer(amount); }, 320);
  };
  const animateAndSkip = () => {
    if (exiting) return;
    setExiting(true);
    exitTimerRef.current = setTimeout(() => { onSkip && onSkip(); }, 320);
  };

  // Completion state
  if (!currentExp) {
    return (
      <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
        <PFGuideHistory guideAnswers={guideAnswers} expenses={expenses} onUndo={onUndo} />
        <PGAIBubble>
          That's everything — your plan is built. Click any line on the left to fine-tune, or save and continue.
        </PGAIBubble>
        <div style={{
          background: "var(--helm-soft-chartreuse)", color: "var(--helm-white)",
          padding: 16, borderRadius: 14,
          display: "flex", alignItems: "center", gap: 12,
        }}>
          <div style={{
            width: 32, height: 32, borderRadius: "50%",
            background: "var(--helm-white)", color: "var(--helm-chartreuse-2)",
            display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
          }}>
            <PGIcon name="check" size={16} stroke={2.4} />
          </div>
          <div>
            <div style={{ fontSize: 13, fontWeight: 700 }}>All {PF_GUIDE_ORDER.length} decisions made</div>
            <div style={{ fontSize: 11, opacity: 0.85 }}>Ready to save and continue to the coverage map.</div>
          </div>
        </div>
        {onContinue && (
          <PGButton variant="primary" onClick={onContinue} style={{ width: "100%" }}>
            Save plan & continue →
          </PGButton>
        )}
      </div>
    );
  }

  const options = pfOptionsFor(currentExp);
  const horizon = PG_HORIZONS.find(h => h.id === currentExp.horizon);
  const customN = parseInt((customAmount || "").replace(/[^\d]/g, ""), 10);
  const customValid = !isNaN(customN) && customN > 0;

  // Detect "first question for this beneficiary" so we can show a
  // transition message ("Now let's talk about Eli.").
  const curIdx = guideAnswers.length;
  const prevId = curIdx > 0 ? PF_GUIDE_ORDER[curIdx - 1] : null;
  const prevExp = prevId ? PG_EXPENSES.find(e => e.id === prevId) : null;
  const isFirstForPerson = curIdx === 0 || (prevExp && prevExp.to !== currentExp.to);
  const personName = PG_PEOPLE.find(p => p.id === currentExp.to)?.name.split(" ")[0] || currentExp.to;

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
      <PFGuideHistory guideAnswers={guideAnswers} expenses={expenses} onUndo={onUndo} />

      {/* Beneficiary transition — only when we just moved to a new person */}
      {isFirstForPerson && (
        <PFGuideSectionHeader personId={currentExp.to} firstOfAll={curIdx === 0} prevPersonName={
          prevExp ? PG_PEOPLE.find(p => p.id === prevExp.to)?.name.split(" ")[0] : null
        } />
      )}

      {/* AI prompt for the current question */}
      <PGAIBubble>
        {pfPromptFor(currentExp)}
      </PGAIBubble>

      {/* Decision card */}
      <div
        key={currentExp.id}
        style={{
          background: "var(--helm-white)", borderRadius: 16,
          border: "1px solid var(--helm-dark-ecru)",
          padding: 16, boxShadow: "var(--shadow-card)",
          animation: exiting ? "pfCardCollapse .32s ease-in forwards"
                             : "pfCardEnter .3s ease-out",
          transformOrigin: "top center",
          overflow: "hidden",
          pointerEvents: exiting ? "none" : "auto",
        }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between",
          marginBottom: 10 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <PGAvatar id={currentExp.to} size={28} />
            <div>
              <div style={{ fontSize: 13, fontWeight: 700 }}>{currentExp.label}</div>
              <div style={{ fontSize: 10, opacity: 0.6 }}>
                For {currentExp.to} · {horizon?.short || ""}
              </div>
            </div>
          </div>
          <span style={{ fontFamily: "var(--font-sans)", fontSize: 18, fontWeight: 700,
            letterSpacing: "-0.02em", fontVariantNumeric: "tabular-nums" }}>
            {pfDollar(currentExp.amount)}
          </span>
        </div>
        <div style={{ marginBottom: 10 }}>
          <PGRationaleNote rationale={currentExp.rationale} source={currentExp.source} compact />
        </div>

        {!customMode ? (
          <>
            <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.06em",
              textTransform: "uppercase", opacity: 0.5, marginBottom: 6 }}>
              Pick one
            </div>
            <div style={{ display: "flex", flexDirection: "column", gap: 6, marginBottom: 10 }}>
              {options.map((s, i) => (
                <button key={i} onClick={() => animateAndAnswer(s.amount)} style={{
                  display: "flex", justifyContent: "space-between", alignItems: "center",
                  padding: "10px 14px", borderRadius: 12,
                  border: s.primary ? "1.5px solid var(--helm-dark-green)" : "1px solid var(--helm-dark-ecru)",
                  background: s.primary ? "var(--helm-ecru)" : "var(--helm-white)",
                  fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 600,
                  color: "var(--helm-dark-green)", cursor: "pointer", textAlign: "left",
                  transition: "transform .1s",
                }}
                  onMouseDown={e => e.currentTarget.style.transform = "translateY(1px)"}
                  onMouseUp={e => e.currentTarget.style.transform = "none"}
                  onMouseLeave={e => e.currentTarget.style.transform = "none"}>
                  <div style={{ minWidth: 0 }}>
                    <div>{s.label}</div>
                    {s.note && <div style={{ fontSize: 10, fontWeight: 500, opacity: 0.55, marginTop: 1 }}>{s.note}</div>}
                  </div>
                  <span style={{ fontVariantNumeric: "tabular-nums", fontWeight: 700,
                    fontSize: 14, flexShrink: 0, marginLeft: 8 }}>{pfDollar(s.amount)}</span>
                </button>
              ))}
            </div>
            <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
              <button onClick={() => setCustomMode(true)}
                style={{ background: "transparent", border: "none",
                  fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 600,
                  color: "var(--helm-dark-green)", cursor: "pointer", padding: "8px 4px",
                  display: "inline-flex", alignItems: "center", gap: 4 }}>
                <PGIcon name="edit" size={11} stroke={2} /> Enter a custom amount
              </button>
              <span style={{ flex: 1 }} />
              <button onClick={animateAndSkip}
                style={{ background: "transparent", border: "none",
                  fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 600,
                  color: "var(--helm-dark-green)", opacity: 0.6, cursor: "pointer", padding: "8px 4px" }}>
                Skip this →
              </button>
            </div>
          </>
        ) : (
          <>
            <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.06em",
              textTransform: "uppercase", opacity: 0.5, marginBottom: 6 }}>
              Your amount
            </div>
            <div style={{
              display: "flex", alignItems: "center", gap: 8,
              padding: "10px 14px", borderRadius: 12,
              border: "1.5px solid var(--helm-dark-green)",
              background: "var(--helm-white)", marginBottom: 10,
            }}>
              <span style={{ fontSize: 18, fontWeight: 700, opacity: 0.55 }}>$</span>
              <input autoFocus
                value={customAmount}
                onChange={e => setCustomAmount(e.target.value.replace(/[^\d]/g, ""))}
                onKeyDown={e => {
                  if (e.key === "Enter" && customValid) animateAndAnswer(customN);
                  if (e.key === "Escape") { setCustomMode(false); setCustomAmount(""); }
                }}
                placeholder="0"
                style={{
                  flex: 1, minWidth: 0, background: "transparent", border: "none", outline: "none",
                  fontFamily: "var(--font-sans)", fontSize: 18, fontWeight: 700,
                  color: "var(--helm-dark-green)", fontVariantNumeric: "tabular-nums",
                  letterSpacing: "-0.01em",
                }}
              />
              <span style={{ fontSize: 11, opacity: 0.55 }}>
                {customValid ? Number(customN).toLocaleString() : "enter dollars"}
              </span>
            </div>
            <div style={{ display: "flex", gap: 6 }}>
              <Button variant="primary"
                onClick={() => customValid && animateAndAnswer(customN)}
                disabled={!customValid}
                style={{ padding: "10px 16px", fontSize: 13,
                  opacity: customValid ? 1 : 0.5,
                  cursor: customValid ? "pointer" : "not-allowed" }}>
                Use this amount →
              </Button>
              <button onClick={() => { setCustomMode(false); setCustomAmount(""); }}
                style={{ background: "transparent", border: "none",
                  fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 600,
                  color: "var(--helm-dark-green)", cursor: "pointer", padding: "10px 8px" }}>
                Back to picks
              </button>
            </div>
          </>
        )}
      </div>
    </div>
  );
};

// Tiny AI prompt synthesizer per expense — sets the conversation context
function pfPromptFor(exp) {
  // Dynamic IDs are templated as `<tid>__<personSlug>` after pgApplyContext.
  const tid = (exp.id || "").split("__")[0];
  const h = window.PG_HOUSEHOLD || {};
  const person = (window.PG_PEOPLE || []).find(p => p.id === exp.to);
  const firstName = person?.firstName || (exp.to || "").split(" ")[0] || "the family";
  const fmtK = (v) => v >= 1000 ? `$${Math.round(v / 1000).toLocaleString()}K` : `$${(v || 0).toLocaleString()}`;
  const fmt$ = (v) => `$${(v || 0).toLocaleString()}`;
  const promptMap = {
    funeral:    <>The NFDA's 2023 median for a traditional funeral is around $9,000. Want to plan for that, or something different?</>,
    mortgage:   <>You've got {fmt$(h.mortgageBalance)} left on the {h.mortgageBank || "mortgage"}. Should I plan to pay it off so {firstName} and the family stay in the house?</>,
    auto:       <>The auto loan balance on the {h.autoVehicle || "car"} is about {fmt$(h.autoLoan)}. Pay it off in one go, or leave the monthly payment?</>,
    cards:      <>Revolving cards come to about {fmt$(h.creditCard)}{h.studentLoans ? `, plus ${fmt$(h.studentLoans)} of student loans` : ""}. Clear it out?</>,
    income:     <>You bring in about {fmt$(h.suzieIncome)}/yr. How much should I set aside so {firstName} doesn't feel a financial cliff?</>,
    therapy:    <>Most families do at least a year of therapy after a loss. Want me to plan for it?</>,
    travel:     <>You mentioned you wanted them to keep traveling. Want me to earmark an annual trip?</>,
    college:    <>{firstName}'s college is on the horizon. The College Board's avg for in-state public is $28,840/yr. Which scenario should I plan for?</>,
    grad:       <>About a third of grads go on to grad school. Want a year of in-state tuition reserved for {firstName}?</>,
    launch:     <>The "soft landing" fund — first apartment, car, the bridge into adulthood. How much for {firstName}?</>,
    invest:     <>Held in {firstName}'s name, low-cost index funds. About $80,000 at ~7% real return compounds for decades. Worth it?</>,
    milestone:  <>The Knot's 2023 median wedding was around $35,000. Cover most of one for {firstName}?</>,
    emergency:  <>Six months of household expenses is the rule of thumb — about {fmtK((h.monthlySpend || 0) * 6)} for you. Reserve it?</>,
    retirement: <>You were contributing to Roth. Should I top up {firstName}'s retirement for the years you would've covered?</>,
    ltc:        <>Genworth puts a year of nursing care at ~$104,000. Want to reserve a buffer for {firstName}'s late-life care?</>,
    propertytax:<>Ten years of property tax keeps the house safe without a forced sale. Reserve it?</>,
  };
  return promptMap[tid] || <>How much should I plan for <b>{exp.label}</b>?</>;
}

// Chat-history strip — past bubbles (with Undo for the most recent)
const PFGuideHistory = ({ guideAnswers, expenses, onUndo }) => {
  const lastIdx = guideAnswers.length - 1;
  return (
    <>
      {guideAnswers.map((a, i) => {
        const e = PG_EXPENSES.find(x => x.id === a.id);
        if (!e) return null;
        // For accepted answers, look up the current amount from the live `expenses`
        const live = expenses.find(x => x.id === a.id);
        const amount = live ? live.amount : a.amount;
        const skipped = !!a.skipped;
        const removed = !skipped && !live;
        const isLast = i === lastIdx;
        return (
          <div key={i} style={{
            alignSelf: "flex-end", maxWidth: "92%",
            padding: "9px 13px", borderRadius: "16px 16px 4px 16px",
            background: skipped ? "var(--helm-ecru)" : "var(--helm-dark-green)",
            color: skipped ? "var(--helm-dark-green)" : "var(--helm-white)",
            border: skipped ? "1px solid var(--helm-dark-ecru)" : "none",
            fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 500,
            display: "flex", alignItems: "center", gap: 8,
            opacity: removed ? 0.55 : 1,
            animation: "pfBubbleEnter .32s ease-out",
          }}>
            {skipped ? (
              <span style={{ fontSize: 11, opacity: 0.75, fontStyle: "italic" }}>↷</span>
            ) : (
              <PGIcon name="check" size={12} stroke={2.5} />
            )}
            <span style={{ flex: 1 }}>
              {e.label}
              {removed && <span style={{ opacity: 0.7, fontStyle: "italic" }}> · removed</span>}
            </span>
            <span style={{ fontWeight: 700, fontVariantNumeric: "tabular-nums" }}>
              {skipped ? "skipped" : pfDollar(amount)}
            </span>
            {isLast && onUndo && (
              <button onClick={onUndo} title="Undo"
                style={{ background: "transparent", border: "none", padding: 0,
                  cursor: "pointer", color: "inherit", opacity: 0.7,
                  fontFamily: "var(--font-sans)", fontSize: 10, fontWeight: 600,
                  textDecoration: "underline",
                }}>undo</button>
            )}
          </div>
        );
      })}
    </>
  );
};

Object.assign(window, {
  PF_STEPS, PF_CURRENT_STEP,
  PF_GUIDE_ORDER, PF_GUIDE_SEED,
  pfDollar, pfOptionsFor,
  FactsConfirmScreen, PathChoiceScreen, PayoutPlanScreen,
  PFGuideQuestionCard, pfChatForScreen,
  PFCoverageModal,
});
