/* =========================================================================
   Form widgets for structured reflection prompts
   Each takes value, onChange, and prompt metadata.
   Answer shapes:
     textarea/text      → string
     radio              → string
     radio-other        → { value, other }
     checks             → string[]
     list               → string[]
     toc-builder        → { [sectionKey]: string[] }
   ========================================================================= */

/* ---------- Textarea ---------- */
function WTextarea({ value, onChange, prompt }) {
  return (
    <textarea
      value={value || ""}
      onChange={e => onChange(e.target.value)}
      placeholder={prompt.placeholder}
      rows={prompt.rows || 3}
    />
  );
}

/* ---------- Single-line text ---------- */
function WText({ value, onChange, prompt }) {
  return (
    <input
      type="text"
      className="w-text"
      value={value || ""}
      onChange={e => onChange(e.target.value)}
      placeholder={prompt.placeholder || ""}
    />
  );
}

/* ---------- Radio ---------- */
function WRadio({ value, onChange, prompt }) {
  return (
    <div className="w-radio">
      {prompt.options.map(opt => {
        const checked = value === opt;
        return (
          <label key={opt} className={`w-opt ${checked ? "w-opt--on" : ""}`}>
            <input
              type="radio"
              name={prompt.id}
              checked={checked}
              onChange={() => onChange(opt)}
            />
            <span className="w-opt__dot">
              {checked && <span className="w-opt__dot-inner" />}
            </span>
            <span className="w-opt__lbl">{opt}</span>
          </label>
        );
      })}
    </div>
  );
}

/* ---------- Radio with "Other" text input + optional inline detail ---------- */
function WRadioOther({ value, onChange, prompt }) {
  const v = value && typeof value === "object" ? value : { value: "", other: "" };
  const selected = v.value;
  const showOther = prompt.otherTrigger && prompt.otherTrigger.includes(selected);
  const detail = prompt.optionDetails && selected ? prompt.optionDetails[selected] : null;

  const setVal = (selectedOpt) => onChange({ value: selectedOpt, other: v.other || "" });
  const setOther = (txt) => onChange({ value: selected, other: txt });

  return (
    <div className="w-radio">
      {prompt.options.map(opt => {
        const checked = selected === opt;
        return (
          <label key={opt} className={`w-opt ${checked ? "w-opt--on" : ""}`}>
            <input type="radio" name={prompt.id} checked={checked} onChange={() => setVal(opt)} />
            <span className="w-opt__dot">{checked && <span className="w-opt__dot-inner" />}</span>
            <span className="w-opt__lbl">{opt}</span>
          </label>
        );
      })}
      {detail && (
        <div className="w-opt-detail">
          <div className="w-opt-detail__attr">{detail.attribution}</div>
          <div className="w-opt-detail__quote">“{detail.quote}”</div>
        </div>
      )}
      {showOther && (
        <div className="w-opt-other">
          {prompt.otherLabel && (
            <label className="w-opt-other__label">{prompt.otherLabel}</label>
          )}
          {prompt.otherType === "textarea" ? (
            <textarea
              value={v.other || ""}
              onChange={e => setOther(e.target.value)}
              placeholder={prompt.otherPlaceholder || ""}
              rows={prompt.otherRows || 3}
            />
          ) : (
            <input
              type="text"
              className="w-text"
              value={v.other || ""}
              onChange={e => setOther(e.target.value)}
              placeholder={prompt.otherPlaceholder || "Please specify…"}
            />
          )}
        </div>
      )}
    </div>
  );
}

/* ---------- Checks (multi-select) ---------- */
function WChecks({ value, onChange, prompt }) {
  const arr = Array.isArray(value) ? value : [];
  const isNA = prompt.naOption && arr.includes("N/A");

  const toggle = (opt) => {
    if (arr.includes(opt)) onChange(arr.filter(x => x !== opt));
    else onChange([...arr, opt]);
  };

  const toggleNA = () => {
    if (isNA) onChange([]);
    else onChange(["N/A"]);
  };

  // "Other" free-text support — hidden when N/A is active
  const otherValue = (!isNA && prompt.otherOption)
    ? (arr.find(x => !prompt.options.includes(x) && x !== "N/A") ?? null)
    : null;
  const otherChecked = otherValue !== null;

  const toggleOther = () => {
    if (otherChecked) onChange(arr.filter(x => prompt.options.includes(x)));
    else onChange([...arr, ""]);
  };
  const setOtherText = (text) => {
    const base = arr.filter(x => prompt.options.includes(x));
    onChange([...base, text]);
  };

  return (
    <div className="w-checks">
      {!isNA && prompt.options.map(opt => {
        const checked = arr.includes(opt);
        return (
          <label key={opt} className={`w-chk ${checked ? "w-chk--on" : ""}`}>
            <input type="checkbox" checked={checked} onChange={() => toggle(opt)} />
            <span className="w-chk__box">
              {checked && <Icon name="check" size={12} />}
            </span>
            <span className="w-chk__lbl">{opt}</span>
          </label>
        );
      })}
      {!isNA && prompt.otherOption && (
        <div className="w-chk-other">
          <label className={`w-chk ${otherChecked ? "w-chk--on" : ""}`}>
            <input type="checkbox" checked={otherChecked} onChange={toggleOther} />
            <span className="w-chk__box">
              {otherChecked && <Icon name="check" size={12} />}
            </span>
            <span className="w-chk__lbl">Other</span>
          </label>
          {otherChecked && (
            <input
              type="text"
              className="w-chk-other__input"
              value={otherValue}
              onChange={e => setOtherText(e.target.value)}
              placeholder="Please specify…"
              autoFocus
            />
          )}
        </div>
      )}
      {prompt.naOption && (
        <label className={`w-chk w-chk--na ${isNA ? "w-chk--on" : ""}`}>
          <input type="checkbox" checked={isNA} onChange={toggleNA} />
          <span className="w-chk__box">
            {isNA && <Icon name="check" size={12} />}
          </span>
          <span className="w-chk__lbl">N/A</span>
        </label>
      )}
    </div>
  );
}

/* ---------- Repeating list ---------- */
function WList({ value, onChange, prompt, hideAdd }) {
  const items = Array.isArray(value) ? value : [];
  const setItem = (i, val) => {
    const next = [...items];
    next[i] = val;
    onChange(next);
  };
  const removeItem = (i) => {
    const next = items.filter((_, idx) => idx !== i);
    onChange(next);
  };
  const addItem = () => onChange([...items, ""]);

  const displayItems = items.length === 0 ? [""] : items;

  return (
    <div className="w-list">
      {displayItems.map((it, i) => (
        <div key={i} className="w-list__row">
          <span className="w-list__bullet">{i + 1}</span>
          <textarea
            className="w-list__field"
            rows={2}
            value={it}
            onChange={e => {
              if (items.length === 0) onChange([e.target.value]);
              else setItem(i, e.target.value);
            }}
            placeholder={prompt.itemPlaceholder || "Add an item…"}
          />
          {items.length > 1 && (
            <button className="w-list__remove" onClick={() => removeItem(i)} aria-label="Remove">
              ×
            </button>
          )}
        </div>
      ))}
      {!hideAdd && (
        <button className="w-list__add" onClick={addItem} type="button">
          + Add another
        </button>
      )}
    </div>
  );
}

