// dashboard-what-step-phase-3-full-data-driv.js — extracted verbatim from dashboard.js (pure move).

// ── WHAT step (Phase 3 — full data-driven) ─────────────────────────

let _composerPlayCatalog = null;
let _composerPlayFamilies = null;
let _composerVoicePresets = null;

async function _ensurePlayCatalog() {
  if (_composerPlayCatalog)
    return { catalog: _composerPlayCatalog, families: _composerPlayFamilies };
  const r = await api("composition-play-catalog").catch(() => null);
  if (r && Array.isArray(r.catalog)) {
    _composerPlayCatalog = r.catalog;
    _composerPlayFamilies = r.families || [];
  }
  return { catalog: _composerPlayCatalog || [], families: _composerPlayFamilies || [] };
}

async function _ensureVoicePresets() {
  if (_composerVoicePresets) return _composerVoicePresets;
  const r = await api("voice-presets-list").catch(() => null);
  _composerVoicePresets = r?.presets || [];
  return _composerVoicePresets;
}

function _composerWhatForm(draft) {
  const root = el("div", {});
  const playList = el("div", { style: "display: flex; flex-direction: column; gap: 8px;" });

  const renderPlays = () => {
    playList.innerHTML = "";
    if ((draft.plays || []).length === 0) {
      playList.appendChild(
        el(
          "div",
          { class: "muted", style: "font-size: 12px; padding: 8px;" },
          "No plays yet. Add one below.",
        ),
      );
      return;
    }
    draft.plays.forEach((p, i) => {
      playList.appendChild(_playCard(p, i, draft, renderPlays));
    });
  };

  const addBtn = el(
    "button",
    {
      class: "btn btn-secondary",
      type: "button",
      onclick: () =>
        _openPlayPickerModal(draft, (picked) => {
          draft.plays.push(_seedPlayFromCatalog(picked));
          renderPlays();
        }),
    },
    "+ Add play",
  );

  // Smart-prefill button when source is crm_selected
  const hasCrmSelected = (draft.audience.resolvers || []).some((r) => r.type === "crm_selected");
  const smartPrefillBtn = hasCrmSelected
    ? el(
        "button",
        {
          class: "btn btn-tiny btn-secondary",
          type: "button",
          title:
            "Pre-fill the first send_dm play's body from shared tags on the selected contacts.",
          onclick: () => _composerSmartPrefillFromSelection(draft, renderPlays),
        },
        "Smart prefill",
      )
    : null;

  renderPlays();
  root.appendChild(playList);
  root.appendChild(
    el(
      "div",
      { class: "row", style: "gap: 8px; margin-top: 12px; flex-wrap: wrap;" },
      [addBtn, smartPrefillBtn].filter(Boolean),
    ),
  );
  return root;
}

async function _openPlayPickerModal(draft, onPick) {
  const { catalog, families } = await _ensurePlayCatalog();
  if (catalog.length === 0) {
    toast("Play catalog unavailable", "err");
    return;
  }

  // Selected channel platforms — used for verb-matrix dimming.
  const channelPlatforms = (draft.channels || []).map((c) => c.platform);

  const overlay = el("div", {
    style:
      "position: fixed; inset: 0; background: rgba(0,0,0,0.7); z-index: 9999; display: flex; align-items: center; justify-content: center;",
    onclick: (ev) => {
      if (ev.target === ev.currentTarget) overlay.remove();
    },
  });
  const modal = el("div", {
    style:
      "background: var(--bg-1); border: 1px solid var(--border-strong); border-radius: 8px; width: 920px; max-width: 95vw; max-height: 80vh; display: flex; flex-direction: column;",
  });

  const header = el(
    "div",
    {
      class: "row",
      style:
        "padding: 14px 18px; border-bottom: 1px solid var(--border); justify-content: space-between; align-items: center;",
    },
    [
      el("div", {}, [
        el("div", { style: "font-weight: 600; font-size: 16px;" }, "Pick a play"),
        el(
          "div",
          { class: "muted", style: "font-size: 11px; margin-top: 2px;" },
          channelPlatforms.length > 0
            ? `Channels: ${channelPlatforms.join(", ")} — incompatible plays are dimmed`
            : "No channels picked — all plays available; verb-matrix will gate at runtime",
        ),
      ]),
      el(
        "button",
        { class: "btn btn-tiny btn-secondary", type: "button", onclick: () => overlay.remove() },
        "✕",
      ),
    ],
  );

  const body = el("div", {
    style: "display: grid; grid-template-columns: 200px 1fr; gap: 0; flex: 1; overflow: hidden;",
  });
  const familyCol = el("div", {
    style: "border-right: 1px solid var(--border); overflow-y: auto;",
  });
  const detailCol = el("div", { style: "padding: 8px; overflow-y: auto;" });

  let selectedFamilyId = families[0]?.id || "outbound";

  const isCompatible = (play) => {
    if (!play.platforms || play.platforms.length === 0) return true; // any-platform
    if (channelPlatforms.length === 0) return true; // no constraint
    return play.platforms.some((p) => channelPlatforms.includes(p));
  };

  const renderFamilies = () => {
    familyCol.innerHTML = "";
    families.forEach((f) => {
      const types = catalog.filter((c) => c.family === f.id);
      const compatCount = types.filter(isCompatible).length;
      const isOn = f.id === selectedFamilyId;
      familyCol.appendChild(
        el(
          "div",
          {
            style: `padding: 12px 14px; cursor: pointer; border-bottom: 1px solid var(--border); ${isOn ? "background: #1a1a2a; border-left: 2px solid #5a8aff;" : ""}`,
            onclick: () => {
              selectedFamilyId = f.id;
              renderFamilies();
              renderDetail();
            },
          },
          [
            el("div", { style: "font-size: 13px;" }, `${f.icon} ${f.label}`),
            el(
              "div",
              { class: "muted", style: "font-size: 10px; margin-top: 2px;" },
              channelPlatforms.length > 0
                ? `${compatCount} compatible · ${types.length - compatCount} dim`
                : `${types.length} plays`,
            ),
          ],
        ),
      );
    });
  };

  const renderDetail = () => {
    detailCol.innerHTML = "";
    const types = catalog.filter((c) => c.family === selectedFamilyId);
    if (types.length === 0) {
      detailCol.appendChild(
        el(
          "div",
          { class: "muted", style: "font-size: 12px; padding: 12px;" },
          "No plays in this family.",
        ),
      );
      return;
    }
    const grid = el("div", { style: "display: grid; grid-template-columns: 1fr 1fr; gap: 8px;" });
    types.forEach((t) => {
      const compat = isCompatible(t);
      const platformLabel = t.platforms && t.platforms.length > 0 ? t.platforms.join(" · ") : "any";
      grid.appendChild(
        el(
          "div",
          {
            style: `padding: 12px; background: ${compat ? "#0c0c0c" : "#0c0c0c"}; border: 1px solid ${compat ? "#222" : "#1a1a1a"}; border-radius: 6px; cursor: ${compat ? "pointer" : "not-allowed"}; ${compat ? "" : "opacity: 0.4;"}`,
            onclick: () => {
              if (!compat) return;
              onPick(t);
              overlay.remove();
            },
          },
          [
            el("div", { style: "font-weight: 500; font-size: 13px;" }, t.label),
            el(
              "div",
              { class: "muted", style: "font-size: 10px; margin-top: 2px; line-height: 1.3;" },
              t.description,
            ),
            el(
              "div",
              { class: "muted", style: "font-size: 10px; margin-top: 6px;" },
              `Platforms: ${platformLabel}`,
            ),
            t.destructive
              ? el(
                  "div",
                  { style: "font-size: 10px; color: var(--red); margin-top: 4px;" },
                  "destructive (modifies CRM)",
                )
              : null,
          ].filter(Boolean),
        ),
      );
    });
    detailCol.appendChild(grid);
  };

  body.appendChild(familyCol);
  body.appendChild(detailCol);
  modal.appendChild(header);
  modal.appendChild(body);
  overlay.appendChild(modal);
  document.body.appendChild(overlay);

  renderFamilies();
  renderDetail();
}

function _seedPlayFromCatalog(catalogEntry) {
  const p = { type: catalogEntry.id };
  for (const knob of catalogEntry.knobs || []) {
    if (knob.default !== undefined) p[knob.key] = knob.default;
  }
  return p;
}