/* ---------- Theory of Change builder ---------- */
function WTocBuilder({ value, onChange, prompt }) {
  const data = value && typeof value === "object" ? value : {};
  const setSection = (key, items) => onChange({ ...data, [key]: items });
  const [open, setOpen] = React.useState(false);

  // count helpers
  const countFor = (key) => {
    const items = data[key];
    return Array.isArray(items) ? items.filter(x => (x || "").toString().trim()).length : 0;
  };

  // outcome columns are merged into one "Outcomes" pillar in the mini-chain
  const totalOutcomes = countFor("outcomes_im") + countFor("outcomes_it") + countFor("outcomes_lt");
  const pillarCounts = {
    inputs:     countFor("inputs"),
    activities: countFor("activities"),
    outputs:    countFor("outputs"),
    outcomes:   totalOutcomes,
    impact:     countFor("impact")
  };
  const totalChain = Object.values(pillarCounts).reduce((a, b) => a + b, 0);
  const assumptionsCount = countFor("assumptions");

  // Pillar order for mini-chain
  const pillars = [
    { key: "inputs",     label: "Inputs" },
    { key: "activities", label: "Activities" },
    { key: "outputs",    label: "Outputs" },
    { key: "outcomes",   label: "Outcomes" },
    { key: "impact",     label: "Impact" }
  ];

  return (
    <div className="w-tocv">
      {/* Compact mini-chain preview */}
      <div className="w-tocv__chain">
        {pillars.map((p, i) => (
          <React.Fragment key={p.key}>
            <div className="w-tocv__pillar">
              <div className="w-tocv__pillar-label">{p.label}</div>
              <div className="w-tocv__pillar-count">
                {pillarCounts[p.key]}<span>item{pillarCounts[p.key] === 1 ? "" : "s"}</span>
              </div>
            </div>
            {i < pillars.length - 1 && <div className="w-tocv__arrow">→</div>}
          </React.Fragment>
        ))}
      </div>

      {assumptionsCount > 0 && (
        <div className="w-tocv__assumptions-preview">
          <span className="w-tocv__assumptions-label">Assumptions</span>
          <span className="w-tocv__assumptions-count">{assumptionsCount} item{assumptionsCount === 1 ? "" : "s"}</span>
        </div>
      )}

      <button
        type="button"
        className="w-tocv__open"
        onClick={() => setOpen(true)}
      >
        {totalChain === 0 && assumptionsCount === 0
          ? <><span style={{ fontSize: 14 }}>＋</span> Build your Theory of Change</>
          : <><Icon name="pencil" size={13} /> Edit your Theory of Change</>}
      </button>

      {open && (
        <TocModal
          prompt={prompt}
          data={data}
          onChange={onChange}
          onClose={() => setOpen(false)}
        />
      )}
    </div>
  );
}

/* ---------- Theory of Change modal editor ---------- */
function TocModal({ prompt, data, onChange, onClose }) {
  // close on Escape
  React.useEffect(() => {
    const handler = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", handler);
    document.body.style.overflow = "hidden";
    return () => {
      window.removeEventListener("keydown", handler);
      document.body.style.overflow = "";
    };
  }, [onClose]);

  const setSection = (key, items) => onChange({ ...data, [key]: items });

  const sectionByKey = (key) => prompt.sections.find(s => s.key === key);

  // Column layout: Inputs | Activities | Outputs | Outcomes (3 sub-rows) | Impact
  // Below: Assumptions full-width
  const columns = [
    { key: "inputs",     label: "Inputs",     desc: sectionByKey("inputs").desc },
    { key: "activities", label: "Activities", desc: sectionByKey("activities").desc },
    { key: "outputs",    label: "Outputs",    desc: sectionByKey("outputs").desc },
    { key: "_outcomes_compound", label: "Outcomes", desc: "Changes in knowledge, attitude, behavior, norms across three time horizons.",
      subSections: [
        { key: "outcomes_im", label: "Immediate (0–3 months)" },
        { key: "outcomes_it", label: "Intermediate (6 mo–3 yrs)" },
        { key: "outcomes_lt", label: "Long-term (3–5+ yrs)" }
      ]
    },
    { key: "impact",     label: "Impact",     desc: sectionByKey("impact").desc }
  ];

  return (
    <div className="toc-modal-backdrop" onClick={onClose}>
      <div className="toc-modal" onClick={e => e.stopPropagation()}>
        <div className="toc-modal__header">
          <div>
            <div className="toc-modal__eyebrow">Step 1.2 · Theory of Change</div>
            <div className="toc-modal__title">Map how your project produces change</div>
          </div>
          <button className="toc-modal__close" onClick={onClose} aria-label="Close">×</button>
        </div>

        <div className="toc-modal__body">
          <div className="toc-chain">
            {columns.map((col, i) => (
              <React.Fragment key={col.key}>
                <div className="toc-col">
                  <div className="toc-col__head">
                    <div className="toc-col__label">{col.label}</div>
                    <div className="toc-col__desc">{col.desc}</div>
                  </div>
                  {col.subSections ? (
                    col.subSections.map(sub => (
                      <div className="toc-subcol" key={sub.key}>
                        <div className="toc-subcol__label">{sub.label}</div>
                        <WList
                          value={Array.isArray(data[sub.key]) ? data[sub.key] : []}
                          onChange={(items) => setSection(sub.key, items)}
                          prompt={{ id: sub.key, itemPlaceholder: sectionByKey(sub.key).itemPlaceholder }}
                        />
                      </div>
                    ))
                  ) : (
                    <WList
                      value={Array.isArray(data[col.key]) ? data[col.key] : []}
                      onChange={(items) => setSection(col.key, items)}
                      prompt={{ id: col.key, itemPlaceholder: sectionByKey(col.key).itemPlaceholder }}
                      hideAdd={col.key === "impact"}
                    />
                  )}
                </div>
                {i < columns.length - 1 && <div className="toc-arrow">→</div>}
              </React.Fragment>
            ))}
          </div>

          <div className="toc-assumptions">
            <div className="toc-assumptions__head">
              <div className="toc-assumptions__label">Assumptions</div>
              <div className="toc-assumptions__desc">{sectionByKey("assumptions").desc} <em>These run beneath the chain and must hold for change to occur.</em></div>
            </div>
            <WList
              value={Array.isArray(data["assumptions"]) ? data["assumptions"] : []}
              onChange={(items) => setSection("assumptions", items)}
              prompt={{ id: "assumptions", itemPlaceholder: sectionByKey("assumptions").itemPlaceholder }}
            />
          </div>
        </div>

        <div className="toc-modal__footer">
          <div className="toc-modal__footer-note">Changes save automatically.</div>
          <button className="btn btn--primary" onClick={onClose}>Done</button>
        </div>
      </div>
    </div>
  );
}

/* ---------- Dispatcher ---------- */
function PromptWidget({ value, onChange, prompt, allAnswers }) {
  switch (prompt.type) {
    case "textarea":     return <WTextarea     value={value} onChange={onChange} prompt={prompt} />;
    case "text":         return <WText         value={value} onChange={onChange} prompt={prompt} />;
    case "radio":        return <WRadio        value={value} onChange={onChange} prompt={prompt} />;
    case "radio-other":  return <WRadioOther   value={value} onChange={onChange} prompt={prompt} />;
    case "checks":       return <WChecks       value={value} onChange={onChange} prompt={prompt} />;
    case "list":         return <WList         value={value} onChange={onChange} prompt={prompt} />;
    case "toc-builder":  return <WTocBuilder   value={value} onChange={onChange} prompt={prompt} />;
    case "outcomes-timeline":    return <WOutcomesTimeline    value={value} onChange={onChange} prompt={prompt} />;
    case "sampling-tree":        return <WSamplingTree        value={value} onChange={onChange} prompt={prompt} />;
    case "toc-per-item":         return <WTocPerItem          value={value} onChange={onChange} prompt={prompt} allAnswers={allAnswers} />;
    case "toc-method-planner":   return <WTocMethodPlanner    value={value} onChange={onChange} prompt={prompt} allAnswers={allAnswers} />;
    case "toc-item-selector":    return <WTocItemSelector     value={value} onChange={onChange} allAnswers={allAnswers} />;
    case "method-per-item":      return <WMethodPerItem       value={value} onChange={onChange} prompt={prompt} allAnswers={allAnswers} />;
    case "toc-method-selector":  return <WTocMethodSelector   value={value} onChange={onChange} prompt={prompt} allAnswers={allAnswers} />;
    case "coverage-review":      return <WCoverageReview      allAnswers={allAnswers} />;
    default:             return <WTextarea    value={value} onChange={onChange} prompt={prompt} />;
  }
}

/* =========================================================================
   Outcomes timeline widget — three phase columns on a time axis
   Answer: { im: string[], it: string[], lt: string[] }
   ========================================================================= */