function _playCard(play, idx, draft, onChange) {
  const catalog = _composerPlayCatalog || [];
  const meta = catalog.find((c) => c.id === play.type);

  const card = el("div", {
    style:
      "background: var(--bg-0); border: 1px solid var(--border); border-radius: 6px; padding: 12px;",
  });

  const channelPlatforms = (draft.channels || []).map((c) => c.platform);
  const compat =
    !meta?.platforms ||
    meta.platforms.length === 0 ||
    channelPlatforms.length === 0 ||
    meta.platforms.some((p) => channelPlatforms.includes(p));

  const headerRow = el(
    "div",
    {
      class: "row",
      style: "justify-content: space-between; align-items: flex-start; margin-bottom: 8px;",
    },
    [
      el(
        "div",
        { style: "flex: 1;" },
        [
          el(
            "div",
            { style: "font-weight: 500; font-size: 13px;" },
            `${idx + 1}. ${meta?.label || play.type}${meta?.destructive ? " ⚠" : ""}`,
          ),
          el(
            "div",
            { class: "muted", style: "font-size: 10px; margin-top: 2px;" },
            meta?.description || "",
          ),
          !compat
            ? el(
                "div",
                { style: "font-size: 10px; color: var(--red); margin-top: 4px;" },
                `Not compatible with selected channel(s) [${channelPlatforms.join(", ") || "none"}]. Will skip at runtime.`,
              )
            : null,
        ].filter(Boolean),
      ),
      el("div", { class: "row", style: "gap: 4px;" }, [
        el(
          "button",
          {
            class: "btn btn-tiny btn-secondary",
            type: "button",
            disabled: idx === 0 ? "disabled" : null,
            title: "Move up",
            onclick: () => {
              if (idx === 0) return;
              [draft.plays[idx - 1], draft.plays[idx]] = [draft.plays[idx], draft.plays[idx - 1]];
              onChange();
            },
          },
          "↑",
        ),
        el(
          "button",
          {
            class: "btn btn-tiny btn-secondary",
            type: "button",
            disabled: idx === draft.plays.length - 1 ? "disabled" : null,
            title: "Move down",
            onclick: () => {
              if (idx === draft.plays.length - 1) return;
              [draft.plays[idx + 1], draft.plays[idx]] = [draft.plays[idx], draft.plays[idx + 1]];
              onChange();
            },
          },
          "↓",
        ),
        el(
          "button",
          {
            class: "btn btn-tiny btn-danger",
            type: "button",
            onclick: () => {
              draft.plays.splice(idx, 1);
              onChange();
            },
          },
          "✕",
        ),
      ]),
    ],
  );
  card.appendChild(headerRow);

  // Render knobs
  const knobsContainer = el("div", {
    style: "display: flex; flex-direction: column; gap: 8px; margin-top: 8px;",
  });
  for (const knob of meta?.knobs || []) {
    if (knob.show_when_platform && !channelPlatforms.includes(knob.show_when_platform)) continue;
    knobsContainer.appendChild(_renderPlayKnob(knob, play));
  }
  card.appendChild(knobsContainer);

  // Phase 33: A/B variants editor — only for plays with a `template` knob
  // (text-generating plays: send_dm, email_send, comment, comment_on_keyword_post,
  // connect_with_note, mass_connect_invite, post_status, quote_tweet).
  // Variants are stored in play.variants = [{ id, template, subject?, weight }].
  // Backend (Phase 25 resolveVariant) picks one per contact by stable hash.
  const supportsVariants = (meta?.knobs || []).some(
    (k) => k.key === "template" && k.type === "textarea",
  );
  if (supportsVariants) {
    card.appendChild(_renderPlayVariantsSection(play, onChange));
  }

  // Phase 33: jitter knobs (Phase 25) — surface plays[].jitter_ms_min/max
  // for any send-class play. Lets users humanize pacing per play.
  const supportsJitter = ![
    "wait",
    "branch",
    "stop_on",
    "log_outcome",
    "record_event",
    "add_tag",
    "remove_tag",
  ].includes(play.type);
  if (supportsJitter) {
    card.appendChild(_renderPlayJitterSection(play, onChange));
  }

  return card;
}

/**
 * Phase 33 — A/B variants editor for text-generating plays.
 * Renders an expander with 2-3 variant rows. Each variant has:
 *   - id (auto-suggested as var_a/var_b/var_c, editable for audit-log labeling)
 *   - template (overrides play.template at runtime)
 *   - weight (relative likelihood — defaults to 1)
 *   - subject (only for email-channel plays)
 */