function WOutcomesTimeline({ value, onChange, prompt }) {
  const data = value && typeof value === "object" ? value : {};
  const setPhase = (key, items) => onChange({ ...data, [key]: items });

  const phases = prompt.phases;

  return (
    <div className="w-tl">
      {/* Phase columns */}
      <div className="w-tl__columns">
        {phases.map((ph, i) => {
          const items = Array.isArray(data[ph.key]) ? data[ph.key] : [];
          return (
            <div className="w-tl__col" key={ph.key}>
              <div className="w-tl__col-head">
                <div className="w-tl__col-label">{ph.label}</div>
                <div className="w-tl__col-window">{ph.subtitle}</div>
                <div className="w-tl__col-desc">{ph.desc}</div>
              </div>
              <div className="w-tl__col-body">
                <WList
                  value={items}
                  onChange={(it) => setPhase(ph.key, it)}
                  prompt={{ id: ph.key, itemPlaceholder: ph.itemPlaceholder || prompt.itemPlaceholder }}
                />
              </div>
              <div className="w-tl__col-anchor" style={{ background: prompt.colorHex || "var(--isd-blue)" }} />
            </div>
          );
        })}
      </div>
      {/* Time axis */}
      <div className="w-tl__axis">
        <div className="w-tl__axis-line" />
        {phases.map((ph, i) => (
          <div className="w-tl__axis-tick" key={ph.key} style={{ left: `${(i + 0.5) * (100 / phases.length)}%` }}>
            <div className="w-tl__axis-tick-mark" />
            <div className="w-tl__axis-tick-lbl">{ph.tickLabel || ph.subtitle}</div>
          </div>
        ))}
      </div>
      <div className="w-tl__axis-bookends">
        <span>{prompt.startLabel || "Project launch"}</span>
        <span>{prompt.endLabel || "Long-term"}</span>
      </div>
    </div>
  );
}

/* =========================================================================
   Sampling decision tree widget — branching Q&A; supports multi-select
   so analysts can pick both approaches and multiple methods.
   Answer: { approaches: string[], methods: string[] }
   ========================================================================= */