function _renderPlayVariantsSection(play, onChange) {
  const wrap = el("div", {
    style: "margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--border);",
  });
  const header = el(
    "div",
    {
      class: "row",
      style: "justify-content: space-between; align-items: center; margin-bottom: 6px;",
    },
    [
      el("div", { style: "font-size: 11px; font-weight: 500;" }, "A/B test variants"),
      el(
        "div",
        { class: "muted", style: "font-size: 10px;" },
        Array.isArray(play.variants) && play.variants.length > 0
          ? `${play.variants.length} variant${play.variants.length === 1 ? "" : "s"} active`
          : "Off — uses single template above",
      ),
    ],
  );
  wrap.appendChild(header);

  const renderVariants = () => {
    // Clear children except the header
    while (wrap.children.length > 1) wrap.removeChild(wrap.lastChild);

    const variants = Array.isArray(play.variants) ? play.variants : [];
    if (variants.length === 0) {
      const enableBtn = el(
        "button",
        {
          class: "btn btn-tiny btn-secondary",
          type: "button",
          onclick: () => {
            // Seed two variants from current template if any
            const seedTpl = play.template || "Hey {{first_name}}, ";
            const seedSubj = play.subject || "";
            play.variants = [
              {
                id: "var_a",
                template: seedTpl,
                weight: 1,
                ...(seedSubj ? { subject: seedSubj } : {}),
              },
              {
                id: "var_b",
                template: seedTpl,
                weight: 1,
                ...(seedSubj ? { subject: seedSubj } : {}),
              },
            ];
            renderVariants();
            if (typeof onChange === "function") onChange();
          },
        },
        "+ Enable A/B testing",
      );
      wrap.appendChild(
        el(
          "div",
          { class: "muted", style: "font-size: 11px; line-height: 1.5; margin-bottom: 6px;" },
          "Test two or more template wordings on this play. Each contact gets one variant via stable hash; variant_id stamps on the audit log so you can see which won.",
        ),
      );
      wrap.appendChild(enableBtn);
      return;
    }

    for (let i = 0; i < variants.length; i++) {
      const v = variants[i];
      const row = el("div", {
        style:
          "padding: 8px; background: var(--bg-0); border: 1px solid var(--border); border-radius: 4px; margin-bottom: 6px;",
      });
      row.appendChild(
        el(
          "div",
          { class: "row", style: "gap: 8px; align-items: center; margin-bottom: 6px;" },
          [
            el("input", {
              type: "text",
              class: "wizard-input",
              value: v.id || `var_${String.fromCharCode(97 + i)}`,
              placeholder: "id (e.g. var_a)",
              style: "width: 100px; font-size: 11px;",
              oninput: (ev) => {
                v.id = ev.target.value;
              },
            }),
            el("span", { class: "muted", style: "font-size: 10px;" }, "weight"),
            el("input", {
              type: "number",
              class: "wizard-input",
              value: String(v.weight ?? 1),
              min: "0",
              step: "1",
              style: "width: 60px; font-size: 11px;",
              oninput: (ev) => {
                v.weight = Number(ev.target.value) || 0;
              },
            }),
            el("div", { style: "flex: 1;" }),
            variants.length > 2
              ? el(
                  "button",
                  {
                    class: "btn btn-tiny btn-danger",
                    type: "button",
                    onclick: () => {
                      play.variants.splice(i, 1);
                      renderVariants();
                      if (typeof onChange === "function") onChange();
                    },
                  },
                  "✕",
                )
              : null,
          ].filter(Boolean),
        ),
      );
      // Subject (only if play.subject was originally set — implies email)
      if (play.subject !== undefined || v.subject !== undefined) {
        row.appendChild(
          el("input", {
            type: "text",
            class: "wizard-input",
            value: v.subject || "",
            placeholder: "subject (for email channel)",
            style: "width: 100%; font-size: 11px; margin-bottom: 4px;",
            oninput: (ev) => {
              v.subject = ev.target.value;
            },
          }),
        );
      }
      // Template body
      row.appendChild(
        el(
          "textarea",
          {
            class: "wizard-textarea",
            placeholder: "template body — supports {{first_name}}, {{name}}, {{company}}, etc",
            style: "width: 100%; min-height: 60px; font-size: 11px;",
            oninput: (ev) => {
              v.template = ev.target.value;
            },
          },
          v.template || "",
        ),
      );
      wrap.appendChild(row);
    }

    const actions = el("div", { class: "row", style: "gap: 6px;" }, [
      el(
        "button",
        {
          class: "btn btn-tiny btn-secondary",
          type: "button",
          disabled: variants.length >= 5 ? "disabled" : null,
          onclick: () => {
            const nextLetter = String.fromCharCode(97 + variants.length);
            play.variants.push({
              id: `var_${nextLetter}`,
              template: "Hey {{first_name}}, ",
              weight: 1,
            });
            renderVariants();
            if (typeof onChange === "function") onChange();
          },
        },
        "+ Add variant",
      ),
      el(
        "button",
        {
          class: "btn btn-tiny btn-danger",
          type: "button",
          title: "Remove all variants and revert to single template above",
          onclick: () => {
            delete play.variants;
            renderVariants();
            if (typeof onChange === "function") onChange();
          },
        },
        "Disable A/B",
      ),
    ]);
    wrap.appendChild(actions);
  };

  renderVariants();
  return wrap;
}

/**
 * Phase 33 — Jitter knobs (Phase 25). Surfaces plays[].jitter_ms_min/max
 * as a small inline control. Default off (no jitter); when enabled, the
 * runner sleeps a random amount in [min, max] ms after the play executes.
 */
function _renderPlayJitterSection(play, onChange) {
  const wrap = el("div", {
    style: "margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--border);",
  });
  const isOn = Number(play.jitter_ms_max) > 0;
  const header = el("div", { class: "row", style: "gap: 8px; align-items: center;" }, [
    el("label", { style: "font-size: 11px; cursor: pointer;" }, [
      el("input", {
        type: "checkbox",
        checked: isOn ? "checked" : null,
        style: "margin-right: 4px;",
        onchange: (ev) => {
          if (ev.target.checked) {
            play.jitter_ms_min = play.jitter_ms_min || 1000;
            play.jitter_ms_max = play.jitter_ms_max || 4000;
          } else {
            delete play.jitter_ms_min;
            delete play.jitter_ms_max;
          }
          if (typeof onChange === "function") onChange();
        },
      }),
      "⏱ Add jitter after this play",
    ]),
  ]);
  wrap.appendChild(header);
  if (isOn) {
    wrap.appendChild(
      el(
        "div",
        { class: "row", style: "gap: 6px; align-items: center; margin-top: 6px; font-size: 11px;" },
        [
          el("span", { class: "muted" }, "min ms"),
          el("input", {
            type: "number",
            class: "wizard-input",
            value: String(play.jitter_ms_min || 1000),
            min: "0",
            step: "100",
            style: "width: 80px;",
            oninput: (ev) => {
              play.jitter_ms_min = Number(ev.target.value) || 0;
            },
          }),
          el("span", { class: "muted" }, "max ms"),
          el("input", {
            type: "number",
            class: "wizard-input",
            value: String(play.jitter_ms_max || 4000),
            min: "0",
            step: "100",
            style: "width: 80px;",
            oninput: (ev) => {
              play.jitter_ms_max = Number(ev.target.value) || 0;
            },
          }),
          el(
            "span",
            { class: "muted", style: "font-size: 10px;" },
            "(capped at 5000 ms by runner)",
          ),
        ],
      ),
    );
  }
  return wrap;
}