function WSamplingTree({ value, onChange, prompt }) {
  // Back-compat: migrate older single-select shape on read
  const v = (() => {
    if (!value || typeof value !== "object") return { approaches: [], methods: [] };
    if (Array.isArray(value.approaches) || Array.isArray(value.methods)) {
      return { approaches: value.approaches || [], methods: value.methods || [] };
    }
    return {
      approaches: value.approach ? [value.approach] : [],
      methods:    value.method   ? [value.method]   : []
    };
  })();

  const tree = prompt.tree;
  const selectedBranches = tree.branches.filter(b => v.approaches.includes(b.category));
  const hasAnyBranch = selectedBranches.length > 0;
  const bothSelected = selectedBranches.length === tree.branches.length;

  const toggleBranch = (branch) => {
    let newApproaches;
    if (v.approaches.includes(branch.category)) {
      newApproaches = v.approaches.filter(a => a !== branch.category);
    } else {
      newApproaches = [...v.approaches, branch.category];
    }
    // when removing a branch, drop its methods from selection
    const stillValidMethods = v.methods.filter(m =>
      tree.branches.some(b => newApproaches.includes(b.category) && b.leaves.some(l => l.value === m))
    );
    onChange({ approaches: newApproaches, methods: stillValidMethods });
  };

  const toggleMethod = (m) => {
    const newMethods = v.methods.includes(m)
      ? v.methods.filter(x => x !== m)
      : [...v.methods, m];
    onChange({ approaches: v.approaches, methods: newMethods });
  };

  const reset = () => onChange({ approaches: [], methods: [] });

  return (
    <div className="w-tree">
      <div className="w-tree__question">
        <span className="w-tree__q-icon">?</span>
        <div className="w-tree__q-text">{tree.question}</div>
      </div>

      <div className="w-tree__hint">Pick one — or both, if your project mixes methods across data streams.</div>

      {/* Branch chooser — multi-select */}
      <div className="w-tree__branches">
        {tree.branches.map((b, i) => {
          const active = v.approaches.includes(b.category);
          return (
            <button
              key={i}
              className={`w-tree__branch ${active ? "w-tree__branch--on" : ""}`}
              onClick={() => toggleBranch(b)}
              aria-pressed={active}
            >
              <span className="w-tree__branch-check">
                {active && <Icon name="check" size={12} />}
              </span>
              <div className="w-tree__branch-body">
                <div className="w-tree__branch-answer">{b.answer}</div>
                <div className="w-tree__branch-cat">{b.category}</div>
              </div>
            </button>
          );
        })}
      </div>

      {bothSelected && (
        <div className="w-tree__mix-note">
          <Icon name="info" size={13} /> You&rsquo;ve chosen a <strong>mixed approach</strong> — methods from both can be selected below.
        </div>
      )}

      {/* Method leaves */}
      {hasAnyBranch && (
        <div className="w-tree__leaves">
          <div className="w-tree__leaves-head">
            <span>Choose your specific method{v.methods.length === 1 ? "" : "s"}{selectedBranches.length === 1 ? <> for <strong>{selectedBranches[0].category}</strong></> : ""}</span>
            {(v.approaches.length > 0 || v.methods.length > 0) && (
              <button className="w-tree__reset" onClick={reset}>reset</button>
            )}
          </div>

          {selectedBranches.map(branch => (
            <div className="w-tree__branch-section" key={branch.category}>
              {bothSelected && (
                <div className="w-tree__branch-section-label">{branch.category}</div>
              )}
              <div className="w-tree__leaf-grid">
                {branch.leaves.map(leaf => {
                  const sel = v.methods.includes(leaf.value);
                  return (
                    <button
                      key={leaf.value}
                      className={`w-tree__leaf ${sel ? "w-tree__leaf--on" : ""}`}
                      onClick={() => toggleMethod(leaf.value)}
                      aria-pressed={sel}
                    >
                      <div className="w-tree__leaf-name">{leaf.value}</div>
                      <div className="w-tree__leaf-desc">{leaf.desc}</div>
                      {sel && <div className="w-tree__leaf-check"><Icon name="check" size={12} /></div>}
                    </button>
                  );
                })}
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}
function isPromptAnswered(prompt, answer) {
  if (answer == null) return false;
  switch (prompt.type) {
    case "textarea":
    case "text":
      return typeof answer === "string" && answer.trim().length > 0;
    case "radio":
      return typeof answer === "string" && answer.length > 0;
    case "radio-other":
      return answer && typeof answer === "object" && (answer.value || "").length > 0;
    case "checks":
    case "list":
      return Array.isArray(answer) && answer.filter(x => (x || "").toString().trim()).length > 0;
    case "toc-builder":
    case "outcomes-timeline":
      if (!answer || typeof answer !== "object") return false;
      return Object.values(answer).some(arr => Array.isArray(arr) && arr.filter(x => (x || "").trim()).length > 0);
    case "sampling-tree": {
      if (!answer || typeof answer !== "object") return false;
      const approaches = Array.isArray(answer.approaches) ? answer.approaches : (answer.approach ? [answer.approach] : []);
      const methods    = Array.isArray(answer.methods)    ? answer.methods    : (answer.method   ? [answer.method]   : []);
      return approaches.length > 0 || methods.length > 0;
    }
    case "toc-per-item":
      if (!answer || typeof answer !== "object") return false;
      return Object.values(answer).some(item =>
        item && typeof item === "object" && Object.values(item).some(v =>
          Array.isArray(v) ? v.filter(x => (x || "").trim()).length > 0 :
          typeof v === "string" ? v.trim().length > 0 : false
        )
      );
    case "toc-method-planner":
      if (!answer || typeof answer !== "object") return false;
      return Object.values(answer).some(item =>
        item && Array.isArray(item.methods) && item.methods.length > 0
      );
    case "toc-item-selector":
      return Array.isArray(answer) && answer.length > 0;
    case "method-per-item":
      if (!answer || typeof answer !== "object") return false;
      return Object.values(answer).some(item =>
        item && typeof item === "object" && Object.values(item).some(v =>
          Array.isArray(v) ? v.filter(x => (x || "").trim()).length > 0 :
          typeof v === "string" ? v.trim().length > 0 : false
        )
      );
    case "toc-method-selector":
      if (!answer || typeof answer !== "object") return false;
      return Object.values(answer).some(item =>
        item && Array.isArray(item.methods) && item.methods.length > 0
      );
    case "coverage-review":
      return false;
    default:
      return false;
  }
}

function promptPreview(prompt, answer) {
  if (!isPromptAnswered(prompt, answer)) return null;
  switch (prompt.type) {
    case "textarea":
    case "text":
      return answer.toString();
    case "radio":
      return answer;
    case "radio-other":
      return answer.other ? `${answer.value} — ${answer.other}` : answer.value;
    case "checks":
      return answer.join(", ");
    case "list":
      return answer.filter(x => (x || "").trim()).join(" · ");
    case "toc-builder":
    case "outcomes-timeline": {
      const counts = Object.values(answer).reduce((sum, arr) => sum + (Array.isArray(arr) ? arr.filter(x => (x || "").trim()).length : 0), 0);
      return `${counts} item${counts === 1 ? "" : "s"}`;
    }
    case "sampling-tree": {
      const approaches = Array.isArray(answer.approaches) ? answer.approaches : (answer.approach ? [answer.approach] : []);
      const methods    = Array.isArray(answer.methods)    ? answer.methods    : (answer.method   ? [answer.method]   : []);
      if (methods.length) return `${approaches.join(" + ")} → ${methods.join(", ")}`;
      return approaches.join(" + ");
    }
    case "toc-per-item": {
      const itemCount = Object.keys(answer).length;
      return `${itemCount} item${itemCount === 1 ? "" : "s"} with indicators`;
    }
    case "toc-method-planner": {
      const methodSets = Object.values(answer)
        .flatMap(item => (item && Array.isArray(item.methods)) ? item.methods : []);
      const unique = [...new Set(methodSets)];
      return unique.length ? unique.join(", ") : null;
    }
    case "toc-item-selector": {
      const count = answer.length;
      return `${count} item${count === 1 ? "" : "s"} selected`;
    }
    case "method-per-item": {
      const filled = Object.keys(answer).length;
      return filled ? `${filled} item${filled === 1 ? "" : "s"} configured` : null;
    }
    case "toc-method-selector": {
      const allMethods = [...new Set(
        Object.values(answer || {})
          .flatMap(item => Array.isArray(item?.methods) ? item.methods : [])
      )];
      return allMethods.length ? allMethods.join(", ") : null;
    }
    case "coverage-review":
      return null;
    default:
      return null;
  }
}

/* ---------- Report-side renderer ---------- */
function _reportMethodDetailLines(mName, mDetails) {
  if (!mDetails || typeof mDetails !== "object") return [];
  const lines = [];
  const fmt = (arr) => Array.isArray(arr) ? arr.filter(x => (x || "").trim()).join(", ") : "";
  switch (mName) {
    case "Surveys":
      if (fmt(mDetails.types))      lines.push({ label: "Question types",              value: fmt(mDetails.types) });
      if (mDetails.instrument)      lines.push({ label: "Validated instrument",         value: mDetails.instrument });
      if (mDetails.design)          lines.push({ label: "Survey design",                value: mDetails.design });
      break;
    case "Interviews":
      if (mDetails.structure)       lines.push({ label: "Structure",                    value: mDetails.structure });
      if (mDetails.focus)           lines.push({ label: "What interviews will explore", value: mDetails.focus });
      if (mDetails.count)           lines.push({ label: "Number of interviews",         value: mDetails.count });
      break;
    case "Focus groups":
      if (mDetails.composition)     lines.push({ label: "Composition",                  value: mDetails.composition });
      if (mDetails.plan)            lines.push({ label: "What focus groups will surface", value: mDetails.plan });
      break;
    case "Case studies":
      if (fmt(mDetails.rationales)) lines.push({ label: "Selection rationales",         value: fmt(mDetails.rationales) });
      if (mDetails.plan)            lines.push({ label: "Case study plan",              value: mDetails.plan });
      break;
    case "Peer research":
      if (mDetails.where)           lines.push({ label: "Where peer researchers add value", value: mDetails.where });
      break;
    case "Online analytics":
      if (mDetails.platforms)       lines.push({ label: "Platforms",                   value: mDetails.platforms });
      if (fmt(mDetails.awareness))  lines.push({ label: "Awareness metrics",           value: fmt(mDetails.awareness) });
      if (fmt(mDetails.engagement)) lines.push({ label: "Engagement metrics",          value: fmt(mDetails.engagement) });
      break;
  }
  return lines;
}

function ReportAnswer({ prompt, answer }) {
  if (!isPromptAnswered(prompt, answer)) {
    return <div className="report-q__ans report-q__ans--empty">— Not yet completed —</div>;
  }
  switch (prompt.type) {
    case "textarea":
    case "text":
      return <div className="report-q__ans">{answer}</div>;
    case "radio":
      return <div className="report-q__ans"><strong>{answer}</strong></div>;
    case "radio-other": {
      const detail = prompt.optionDetails && prompt.optionDetails[answer.value];
      return (
        <div className="report-q__ans">
          <strong>{answer.value}</strong>
          {answer.other && <span> — {answer.other}</span>}
          {detail && (
            <blockquote className="report-q__definition">
              <p>{detail.quote}</p>
              {detail.attribution && <cite>{detail.attribution}</cite>}
            </blockquote>
          )}
        </div>
      );
    }
    case "checks":
      return (
        <ul className="report-q__list">
          {answer.filter(x => (x || "").trim()).map((x, i) => <li key={i}>{x}</li>)}
        </ul>
      );
    case "list":
      return (
        <ol className="report-q__list report-q__list--ol">
          {answer.filter(x => (x || "").trim()).map((x, i) => <li key={i}>{x}</li>)}
        </ol>
      );
    case "toc-builder": {
      const chainPillars = [
        { label: "Inputs",     keys: ["inputs"] },
        { label: "Activities", keys: ["activities"] },
        { label: "Outputs",    keys: ["outputs"] },
        { label: "Outcomes",   keys: ["outcomes_im", "outcomes_it", "outcomes_lt"],
          subLabels: { outcomes_im: "Immediate", outcomes_it: "Intermediate", outcomes_lt: "Long-term" } },
        { label: "Impact",     keys: ["impact"] }
      ];
      const assumptionItems = (answer.assumptions || []).filter(x => (x || "").trim());
      return (
        <div className="report-toc">
          <div className="report-toc__chain">
            {chainPillars.map((pillar, i) => {
              const rows = pillar.keys.flatMap(k => {
                const subLabel = pillar.subLabels ? pillar.subLabels[k] : null;
                return (answer[k] || []).filter(x => (x || "").trim()).map(text => ({ text, subLabel }));
              });
              return (
                <React.Fragment key={pillar.label}>
                  <div className="report-toc__pillar">
                    <div className="report-toc__pillar-lbl">{pillar.label}</div>
                    {rows.length > 0 ? (
                      <ul className="report-toc__items">
                        {rows.map((r, j) => (
                          <li key={j}>
                            {r.subLabel && <em className="report-toc__sublbl">{r.subLabel}: </em>}
                            {r.text}
                          </li>
                        ))}
                      </ul>
                    ) : <span className="report-toc__empty">—</span>}
                  </div>
                  {i < chainPillars.length - 1 && <div className="report-toc__sep" aria-hidden="true">→</div>}
                </React.Fragment>
              );
            })}
          </div>
          {assumptionItems.length > 0 && (
            <div className="report-toc__assumptions">
              <div className="report-toc__pillar-lbl">Assumptions</div>
              <ul className="report-toc__items">
                {assumptionItems.map((x, j) => <li key={j}>{x}</li>)}
              </ul>
            </div>
          )}
        </div>
      );
    }
    case "outcomes-timeline":
      return (
        <div className="report-q__toc">
          {prompt.phases.map(ph => {
            const items = (answer[ph.key] || []).filter(x => (x || "").trim());
            if (items.length === 0) return null;
            return (
              <div className="report-q__toc-sec" key={ph.key}>
                <div className="report-q__toc-lbl">{ph.label} — {ph.subtitle}</div>
                <ul className="report-q__list">
                  {items.map((x, i) => <li key={i}>{x}</li>)}
                </ul>
              </div>
            );
          })}
        </div>
      );
    case "sampling-tree": {
      const approaches = Array.isArray(answer.approaches) ? answer.approaches : (answer.approach ? [answer.approach] : []);
      const methods    = Array.isArray(answer.methods)    ? answer.methods    : (answer.method   ? [answer.method]   : []);
      return (
        <div className="report-q__ans">
          <strong>{approaches.join(" + ")}</strong>
          {methods.length > 0 && (
            <ul className="report-q__list" style={{ marginTop: 8 }}>
              {methods.map((m, i) => <li key={i}>{m}</li>)}
            </ul>
          )}
        </div>
      );
    }
    case "toc-per-item": {
      const entries = Object.entries(answer);
      if (!entries.length) return <div className="report-q__ans report-q__ans--empty">— Not yet completed —</div>;
      return (
        <div className="report-toc-pi">
          {entries.map(([key, item]) => {
            if (!item || typeof item !== "object") return null;
            const tocSection = prompt.tocSections
              ? prompt.tocSections.find(s => key.startsWith(s.key + "__"))
              : null;
            const sectionLabel = tocSection ? tocSection.label : key;
            const timeframe = tocSection ? tocSection.timeframe : null;
            return (
              <div key={key} className="report-toc-pi__item">
                <div className="report-toc-pi__meta">
                  <strong>{sectionLabel}</strong>{timeframe && <span> — {timeframe}</span>}
                </div>
                {prompt.fields && prompt.fields.map(f => {
                  const fval = item[f.id];
                  if (!fval || (Array.isArray(fval) && !fval.filter(x => (x || "").trim()).length)) return null;
                  return (
                    <div key={f.id} className="report-toc-pi__field">
                      <div className="report-toc-pi__field-label">{f.label}</div>
                      <ReportAnswer prompt={f} answer={fval} />
                    </div>
                  );
                })}
              </div>
            );
          })}
        </div>
      );
    }
    case "toc-method-planner": {
      const entries = Object.entries(answer);
      if (!entries.length) return <div className="report-q__ans report-q__ans--empty">— Not yet completed —</div>;
      return (
        <div className="report-toc-pi">
          {entries.map(([key, item]) => {
            if (!item || !Array.isArray(item.methods) || !item.methods.length) return null;
            return (
              <div key={key} className="report-toc-pi__item">
                <div className="report-toc-pi__meta"><strong>{key.replace(/__\d+$/, "").replace(/_/g, " ")}</strong></div>
                <ul className="report-q__list">
                  {item.methods.map((m, i) => <li key={i}>{m}</li>)}
                </ul>
              </div>
            );
          })}
        </div>
      );
    }
    case "method-per-item": {
      const entries = Object.entries(answer);
      if (!entries.length) return <div className="report-q__ans report-q__ans--empty">— Not yet completed —</div>;
      return (
        <div className="report-toc-pi">
          {entries.map(([key, item]) => {
            if (!item || typeof item !== "object") return null;
            const parts = key.split("__");
            const tocKey = parts.slice(0, -1).join("__");
            const group = TOC_ITEM_GROUPS.find(g => g.tocKey === tocKey);
            const sectionLabel = group ? group.label : tocKey;
            const timeframe = group ? group.timeframe : null;
            return (
              <div key={key} className="report-toc-pi__item">
                <div className="report-toc-pi__meta">
                  <strong>{sectionLabel}</strong>{timeframe && <span> — {timeframe}</span>}
                </div>
                {prompt.fields && prompt.fields.map(f => {
                  const fval = item[f.id];
                  if (!fval || (Array.isArray(fval) && !fval.filter(x => (x || "").trim()).length)) return null;
                  return (
                    <div key={f.id} className="report-toc-pi__field">
                      <div className="report-toc-pi__field-label">{f.label}</div>
                      <ReportAnswer prompt={f} answer={fval} />
                    </div>
                  );
                })}
              </div>
            );
          })}
        </div>
      );
    }
    case "toc-item-selector":
      if (!Array.isArray(answer) || !answer.length) return <div className="report-q__ans report-q__ans--empty">— Not yet completed —</div>;
      return (
        <ul className="report-q__list">
          {answer.map((key, i) => {
            const label = key.replace(/__\d+$/, "").replace(/_/g, " ");
            return <li key={i}>{label} (item {parseInt(key.match(/\d+$/)?.[0] ?? "0") + 1})</li>;
          })}
        </ul>
      );
    case "toc-method-selector": {
      const entries = Object.entries(answer || {});
      if (!entries.length) return <div className="report-q__ans report-q__ans--empty">— Not yet completed —</div>;
      return (
        <div className="report-toc-pi">
          {entries.map(([key, item]) => {
            if (!item || !Array.isArray(item.methods) || !item.methods.length) return null;
            const tocKey = key.split("__")[0];
            const group = TOC_ITEM_GROUPS.find(g => g.tocKey === tocKey);
            const sectionLabel = group ? group.label : tocKey;
            const timeframe = group ? (group.timeframe || null) : null;
            const methodList = item.other ? [...item.methods, item.other] : item.methods;
            const details = (item.details && typeof item.details === "object") ? item.details : {};
            return (
              <div key={key} className="report-toc-pi__item">
                <div className="report-toc-pi__meta">
                  <strong>{sectionLabel}</strong>{timeframe && <span> — {timeframe}</span>}
                </div>
                <ul className="report-q__list">
                  {methodList.map((m, mi) => <li key={mi}>{m}</li>)}
                </ul>
                {Object.keys(details).map(mName => {
                  const lines = _reportMethodDetailLines(mName, details[mName]);
                  if (!lines.length) return null;
                  return (
                    <div key={mName} className="report-toc-pi__method-details">
                      <div className="report-toc-pi__method-name">{mName}</div>
                      <dl className="report-toc-pi__method-dl">
                        {lines.map((line, i) => (
                          <React.Fragment key={i}>
                            <dt>{line.label}</dt>
                            <dd>{line.value}</dd>
                          </React.Fragment>
                        ))}
                      </dl>
                    </div>
                  );
                })}
              </div>
            );
          })}
        </div>
      );
    }
    case "coverage-review":
      return <div className="report-q__ans report-q__ans--empty">— See coverage table in toolkit —</div>;
    default:
      return <div className="report-q__ans">{String(answer)}</div>;
  }
}

/* =========================================================================
   WTocPerItem — per-TOC-item indicator builder
   Used in 2.1 (outputs), 2.2 (outcomes), 2.3 (impact).
   Answer shape: { "outputs__0": { types: [], indicators: [], ... }, ... }
   ========================================================================= */
function tocTypeColor(key) {
  if (key.startsWith("outputs")) return "#0068B2";   /* isd-blue */
  if (key.startsWith("outcomes")) return "#E76863";  /* isd-coral */
  if (key.startsWith("impact")) return "#4C4193";    /* isd-purple */
  return null;
}

function WTocPerItem({ value, onChange, prompt, allAnswers }) {
  const data = (value && typeof value === "object") ? value : {};
  const toc = (allAnswers && allAnswers.toc) ? allAnswers.toc : {};

  const items = [];
  for (const section of (prompt.tocSections || [])) {
    const tocItems = (toc[section.key] || []).filter(x => (x || "").trim());
    tocItems.forEach((text, idx) => {
      items.push({
        key: `${section.key}__${idx}`,
        sectionLabel: section.label,
        timeframe: section.timeframe || null,
        text
      });
    });
  }

  const setItem = (key, val) => onChange({ ...data, [key]: val });

  if (items.length === 0) {
    return (
      <div className="toc-pi__empty">
        <Icon name="info" size={15} />
        No items from your Theory of Change yet — return to Step 1.2 to add outputs, outcomes, and impact.
      </div>
    );
  }

  return (
    <div className="toc-pi">
      {items.map(item => (
        <TocItemBlock
          key={item.key}
          item={item}
          value={data[item.key] || {}}
          onChange={val => setItem(item.key, val)}
          fields={prompt.fields || []}
          color={tocTypeColor(item.key)}
        />
      ))}
    </div>
  );
}

function TocItemBlock({ item, value, onChange, fields, color }) {
  const [open, setOpen] = useState(true);
  const setField = (id, val) => onChange({ ...value, [id]: val });

  return (
    <div className={`toc-pi__block ${open ? "toc-pi__block--open" : ""}`}>
      <button type="button" className="toc-pi__header" onClick={() => setOpen(o => !o)}>
        <div className="toc-pi__header-meta">
          <span className="toc-pi__cat" style={color ? { background: color, color: "#fff" } : {}}>{item.sectionLabel}</span>
          {item.timeframe && <span className="toc-pi__tf">{item.timeframe}</span>}
        </div>
        <div className="toc-pi__item-text">{item.text}</div>
        <span className="toc-pi__chevron" aria-hidden="true">{open ? "▲" : "▼"}</span>
      </button>
      {open && (
        <div className="toc-pi__fields">
          {fields.map(f => (
            <div className="toc-pi__field" key={f.id}>
              <label className="toc-pi__field-label">{f.label}</label>
              <PromptWidget prompt={f} value={value[f.id]} onChange={val => setField(f.id, val)} />
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

/* =========================================================================
   WTocMethodPlanner — per-output/outcome method selector
   Used in 3.1. Answer shape:
   { "outputs__0": { methods: ["Surveys"] }, "outcomes_im__0": { methods: [...] }, ... }
   ========================================================================= */
const AVAILABLE_METHODS = [
  "Surveys",
  "Interviews",
  "Focus groups",
  "Case studies",
  "Peer research",
  "Online analytics"
];

function WTocMethodPlanner({ value, onChange, allAnswers }) {
  const data = (value && typeof value === "object") ? value : {};
  const toc = (allAnswers && allAnswers.toc) ? allAnswers.toc : {};
  const outputIndicators   = (allAnswers && allAnswers.output_indicators)  || {};
  const outcomeIndicators  = (allAnswers && allAnswers.outcome_indicators) || {};

  const items = [];

  (toc.outputs || []).filter(x => (x || "").trim()).forEach((text, idx) => {
    const key = `outputs__${idx}`;
    const ind = outputIndicators[key] || {};
    items.push({ key, category: "Output", text, indicators: (ind.indicators || []).filter(x => (x || "").trim()) });
  });

  const outcomeGroups = [
    { tocKey: "outcomes_im", label: "Immediate outcome",     timeframe: "0–3 months" },
    { tocKey: "outcomes_it", label: "Intermediate outcome",  timeframe: "6 mo – 3 yrs" },
    { tocKey: "outcomes_lt", label: "Long-term outcome",     timeframe: "3–5+ years" },
    { tocKey: "impact",      label: "Impact",                timeframe: null }
  ];
  outcomeGroups.forEach(({ tocKey, label, timeframe }) => {
    (toc[tocKey] || []).filter(x => (x || "").trim()).forEach((text, idx) => {
      const key = `${tocKey}__${idx}`;
      const ind = outcomeIndicators[key] || {};
      items.push({ key, category: label, timeframe, text, indicators: (ind.indicators || []).filter(x => (x || "").trim()) });
    });
  });

  const setItem = (key, val) => onChange({ ...data, [key]: val });

  if (items.length === 0) {
    return (
      <div className="toc-pi__empty">
        <Icon name="info" size={15} />
        Complete your Theory of Change (Step 1.2) and indicator settings (Steps 2.1–2.2) to use the method planner.
      </div>
    );
  }

  return (
    <div className="toc-mp">
      {items.map(item => (
        <MethodPlannerBlock
          key={item.key}
          item={item}
          value={data[item.key] || {}}
          onChange={val => setItem(item.key, val)}
          color={tocTypeColor(item.key)}
        />
      ))}
    </div>
  );
}

function MethodPlannerBlock({ item, value, onChange, color }) {
  const [open, setOpen] = useState(true);
  const methods = Array.isArray(value.methods) ? value.methods : [];

  const toggleMethod = (m) => {
    const next = methods.includes(m) ? methods.filter(x => x !== m) : [...methods, m];
    onChange({ ...value, methods: next });
  };

  return (
    <div className={`toc-mp__block ${open ? "toc-mp__block--open" : ""}`}>
      <button type="button" className="toc-mp__header" onClick={() => setOpen(o => !o)}>
        <div className="toc-mp__header-meta">
          <span className="toc-mp__cat" style={color ? { background: color, color: "#fff" } : {}}>{item.category}</span>
          {item.timeframe && <span className="toc-mp__tf">{item.timeframe}</span>}
        </div>
        <div className="toc-mp__item-text">{item.text}</div>
        {item.indicators.length > 0 && (
          <div className="toc-mp__ind-row">
            <span className="toc-mp__ind-eyebrow">Indicators: </span>
            {item.indicators.map((ind, i) => (
              <span key={i} className="toc-mp__ind-chip">{ind}</span>
            ))}
          </div>
        )}
        <span className="toc-mp__chevron" aria-hidden="true">{open ? "▲" : "▼"}</span>
      </button>
      {open && (
        <div className="toc-mp__methods">
          <p className="toc-mp__methods-label">Which method(s) will you use to collect data for this?</p>
          <div className="toc-mp__method-grid">
            {AVAILABLE_METHODS.map(m => {
              const on = methods.includes(m);
              return (
                <button
                  key={m}
                  type="button"
                  className={`toc-mp__method-btn ${on ? "toc-mp__method-btn--on" : ""}`}
                  onClick={() => toggleMethod(m)}
                >
                  {on && <Icon name="check" size={12} />}
                  {m}
                </button>
              );
            })}
          </div>
          {methods.length > 0 && (
            <p className="toc-mp__methods-hint">
              Find detailed guidance on {methods.join(", ")} in the sections below (3.{methods.length > 1 ? "3–3.8" : "3–3.8"}).
            </p>
          )}
        </div>
      )}
    </div>
  );
}

/* =========================================================================
   WMethodPerItem — per-selected-item design questions for a data collection method.
   Reads allAnswers[prompt.scopeKey] to know which items were selected, then
   renders one TocItemBlock per item with the method's fields inside.
   Answer shape: { "outputs__0": { types: [...], design: "..." }, ... }
   ========================================================================= */
const TOC_ITEM_GROUPS = [
  { tocKey: "outputs",      label: "Output" },
  { tocKey: "outcomes_im",  label: "Immediate outcome",    timeframe: "0–3 months" },
  { tocKey: "outcomes_it",  label: "Intermediate outcome", timeframe: "6 mo – 3 yrs" },
  { tocKey: "outcomes_lt",  label: "Long-term outcome",    timeframe: "3–5+ years" },
  { tocKey: "impact",       label: "Impact" },
];

function WMethodPerItem({ value, onChange, prompt, allAnswers }) {
  const data = (value && typeof value === "object") ? value : {};
  const toc = (allAnswers && allAnswers.toc) ? allAnswers.toc : {};
  const selectedKeys = Array.isArray(allAnswers && allAnswers[prompt.scopeKey])
    ? allAnswers[prompt.scopeKey]
    : [];

  const items = selectedKeys.map(key => {
    const parts = key.split("__");
    const tocKey = parts.slice(0, -1).join("__");
    const idx = parseInt(parts[parts.length - 1]);
    const group = TOC_ITEM_GROUPS.find(g => g.tocKey === tocKey);
    const text = ((toc[tocKey] || [])[idx] || "").trim() || key;
    return { key, sectionLabel: group ? group.label : tocKey, timeframe: group ? (group.timeframe || null) : null, text };
  });

  if (items.length === 0) {
    return (
      <div className="toc-pi__empty">
        <Icon name="info" size={15} />
        Select outputs, outcomes and impacts above to answer design questions for each one.
      </div>
    );
  }

  const setItem = (key, val) => onChange({ ...data, [key]: val });

  return (
    <div className="toc-pi">
      {items.map(item => (
        <TocItemBlock
          key={item.key}
          item={item}
          value={data[item.key] || {}}
          onChange={val => setItem(item.key, val)}
          fields={prompt.fields || []}
          color={tocTypeColor(item.key)}
        />
      ))}
    </div>
  );
}

/* =========================================================================
   WTocItemSelector — multi-select checkboxes of TOC outputs + outcomes
   Used at the bottom of each method section (3.3–3.8).
   Answer shape: string[]  — array of item keys e.g. ["outputs__0", "outcomes_im__1"]
   ========================================================================= */
function WTocItemSelector({ value, onChange, allAnswers }) {
  const selected = Array.isArray(value) ? value : [];
  const toc = (allAnswers && allAnswers.toc) ? allAnswers.toc : {};

  const groups = [
    { tocKey: "outputs",      label: "Outputs" },
    { tocKey: "outcomes_im",  label: "Immediate outcomes",    timeframe: "0–3 months" },
    { tocKey: "outcomes_it",  label: "Intermediate outcomes", timeframe: "6 mo – 3 yrs" },
    { tocKey: "outcomes_lt",  label: "Long-term outcomes",    timeframe: "3–5+ years" },
    { tocKey: "impact",       label: "Impact" },
  ];

  const toggle = (key) => {
    const next = selected.includes(key) ? selected.filter(k => k !== key) : [...selected, key];
    onChange(next);
  };

  const allItems = groups.flatMap(g =>
    (toc[g.tocKey] || [])
      .filter(x => (x || "").trim())
      .map((text, idx) => ({ key: `${g.tocKey}__${idx}`, groupLabel: g.label, timeframe: g.timeframe, text }))
  );

  if (allItems.length === 0) {
    return (
      <div className="toc-pi__empty">
        <Icon name="info" size={15} />
        Complete your Theory of Change (Step 1.2) to see your outputs and outcomes here.
      </div>
    );
  }

  return (
    <div className="toc-is">
      {groups.map(g => {
        const items = allItems.filter(i => i.key.startsWith(g.tocKey + "__"));
        if (!items.length) return null;
        const gColor = tocTypeColor(g.tocKey);
        return (
          <div key={g.tocKey} className="toc-is__group">
            <div className="toc-is__group-label" style={gColor ? { color: gColor } : {}}>
              {g.label}{g.timeframe && <span className="toc-is__group-tf"> — {g.timeframe}</span>}
            </div>
            {items.map(item => {
              const on = selected.includes(item.key);
              return (
                <label key={item.key} className={`toc-is__item ${on ? "toc-is__item--on" : ""}`}
                  style={on && gColor ? { borderColor: gColor } : {}}>
                  <input
                    type="checkbox"
                    checked={on}
                    onChange={() => toggle(item.key)}
                    className="toc-is__checkbox"
                  />
                  <span className="toc-is__item-dot"
                    style={on && gColor ? { background: gColor, borderColor: gColor } : {}}>
                    {on && <Icon name="check" size={11} />}
                  </span>
                  <span className="toc-is__item-text">{item.text}</span>
                </label>
              );
            })}
          </div>
        );
      })}
    </div>
  );
}

/* =========================================================================
   WTocMethodSelector — per-TOC-item method selector for sections 3.3–3.5.
   For each item in the specified tocKeys, renders a collapsible block where
   the user picks which data collection methods apply (+ Other free text).
   Answer shape: { "outputs__0": { methods: string[], other: string, details: {...} }, ... }
   ========================================================================= */
const COLLECTION_METHODS = [
  "Surveys",
  "Interviews",
  "Focus groups",
  "Case studies",
  "Peer research",
  "Online analytics",
];

/* --- Method-detail question definitions (restored from original toolkit) --- */
const SURVEY_TYPES    = ["Rating / Likert", "Knowledge (multiple choice)", "Intention", "Frequency", "Barriers", "Open-ended", "Demographic"];
const CS_RATIONALES   = ["Typical", "Extreme", "Critical", "Diverse"];
const AWARENESS_OPTS  = ["Impressions", "Reach", "Video views", "Frequency", "Share of voice"];
const ENGAGEMENT_OPTS = ["Likes", "Click-through rate", "Saves / bookmarks", "Shares / reposts", "Comments & DMs", "Completion rate", "Dwell time / scroll depth / downloads"];

function MdCheckGroup({ value, onChange, opts, withOther }) {
  const arr = Array.isArray(value) ? value : [];
  const toggle = (opt) => onChange(arr.includes(opt) ? arr.filter(x => x !== opt) : [...arr, opt]);
  const otherVal    = withOther ? (arr.find(x => !opts.includes(x)) ?? null) : null;
  const otherOn     = otherVal !== null;
  const toggleOther = () => {
    if (otherOn) onChange(arr.filter(x => opts.includes(x)));
    else onChange([...arr, ""]);
  };
  const setOtherText = (text) => onChange([...arr.filter(x => opts.includes(x)), text]);
  return (
    <div className="w-checks">
      {opts.map(opt => {
        const checked = arr.includes(opt);
        return (
          <label key={opt} className={`w-chk ${checked ? "w-chk--on" : ""}`}>
            <input type="checkbox" checked={checked} onChange={() => toggle(opt)} />
            <span className="w-chk__box">{checked && <Icon name="check" size={12} />}</span>
            <span className="w-chk__lbl">{opt}</span>
          </label>
        );
      })}
      {withOther && (
        <div className="w-chk-other">
          <label className={`w-chk ${otherOn ? "w-chk--on" : ""}`}>
            <input type="checkbox" checked={otherOn} onChange={toggleOther} />
            <span className="w-chk__box">{otherOn && <Icon name="check" size={12} />}</span>
            <span className="w-chk__lbl">Other</span>
          </label>
          {otherOn && (
            <input type="text" className="w-chk-other__input" value={otherVal || ""} onChange={e => setOtherText(e.target.value)} placeholder="Please specify…" />
          )}
        </div>
      )}
    </div>
  );
}

function MdRadioGroup({ value, onChange, opts }) {
  return (
    <div className="w-radio">
      {opts.map(opt => {
        const checked = value === opt;
        return (
          <label key={opt} className={`w-opt ${checked ? "w-opt--on" : ""}`}>
            <input type="radio" checked={checked} onChange={() => onChange(opt)} />
            <span className="w-opt__dot">{checked && <span className="w-opt__dot-inner" />}</span>
            <span className="w-opt__lbl">{opt}</span>
          </label>
        );
      })}
    </div>
  );
}

function MdQ({ label, children }) {
  return (
    <div className="toc-pi__method-q">
      <label className="toc-pi__field-label">{label}</label>
      {children}
    </div>
  );
}

function MethodDetail({ methodName, value, onChange }) {
  const v   = (value && typeof value === "object") ? value : {};
  const set = (key, val) => onChange({ ...v, [key]: val });

  const ta = (key, placeholder, rows) => (
    <textarea className="toc-pi__ta" rows={rows || 2} value={v[key] || ""} onChange={e => set(key, e.target.value)} placeholder={placeholder} />
  );
  const ti = (key, placeholder) => (
    <input type="text" className="w-text" value={v[key] || ""} onChange={e => set(key, e.target.value)} placeholder={placeholder} />
  );

  return (
    <div className="toc-pi__method-detail">
      <div className="toc-pi__method-detail-header">{methodName}</div>
      {methodName === "Surveys" && <>
        <MdQ label="Which question types will you include?">
          <MdCheckGroup value={v.types} onChange={val => set("types", val)} opts={SURVEY_TYPES} withOther={false} />
        </MdQ>
        <MdQ label="Pre/post? Longitudinal? Anonymous?">
          {ta("design", "Describe your survey design — administration mode, anonymity, timing of follow-ups…")}
        </MdQ>
      </>}
      {methodName === "Interviews" && <>
        <MdQ label="What structure?">
          <MdRadioGroup value={v.structure} onChange={val => set("structure", val)} opts={["Structured", "Semi-structured", "Unstructured"]} />
        </MdQ>
        <MdQ label="Number of interviews">
          {ti("count", "e.g. 5")}
        </MdQ>
      </>}
      {methodName === "Focus groups" && <>
        <MdQ label="How will you compose them?">
          <MdRadioGroup value={v.composition} onChange={val => set("composition", val)} opts={["Homogeneous (shared characteristics)", "Heterogeneous (diverse perspectives)", "A mix — different compositions for different questions"]} />
        </MdQ>
        <MdQ label="What will focus groups surface that other methods can't?">
          {ta("plan", "e.g. How young people in target communities talk about antisemitism among peers; group norms around reporting…")}
        </MdQ>
      </>}
      {methodName === "Case studies" && <>
        <MdQ label="Which selection rationales will you use?">
          <MdCheckGroup value={v.rationales} onChange={val => set("rationales", val)} opts={CS_RATIONALES} withOther={false} />
        </MdQ>
        <MdQ label="Describe your case study plan">
          {ta("plan", "Number of cases; selection criteria; data sources per case; analytical approach (process tracing, etc.)…", 3)}
        </MdQ>
      </>}
      {methodName === "Peer research" && <>
        <MdQ label="Where do peer researchers add the most value?">
          {ta("where", "e.g. Engaging Jewish youth respondents who would be reticent with external researchers; recruiting via community networks…", 3)}
        </MdQ>
      </>}
      {methodName === "Online analytics" && <>
        <MdQ label="Which platforms will you collect from?">
          {ta("platforms", "e.g. Instagram, TikTok, X, YouTube, project website")}
        </MdQ>
        <MdQ label="Awareness metrics to track">
          <MdCheckGroup value={v.awareness} onChange={val => set("awareness", val)} opts={AWARENESS_OPTS} withOther={true} />
        </MdQ>
        <MdQ label="Engagement metrics to track">
          <MdCheckGroup value={v.engagement} onChange={val => set("engagement", val)} opts={ENGAGEMENT_OPTS} withOther={true} />
        </MdQ>
      </>}
    </div>
  );
}

function WTocMethodSelector({ value, onChange, prompt, allAnswers }) {
  const data = (value && typeof value === "object") ? value : {};
  const toc  = (allAnswers && allAnswers.toc) ? allAnswers.toc : {};

  const tocKeys = Array.isArray(prompt.tocKeys) ? prompt.tocKeys : [];

  const items = [];
  for (const key of tocKeys) {
    (toc[key] || []).filter(x => (x || "").trim()).forEach((text, idx) => {
      const group = TOC_ITEM_GROUPS.find(g => g.tocKey === key);
      items.push({
        key: `${key}__${idx}`,
        sectionLabel: group ? group.label : key,
        timeframe: group ? (group.timeframe || null) : null,
        text,
      });
    });
  }

  const setItem = (key, val) => onChange({ ...data, [key]: val });

  if (items.length === 0) {
    return (
      <div className="toc-pi__empty">
        <Icon name="info" size={15} />
        Complete your Theory of Change (Step 1.2) to see your results chain here.
      </div>
    );
  }

  return (
    <div className="toc-pi">
      {items.map(item => (
        <MethodSelectorBlock
          key={item.key}
          item={item}
          value={data[item.key] || {}}
          onChange={val => setItem(item.key, val)}
          color={tocTypeColor(item.key)}
        />
      ))}
    </div>
  );
}

function MethodSelectorBlock({ item, value, onChange, color }) {
  const [open, setOpen] = useState(true);
  const methods  = Array.isArray(value.methods) ? value.methods : [];
  const other    = value.other || "";
  const otherOn  = methods.includes("Other");
  const details  = (value.details && typeof value.details === "object") ? value.details : {};

  const toggleMethod = (m) => {
    const next = methods.includes(m)
      ? methods.filter(x => x !== m)
      : [...methods, m];
    onChange({ ...value, methods: next });
  };

  const setOther  = (text) => onChange({ ...value, other: text });
  const setDetail = (mName, val) => onChange({ ...value, details: { ...details, [mName]: val } });

  return (
    <div className={`toc-pi__block ${open ? "toc-pi__block--open" : ""}`}>
      <button type="button" className="toc-pi__header" onClick={() => setOpen(o => !o)}>
        <div className="toc-pi__header-meta">
          <span className="toc-pi__cat" style={color ? { background: color, color: "#fff" } : {}}>{item.sectionLabel}</span>
          {item.timeframe && <span className="toc-pi__tf">{item.timeframe}</span>}
        </div>
        <div className="toc-pi__item-text">{item.text}</div>
        <span className="toc-pi__chevron" aria-hidden="true">{open ? "▲" : "▼"}</span>
      </button>
      {open && (
        <div className="toc-pi__fields">
          <div className="toc-pi__field">
            <label className="toc-pi__field-label">Which data collection methods will you use?</label>
            <div className="toc-mp__method-grid">
              {COLLECTION_METHODS.map(m => {
                const on = methods.includes(m);
                return (
                  <button
                    key={m}
                    type="button"
                    className={`toc-mp__method-btn ${on ? "toc-mp__method-btn--on" : ""}`}
                    onClick={() => toggleMethod(m)}
                  >
                    {on && <Icon name="check" size={12} />}
                    {m}
                  </button>
                );
              })}
              <button
                type="button"
                className={`toc-mp__method-btn ${otherOn ? "toc-mp__method-btn--on" : ""}`}
                onClick={() => toggleMethod("Other")}
              >
                {otherOn && <Icon name="check" size={12} />}
                Other
              </button>
            </div>
            {otherOn && (
              <input
                type="text"
                className="w-text"
                style={{ marginTop: 10 }}
                value={other}
                onChange={e => setOther(e.target.value)}
                placeholder="Describe the other method or tool…"
              />
            )}
          </div>
          {methods.filter(m => COLLECTION_METHODS.includes(m)).map(m => (
            <MethodDetail
              key={m}
              methodName={m}
              value={details[m] || {}}
              onChange={val => setDetail(m, val)}
            />
          ))}
        </div>
      )}
    </div>
  );
}

/* =========================================================================
   WCoverageReview — crosswalk grid + gap detector for 3.6
   Reads toc + outputs_methods / outcomes_methods / impact_methods answers.
   ========================================================================= */
const STANDARD_METHOD_NAMES = [
  "Surveys", "Interviews", "Focus groups", "Case studies", "Peer research", "Online analytics",
];

function _getMethodBag(tocKey, allAnswers) {
  if (tocKey === "outputs") return allAnswers.outputs_methods || {};
  if (["outcomes_im", "outcomes_it", "outcomes_lt"].includes(tocKey)) return allAnswers.outcomes_methods || {};
  if (tocKey === "impact") return allAnswers.impact_methods || {};
  return {};
}

function _getMethodsForItem(itemKey, allAnswers) {
  const tocKey = itemKey.split("__")[0];
  const bag    = _getMethodBag(tocKey, allAnswers);
  const entry  = bag[itemKey];
  if (!entry) return [];
  const methods = Array.isArray(entry.methods) ? entry.methods : [];
  const extra   = (entry.other || "").trim();
  return extra ? [...methods, extra] : methods;
}

function _getMethodEntryForItem(itemKey, allAnswers) {
  const tocKey = itemKey.split("__")[0];
  const bag    = _getMethodBag(tocKey, allAnswers);
  return bag[itemKey] || null;
}

function WCoverageReview({ allAnswers }) {
  const toc = (allAnswers && allAnswers.toc) ? allAnswers.toc : {};

  const itemGroups = [
    { tocKey: "outputs",     label: "Outputs" },
    { tocKey: "outcomes_im", label: "Immediate outcomes",    timeframe: "0–3 months" },
    { tocKey: "outcomes_it", label: "Intermediate outcomes", timeframe: "6 mo – 3 yrs" },
    { tocKey: "outcomes_lt", label: "Long-term outcomes",    timeframe: "3–5+ years" },
    { tocKey: "impact",      label: "Impact" },
  ];

  const allItems = itemGroups.flatMap(g =>
    (toc[g.tocKey] || [])
      .filter(x => (x || "").trim())
      .map((text, idx) => ({ key: `${g.tocKey}__${idx}`, groupLabel: g.label, timeframe: g.timeframe, text }))
  );

  if (allItems.length === 0) {
    return (
      <div className="toc-pi__empty">
        <Icon name="info" size={15} />
        Complete your Theory of Change (Step 1.2) to see your coverage review.
      </div>
    );
  }

  const getItemMethods = (item) => _getMethodsForItem(item.key, allAnswers || {});

  // Build the set of method columns: standard methods that are used + any "other" values
  const allUsed = allItems.flatMap(item => getItemMethods(item));
  const standardUsed  = STANDARD_METHOD_NAMES.filter(m => allUsed.includes(m));
  const otherUsed     = [...new Set(allUsed.filter(m => !STANDARD_METHOD_NAMES.includes(m)))];
  const displayCols   = [...standardUsed, ...otherUsed];

  const gaps = allItems.filter(item => getItemMethods(item).length === 0);

  return (
    <div className="cov-review">
      {gaps.length > 0 && (
        <div className="cov-review__gap-warn">
          <Icon name="warn" size={15} />
          <div>
            <strong>{gaps.length} item{gaps.length > 1 ? "s have" : " has"} no data collection method assigned:</strong>
            <ul className="cov-review__gap-list">
              {gaps.map(g => <li key={g.key}>{g.text}</li>)}
            </ul>
          </div>
        </div>
      )}

      {displayCols.length === 0 ? (
        <p className="cov-review__empty">No methods have been assigned yet. Work through Sections 3.3–3.5 first.</p>
      ) : (
        <div className="cov-review__table-wrap">
          <table className="cov-review__table">
            <thead>
              <tr>
                <th className="cov-review__th cov-review__th--item">Output / Outcome / Impact</th>
                {displayCols.map(m => (
                  <th key={m} className="cov-review__th">{m}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {allItems.map(item => {
                const covered = getItemMethods(item);
                const hasGap  = covered.length === 0;
                return (
                  <tr key={item.key} className={hasGap ? "cov-review__row--gap" : ""}>
                    <td className="cov-review__td-item">
                      <span className="cov-review__item-group">{item.groupLabel}</span>
                      <span className="cov-review__item-text">{item.text}</span>
                    </td>
                    {displayCols.map(m => {
                      const on = covered.includes(m);
                      return (
                        <td key={m} className={`cov-review__td ${on ? "cov-review__td--on" : ""}`}>
                          {on && <Icon name="check" size={14} />}
                        </td>
                      );
                    })}
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}

Object.assign(window, {
  PromptWidget,
  isPromptAnswered,
  promptPreview,
  ReportAnswer,
  _getMethodsForItem,
  _getMethodEntryForItem,
  _reportMethodDetailLines,
});