function _renderPlayKnob(knob, play) {
  const wrap = el("div", {});
  if (knob.type === "hidden") return wrap;

  const labelLine = el(
    "div",
    { class: "muted", style: "font-size: 11px; margin-bottom: 2px;" },
    [
      knob.label,
      knob.required ? el("span", { style: "color: var(--red); margin-left: 4px;" }, "*") : null,
      knob.supports_vars
        ? el(
            "span",
            { class: "muted", style: "font-size: 10px; margin-left: 8px;" },
            "(supports {{first_name}}, {{name}}, {{company}}, {{role}}, {{sector}})",
          )
        : null,
    ].filter(Boolean),
  );
  wrap.appendChild(labelLine);

  const cur = play[knob.key];

  if (knob.type === "string" || knob.type === "number") {
    const input = el("input", {
      type: knob.type === "number" ? "number" : "text",
      class: "wizard-input",
      value: cur ?? "",
      placeholder: knob.placeholder || "",
      style: "width: 100%; max-width: 480px;",
    });
    input.addEventListener("input", () => {
      let v = input.value;
      if (knob.type === "number") v = v === "" ? null : parseFloat(v);
      play[knob.key] = v;
    });
    wrap.appendChild(input);
  } else if (knob.type === "textarea") {
    const ta = el(
      "textarea",
      {
        class: "wizard-textarea",
        placeholder: knob.placeholder || "",
        style: "width: 100%; min-height: 80px;",
      },
      cur || "",
    );
    ta.addEventListener("input", () => {
      play[knob.key] = ta.value;
    });
    wrap.appendChild(ta);
  } else if (knob.type === "select") {
    const sel = el(
      "select",
      { class: "wizard-input", style: "max-width: 240px;" },
      (knob.options || []).map((o) => el("option", { value: String(o) }, String(o))),
    );
    sel.value = cur ?? knob.default ?? (knob.options?.[0] || "");
    sel.addEventListener("change", () => {
      play[knob.key] = sel.value;
    });
    wrap.appendChild(sel);
  } else if (knob.type === "voice_picker") {
    // Phase 33: surface preset descriptions inline so users know what each
    // voice does without leaving the wizard. Description updates when
    // selection changes.
    const sel = el("select", { class: "wizard-input", style: "max-width: 280px;" });
    const descLine = el("div", {
      class: "muted",
      style: "font-size: 10px; line-height: 1.4; margin-top: 4px; min-height: 14px;",
    });
    sel.appendChild(el("option", { value: "" }, "Account default voice"));
    _ensureVoicePresets()
      .then((presets) => {
        sel.innerHTML = "";
        sel.appendChild(
          el(
            "option",
            { value: "", title: "Use whatever voice is configured on the sending account." },
            "Account default voice",
          ),
        );
        presets.forEach((p) => {
          const opt = el(
            "option",
            { value: p.id, title: p.description || "" },
            `${p.label || p.id}`,
          );
          sel.appendChild(opt);
        });
        sel.value = cur ?? "";
        // Initial description for current selection
        const initial = presets.find((p) => p.id === sel.value);
        descLine.textContent =
          initial?.description ||
          (sel.value ? "" : "Falls back to the account's configured voice.");
        sel.addEventListener("change", () => {
          play[knob.key] = sel.value || null;
          const picked = presets.find((p) => p.id === sel.value);
          descLine.textContent =
            picked?.description ||
            (sel.value ? "" : "Falls back to the account's configured voice.");
        });
      })
      .catch(() => {});
    wrap.appendChild(sel);
    wrap.appendChild(descLine);
  } else if (knob.type === "play_list") {
    // For branch.then — a nested ordered list of plays. Reuses the same
    // play card + play picker pattern at one level of nesting (Phase 4
    // doesn't support deeper nesting; that's Phase 7 polish).
    if (!Array.isArray(play[knob.key])) play[knob.key] = [];
    const subContainer = el("div", {
      style:
        "margin-top: 6px; padding: 8px; background: var(--bg-0); border: 1px dashed var(--border-strong); border-radius: 4px;",
    });
    const renderSub = () => {
      subContainer.innerHTML = "";
      if (play[knob.key].length === 0) {
        subContainer.appendChild(
          el(
            "div",
            { class: "muted", style: "font-size: 11px; padding: 6px;" },
            "No nested plays — branch is a no-op until you add some.",
          ),
        );
      } else {
        play[knob.key].forEach((sub, si) => {
          const meta = (_composerPlayCatalog || []).find((c) => c.id === sub.type);
          subContainer.appendChild(
            el(
              "div",
              {
                class: "row",
                style:
                  "padding: 4px 6px; background: var(--bg-0); border: 1px solid var(--border); border-radius: 3px; gap: 6px; align-items: center; font-size: 11px; margin-bottom: 4px;",
              },
              [
                el(
                  "span",
                  { style: "flex: 1;" },
                  `${si + 1}. ${meta?.label || sub.type}${sub.template ? ` — "${String(sub.template).slice(0, 40)}…"` : ""}`,
                ),
                el(
                  "button",
                  {
                    class: "btn btn-tiny btn-danger",
                    type: "button",
                    onclick: () => {
                      play[knob.key].splice(si, 1);
                      renderSub();
                    },
                  },
                  "✕",
                ),
              ],
            ),
          );
        });
      }
    };
    const addBtn = el(
      "button",
      {
        class: "btn btn-tiny btn-secondary",
        type: "button",
        style: "margin-top: 4px;",
        onclick: () =>
          _openPlayPickerModal({ channels: [] }, (picked) => {
            play[knob.key].push(_seedPlayFromCatalog(picked));
            renderSub();
          }),
      },
      "+ Add nested play",
    );
    renderSub();
    wrap.appendChild(subContainer);
    wrap.appendChild(addBtn);
  }
  return wrap;
}
