/* assets/js/pt-wizard.js */
(function (wp) {
  const { createElement: h, Fragment, useState, useEffect, useRef } = wp.element;
  const {
    Button,
    Card,
    CardBody,
    TextControl,
    TextareaControl,
    RadioControl,
    Spinner,
    Notice,
    SelectControl,
    SearchControl,
    Tooltip,
    ToggleControl,
  } = wp.components;

const apiFetch = wp.apiFetch;

// Decode HTML entities in titles (e.g. &amp; -> &)
const decodeEntities =
  (wp.htmlEntities && wp.htmlEntities.decodeEntities)
    ? wp.htmlEntities.decodeEntities
    : (s) => {
        // fallback
        const el = document.createElement("textarea");
        el.innerHTML = String(s || "");
        return el.value;
      };
  const cfg = window.abtestkit_PT || {};

  // ─────────────────────────────────────────────────────────────
  // Telemetry helpers (opt-in gated server-side)
  // Sends to WP REST: /abtestkit/v1/telemetry
  // ─────────────────────────────────────────────────────────────
  const ABTK_TLM_PATH = "/abtestkit/v1/telemetry";

  const abtkMakeSessionId = () => {
    try {
      if (window.crypto && crypto.randomUUID) return crypto.randomUUID();
    } catch (_) {}
    return (
      "ws_" +
      Math.random().toString(16).slice(2) +
      "_" +
      Date.now().toString(16)
    );
  };

  const abtkSafeInt = (n, min = 0, max = 86400000) => {
    const x = parseInt(n, 10);
    if (!Number.isFinite(x)) return min;
    return Math.max(min, Math.min(max, x));
  };

  const abtkSendTelemetry = (event, payload) => {
    // If cfg.nonce is missing, do nothing (never break UX)
    if (!cfg || !cfg.nonce) return Promise.resolve();

    return apiFetch({
      path: ABTK_TLM_PATH,
      method: "POST",
      headers: { "X-WP-Nonce": cfg.nonce, "Content-Type": "application/json" },
      data: {
        event,
        payload: payload && typeof payload === "object" ? payload : {},
      },
    }).catch(() => {});
  };

  // Simple wrapper around the WordPress media frame so we can pick images
  const openMediaFrame = ({ multiple = false, title, buttonText, onSelect }) => {
    if (!window.wp || !wp.media || typeof onSelect !== "function") return;

    const frame = wp.media({
      title: title || "Select image",
      library: { type: "image" },
      multiple,
      button: {
        text: buttonText || "Use image",
      },
    });

    frame.on("select", () => {
      const selection = frame.state().get("selection");
      if (!selection) return;

      const items = [];
      selection.each((attachment) => {
        if (!attachment || !attachment.get) return;
        const id = attachment.get("id");
        const url =
          (attachment.get("url") ||
            (attachment.attributes && attachment.attributes.url)) ||
          "";
        if (id && url) {
          items.push({ id, url });
        }
      });

      if (!items.length) return;

      if (multiple) {
        onSelect(items);
      } else {
        onSelect(items[0]);
      }
    });

    frame.open();
  };

  // TinyMCE-based classic editor field used for Version B product descriptions
  const ClassicEditorField = ({ id, value, onChange, help, readOnly = false }) => {
    const textareaId = id;

    // Initialise editor once
    useEffect(() => {
      if (!window.wp || !wp.editor || !wp.editor.initialize) return;

      const $ = window.jQuery || window.$;
      if (!$) return;

      // Set initial value in the textarea before turning it into an editor
      if (typeof value === "string") {
        $("#" + textareaId).val(value);
      }

      // Clean up any previous editor instance on this ID
      if (wp.editor.remove) {
        try {
          wp.editor.remove(textareaId);
        } catch (e) {}
      }

      const onInit = (event, editor) => {
        if (!editor || editor.id !== textareaId) return;
        editor.on("change keyup", () => {
          const content = editor.getContent();
          if (typeof onChange === "function") {
            onChange(content);
          }
        });
      };

      $(document).on(
        "tinymce-editor-init.abtestkit-" + textareaId,
        onInit
      );

      wp.editor.initialize(textareaId, {
        tinymce: {
          wpautop: true,
          toolbar1: readOnly ? false : "formatselect,bold,italic,bullist,numlist,link,unlink,blockquote,undo,redo",
          toolbar2: "",
          readonly: readOnly,
        },
        quicktags: true,
      });

      return () => {
        $(document).off(
          "tinymce-editor-init.abtestkit-" + textareaId,
          onInit
        );
        if (wp.editor.remove) {
          try {
            wp.editor.remove(textareaId);
          } catch (e) {}
        }
      };
    }, [textareaId]);

    // Keep programmatic value changes (like prefill from Version A) in sync
    useEffect(() => {
      if (!window.tinymce) return;
      const ed = window.tinymce.get(textareaId);
      if (!ed) return;
      if (typeof value === "string" && value !== ed.getContent()) {
        ed.setContent(value);
      }
    }, [textareaId, value]);

    return h(
      "div",
      null,
      [
        h("textarea", {
          id: textareaId,
          defaultValue: value || "",
          style: { width: "100%", minHeight: 200 },
        }),
        help
          ? h(
              "p",
              { style: { marginTop: 4, color: "#6c7781", fontSize: 12 } },
              help
            )
          : null,
      ]
    );
  };

  // Utility: strip HTML tags for plain-text placeholders, etc.
  const stripHtml = (html) =>
    typeof html === "string"
      ? html.replace(/<\/?[^>]+(>|$)/g, "").replace(/\s+/g, " ").trim()
      : "";

  /* ──────────────────────────────────────────────────────────────
   * Vertical stepper (wizard step navigation) on the left
   * ────────────────────────────────────────────────────────────── */
  const Step = ({ title, index, current }) => {
    const done = index < current;
    const active = index === current;
    return h(
      "div",
      {
        style: {
          display: "flex",
          gap: "8px",
          alignItems: "center",
          opacity: done ? 0.7 : 1,
          margin: "8px 0",
        },
      },
      [
        h("div", {
          style: {
            width: 18,
            height: 18,
            borderRadius: 18,
            border: "2px solid #2271b1",
            background: active ? "#2271b1" : done ? "#e5f1fa" : "white",
          },
        }),
        h("div", { style: { fontWeight: active ? 600 : 400 } }, title),
      ]
    );
  };

    // ──────────────────────────────────────────────────────────────
  // Tips panel shown in the grey area under the main wizard card (Step 0)
  // ──────────────────────────────────────────────────────────────
  const TipsPanel = ({ postType, step }) => {
    if (step !== 0) return null;

    const tipsByType = {
      product: {
        title: "Tips: WooCommerce product tests",
        items: [
          "Only one real product exists (one URL) shoppers see Version A or B on the same product.",
          "SKU, stock, inventory from version A is always used and updates in real time.",
          "Version B is never indexed for SEO safety.",
          "The version presented is kept consistent across the website experience (product gallery + common product cards + checkout where product is shown).",
        ],
      },
    };

    const data = tipsByType[String(postType || "")];

    // Completely hide until a valid test type is selected
    if (!data) return null;

    return h(
      "div",
      {
        style: {
          marginTop: 14,
          padding: "14px 16px",
          background: "#ffffff",
          border: "1px solid #dcdcde",
          borderLeft: "4px solid #2271b1",
          borderRadius: 8,
          boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
        },
      },
      [
        h(
          "div",
          { style: { display: "flex", alignItems: "flex-start", gap: 10 } },
          [
            h(
              "span",
              {
                style: {
                  width: 28,
                  height: 28,
                  borderRadius: 999,
                  background: "#e5f1fa",
                  display: "inline-flex",
                  alignItems: "center",
                  justifyContent: "center",
                  flex: "0 0 auto",
                  marginTop: 1,
                },
              },
              h("span", {
                className: "dashicons dashicons-info-outline",
                style: { fontSize: 18, color: "#2271b1" },
              })
            ),

            h("div", { style: { minWidth: 0, width: "100%" } }, [
              h(
                "div",
                { style: { fontWeight: 600, fontSize: 14, lineHeight: 1.3 } },
                data.title
              ),

              h(
                "ul",
                {
                  style: {
                    margin: "10px 0 0",
                    paddingLeft: 18,
                    listStyleType: "disc",
                    listStylePosition: "outside",
                    color: "#50575e",
                    fontSize: 13,
                    lineHeight: 1.5,
                  },
                },
                data.items.map((t, i) =>
                  h("li", { key: i, style: { margin: "6px 0" } }, t)
                )
              ),
            ]),
          ]
        ),
      ]
    );
  };

// ─────────────────────────────────────────────────────────────
// ElementPicker: iframe preview + guarded picking
// goal === "clicks" → any clickable element (links, buttons, etc.)
// onPick({ selector, href, label }) receives a stable selector or URL
// ─────────────────────────────────────────────────────────────
const ElementPicker = ({
  pageId,
  viewBase,
  goal = "clicks",
  selected = [],
  onPick,
  onWarn,
  label = "Version A",
}) => {
  const { useRef, useEffect, useState, Fragment, createElement: h } = wp.element;
  const frameRef = useRef(null);
  const [picking, setPicking] = useState(false);

  // --- helpers for specific, stable selectors -----------------
  const cssEscape = (window.CSS && CSS.escape) ? CSS.escape : (s) =>
    String(s).replace(/[^a-zA-Z0-9_-]/g, (m) => "\\" + m.charCodeAt(0).toString(16) + " ");

  const nthOfType = (el) => {
    let i = 1, sib = el;
    while ((sib = sib.previousElementSibling)) {
      if (sib.tagName === el.tagName) i++;
    }
    return i;
  };

  const nearestButtonContainer = (el) => {
    // Prefer common Gutenberg wrappers so selectors remain stable after edits
    return el.closest(".wp-block-buttons, .wp-block-group, main, article, section, .entry-content, body");
  };

  const makeSelector = (rawEl) => {
    if (!rawEl || !rawEl.tagName) return "";

    // Always build the selector for the *clickable* thing
    const el =
      rawEl.closest("a[href],button,[role='button'],[onclick],input[type='submit'],.wp-block-button__link") ||
      rawEl;

    // If it has an ID, that's the best selector
    if (el.id) return "#" + cssEscape(el.id);

    // Try to build a short path from a meaningful container
    const container = nearestButtonContainer(el);
    let cur = el;
    const parts = [];

    while (cur && cur !== container.parentElement) {
      let seg;

      // Prefer a single, specific Gutenberg class when present
      const classes = [...(cur.classList || [])];
      const wpClass = classes.find((c) => c.startsWith("wp-block"));
      if (wpClass) {
        seg = "." + wpClass;
      } else if (classes.length) {
        // keep it short but specific
        seg = cur.tagName.toLowerCase() + "." + classes.slice(0, 2).map(cssEscape).join(".");
      } else {
        seg = cur.tagName.toLowerCase();
      }

      // Disambiguate siblings of same tag
      const n = nthOfType(cur);
      if (n > 1) seg += `:nth-of-type(${n})`;

      parts.unshift(seg);

      if (cur === container) break;
      cur = cur.parentElement;
    }

    // Typical Gutenberg: .wp-block-buttons .wp-block-button:nth-of-type(N) .wp-block-button__link
    return parts.join(" > ");
  };

  const isValidPick = (win, target) => {
    if (!(target instanceof win.Element)) return false;

    const clickable =
      target.closest &&
      target.closest(
        "a[href], button, [role='button'], [onclick], input[type='submit'], .wp-block-button__link"
      );

    if (goal === "clicks") return !!clickable;
    // Picker isn't used for "form" – those are handled by submissions
    if (goal === "form") return false;

    return false;
  };

  useEffect(() => {
    if (!picking || !frameRef.current) return;

    const frame = frameRef.current;

    const bind = () => {
      const win = frame.contentWindow;
      const doc = win && win.document;
      if (!doc) return;

      // Highlight box
      const hi = doc.createElement("div");
      hi.style.cssText =
        "position:fixed;pointer-events:none;outline:2px solid #2271b1;z-index:2147483647;transition:outline-color .15s ease";
      doc.body.appendChild(hi);

      let lastBadFlash = 0;
      const flashBad = () => {
        const now = Date.now();
        if (now - lastBadFlash < 250) return; // rate-limit the flash
        lastBadFlash = now;
        hi.style.outlineColor = "#d63638"; // WP error red
        setTimeout(() => {
          hi.style.outlineColor = "#2271b1";
        }, 180);
      };

      const move = (e) => {
        const t = e.target;
        if (!(t instanceof win.Element)) return;
        const r = t.getBoundingClientRect();
        hi.style.left = r.left + "px";
        hi.style.top = r.top + "px";
        hi.style.width = r.width + "px";
        hi.style.height = r.height + "px";
      };

      const pick = (e) => {
        // Stop normal navigation / click behaviour
        e.preventDefault();
        e.stopPropagation();
        if (e.stopImmediatePropagation) e.stopImmediatePropagation();

        const t = e.target;
        if (!isValidPick(win, t)) {
          flashBad();
          if (typeof onWarn === "function") {
            onWarn("Please click something clickable (button, link, or interactive element).");
          }
          return;
        }

        const anchor = t.closest("a[href]");
        const selector = makeSelector(t);

        // Build a small display payload (for the UI list)
        let href = "";
        let label = "";
        if (anchor) {
          try {
            const a = anchor;
            const u = new URL(a.href, win.location.href);
            // store path+query only (stable across domains)
            href = (u.pathname.replace(/\/+$/, "") || "/") + (u.search || "");
          } catch (_) {
            href = anchor.getAttribute("href") || "";
          }
          label = (anchor.textContent || "").trim().replace(/\s+/g, " ").slice(0, 80);
        } else if (t && t.textContent) {
          label = t.textContent.trim().replace(/\s+/g, " ").slice(0, 80);
        }

        onPick({ selector, href, label });

        cleanup();
      };

      const esc = (e) => {
        if (e.key === "Escape") cleanup();
      };

      const cleanup = () => {
        try {
          hi.remove();
        } catch (_) {}
        doc.removeEventListener("mousemove", move, true);
        doc.removeEventListener("click", pick, true);
        doc.removeEventListener("keydown", esc, true);
        setPicking(false);
      };

      doc.addEventListener("mousemove", move, true);
      doc.addEventListener("click", pick, true); // capture phase so we intercept before page handlers
      doc.addEventListener("keydown", esc, true);

      return cleanup;
    };

    if (frame.contentDocument && frame.contentDocument.readyState === "complete") {
      return bind();
    } else {
      const onLoad = () => bind();
      frame.addEventListener("load", onLoad, { once: true });
      return () => frame.removeEventListener("load", onLoad);
    }
  }, [picking, goal]);

  const url = `${viewBase}${pageId}&abtestkit_preview=1`;

  const hasPicks = Array.isArray(selected) && selected.length > 0;
  const buttonLabel = picking
    ? "Now click your target in the preview above… (Esc to cancel)"
    : hasPicks
      ? "Select another"
      : "Begin selecting click targets";

  return h(Fragment, null, [
    h("iframe", {
      ref: frameRef,
      src: url,
      style: {
        width: "100%",
        height: "420px",
        border: "1px solid #dcdcde",
        borderRadius: "4px",
        background: "#fff",
        pointerEvents: picking ? "auto" : "none",
      },
            title: label + " preview",
    }),


    // BUTTON + INFO UNDERNEATH
    h(
      "div",
      {
        style: {
          marginTop: 8,
          display: "flex",
          gap: 8,
          alignItems: "center",
        },
      },
      [
        h(
          "button",
          {
            className: "button button-primary button-large abtestkit-picker-button",
            style: {
              padding: "8px 18px",
              fontSize: "14px",
              fontWeight: 600,
            },
            onClick: () => setPicking((s) => !s),
          },
          buttonLabel
        ),
        h(
          "span",
          { style: { color: "#757575" } },
          "Preview: " + label + " • Accepting clickable elements"
        ),
      ]
    ),
  ]);
};


// ─────────────────────────────────────────────────────────────
// PrettyPickedList: shows {label, href, selector} and allows removing
// ─────────────────────────────────────────────────────────────
const PrettyPickedList = ({ picks = [], onRemove }) => {
  const { createElement: h, Fragment } = wp.element;
  if (!picks.length) return null;

  return h("div", { style: { marginTop: 10 } }, [
    h("div", { style: { fontWeight: 600, marginBottom: 6 } }, "Selected targets"),
    h(
      "ul",
      {
        style: {
          listStyle: "none",
          padding: 0,
          margin: 0,
          display: "flex",
          flexDirection: "column",
          gap: 6,
        },
      },
      picks.map((p, i) =>
        h(
          "li",
          {
            key: (p.selector || p.href || "x") + "_" + i,
            style: {
              display: "flex",
              alignItems: "center",
              gap: 8,
              background: "#f6f7f7",
              border: "1px solid #dcdcde",
              borderRadius: 4,
              padding: "6px 8px",
            },
          },
          [
            h("div", null, [
              p.label ? h("strong", null, p.label + " ") : null,
              p.href ? h("code", null, p.href) : null,
              !p.label && !p.href ? h("code", null, p.selector) : null,
              p.selector && (p.label || p.href)
                ? h("div", { style: { color: "#6c7781", fontSize: 12 } }, p.selector)
                : null,
            ]),
            h(
              "button",
              { className: "button button-small", onClick: () => onRemove(p) },
              "× Remove"
            ),
          ]
        )
      )
    ),
  ]);
};


  /* Key-value row used in the summary step (readable + dividers) */
  const ListItem = ({ label, value, noBorder = false }) =>
    h(
      "div",
      {
        style: {
          display: "grid",
          gridTemplateColumns: "190px 1fr", // keeps values close to labels on wide screens
          gap: 12,
          alignItems: "start",
          padding: "10px 0",
          borderBottom: noBorder ? "none" : "1px solid #e5e7eb",
        },
      },
      [
        h(
          "div",
          {
            style: {
              color: "#6c7781",
              fontSize: 13,
              lineHeight: "1.35",
              paddingTop: 2,
            },
          },
          label
        ),
        h(
          "div",
          {
            style: {
              minWidth: 0,
              fontSize: 13,
              lineHeight: "1.45",
              wordBreak: "break-word",
            },
          },
          value
        ),
      ]
    );

  /* WordPress-style list table for selecting pages (with simple pagination) */
  const PageTable = ({ pages, selectedId, onSelect, empty = "No pages found." }) => {
    const [pageIndex, setPageIndex] = useState(0);
    const pageSize = 25;

    const list = Array.isArray(pages) ? pages : [];
    const total = list.length;
    const totalPages = total ? Math.ceil(total / pageSize) : 1;

    // Clamp page index when results change (e.g. new search)
    useEffect(() => {
      if (pageIndex > 0 && pageIndex > totalPages - 1) {
        setPageIndex(0);
      }
    }, [total]);

    const start = pageIndex * pageSize;
    const end = Math.min(start + pageSize, total);
    const visible = total ? list.slice(start, end) : [];

    const table = h(
      "table",
      { className: "wp-list-table widefat fixed striped", style: { marginTop: 12 } },
      [
        h(
          "thead",
          null,
          h("tr", null, [
            h("th", { style: { width: 24 } }, ""), // radio column
            h("th", null, "Title"),
            h("th", { style: { width: 160 } }, "Category"),
            h("th", { style: { width: 120 } }, "Status"),
            h("th", { style: { width: 180 } }, "Date"),
          ])
        ),
        h(
          "tbody",
          null,
          visible && visible.length
            ? visible.map((p) => {
                const isSel = String(selectedId || "") === String(p.id);

                // Mark pages/products that are already in a running test
                const isLocked = !!p.in_running_test;
                const statusLabel = isLocked
                  ? "In running test"
                  : (p.status || "").replace(/^./, (s) => s.toUpperCase());

                return h(
                  "tr",
                  {
                    key: p.id,
                    onClick: () => {
                      if (!isLocked) onSelect(p);
                    },
                    style: {
                      cursor: isLocked ? "not-allowed" : "pointer",
                      opacity: isLocked ? 0.55 : 1,
                    },
                  },
                  [
                    h(
                      "td",
                      null,
                      h("input", {
                        type: "radio",
                        checked: isSel,
                        disabled: isLocked,
                        onChange: () => {
                          if (!isLocked) onSelect(p);
                        },
                      })
                    ),
                    h("td", null, h("strong", null, decodeEntities(p.title || "(no title)"))),
                    h("td", null, p.category || "—"),
                    h("td", null, statusLabel),
                    h("td", null, p.date || "—"),
                  ]
                );
              })
            : h(
                "tr",
                null,
                h(
                  "td",
                  {
                    colSpan: 5,
                    style: { padding: "12px 10px", color: "#6c7781" },
                  },
                  empty
                )
              )
        ),
      ]
    );

const pager =
  totalPages > 1
    ? (() => {
        // Build a compact page number strip (max 7 buttons)
        const maxButtons = 7;
        const half = Math.floor(maxButtons / 2);

        let startPage = Math.max(0, pageIndex - half);
        let endPage = Math.min(totalPages - 1, startPage + (maxButtons - 1));
        startPage = Math.max(0, endPage - (maxButtons - 1));

        const pageButtons = [];
        for (let i = startPage; i <= endPage; i++) {
          const active = i === pageIndex;
          pageButtons.push(
            h(
              Button,
              {
                key: "p" + i,
                isSmall: true,
                isPrimary: active,
                isSecondary: !active,
                onClick: () => setPageIndex(i),
              },
              String(i + 1)
            )
          );
        }

        return h(
          "div",
          {
            style: {
              marginTop: 8,
              display: "flex",
              justifyContent: "space-between",
              alignItems: "center",
              fontSize: 12,
              color: "#6c7781",
              gap: 10,
              flexWrap: "wrap",
            },
          },
          [
            h("span", null, `Showing ${start + 1}–${end} of ${total}`),

            h(
              "div",
              { style: { display: "flex", gap: 4, alignItems: "center", flexWrap: "wrap" } },
              [
                h(
                  Button,
                  {
                    isSmall: true,
                    disabled: pageIndex === 0,
                    onClick: () => setPageIndex(0),
                  },
                  "First"
                ),

                h(
                  Button,
                  {
                    isSmall: true,
                    disabled: pageIndex === 0,
                    onClick: () => setPageIndex((i) => Math.max(0, i - 1)),
                  },
                  "Previous"
                ),

                ...pageButtons,

                h(
                  Button,
                  {
                    isSmall: true,
                    disabled: pageIndex >= totalPages - 1,
                    onClick: () => setPageIndex((i) => Math.min(totalPages - 1, i + 1)),
                  },
                  "Next"
                ),

                h(
                  Button,
                  {
                    isSmall: true,
                    disabled: pageIndex >= totalPages - 1,
                    onClick: () => setPageIndex(totalPages - 1),
                  },
                  "Last"
                ),
              ]
            ),
          ]
        );
      })()
    : null;

    return h("div", null, [table, pager]);
  };


  // Small iframe preview used on the "Review versions" step
const PreviewPane = ({ pageId, label, viewBase, extraQuery = "" }) => {
  if (!pageId) return null;

  const safeExtra =
    typeof extraQuery === "string" && extraQuery.trim() !== ""
      ? extraQuery.trim()
      : "";

  const src = `${viewBase}${pageId}&abtestkit_preview=1${safeExtra}`; // preview disables redirects/tracking

  return wp.element.createElement(
    "div",
    { style: { display: "grid", gap: 8 } },
    [
      label
        ? wp.element.createElement(
            "div",
            { style: { fontWeight: 600 } },
            label
          )
        : null,
      wp.element.createElement("iframe", {
        src,
        title: label || "Preview",
        style: {
          width: "100%",
          height: "460px",
          border: "1px solid #dcdcde",
          borderRadius: "4px",
          background: "#fff",
        },
      }),
    ]
  );
};

function fetchPages(postType, q) {
    if (!postType) return Promise.resolve([]);

    const allowedTypes = ["page", "post", "product"];
    const safeType = allowedTypes.includes(postType) ? postType : "page";
    const typeParam = `&type=${safeType}`;

    return apiFetch({
      path: `/abtestkit/v1/pt/pages?q=${encodeURIComponent(
        q || ""
      )}${typeParam}`,
      headers: { "X-WP-Nonce": cfg.nonce },
    }).then((r) => {
      const list = Array.isArray(r && r.pages) ? r.pages : [];

      return list.filter((p) => {
        if (!p) return false;

        const actualType = String(p.post_type || "").toLowerCase();
        const isProduct =
          !!p.product ||
          actualType === "product" ||
          actualType === "product_variation";

        if (safeType === "product") {
          return isProduct;
        }

        if (safeType === "post") {
          return !isProduct && actualType === "post";
        }

        // For pages, allow anything that is clearly not a product and not a post.
        // This keeps products out, keeps posts out, and avoids live-site payload quirks
        // where page items do not come through as a clean "page" string every time.
        return !isProduct && actualType !== "post";
      });
    });
  }

  function Wizard() {
    const [step, setStep] = useState(0);

    // "Page type" selection: 'page' | 'product'
    const [postType, setPostType] = useState(""); // required on first step

    // Hard reset for anything downstream of test type selection.
    // This prevents "state leaking" (e.g. Version B sticking around across runs).
    const resetWizardState = () => {
      // Lists/search
      setPageSearch("");
      setPages([]);
      setLoading(false);
      setError("");

      // A/B selection
      setPageA(null);
      setTestTitle("");
      setBMode("duplicate");
      setSeoSafeExistingB(true);
      setPageBSearch("");
      setPagesB([]);
      setPageB(null);
      setHasEditedB(false);
      setTempBDraftId(null);
      setRequiresBEdit(false);

      // Goal / conversions
      setGoal("");
      setConversionChosen(false);
      setClickScope("on_test_pages");

      // Click targets
      setLinks("");
      setPrettyPicks([]);
      setShowManualTargets(false);
      setGoalPageSearch("");
      setGoalPages([]);
      setGoalPage(null);

      // Product overrides
      setProductPreviewToken("");
      setProductBTitle("");
      setProductBPrice("");
      setProductBSalePrice("");
      setProductBShortDesc("");
      setProductBLongDesc("");
      setProductBImageUrl("");
      setProductBImageId("");
      setProductBGalleryUrls("");
      setProductBGalleryIds([]);

      // Reset “prefill from A” flags
      shortHydratedRef.current = false;
      longHydratedRef.current = false;
    };

    const selectTestType = (type) => {
      // When user picks a new test type, reset everything else.
      resetWizardState();
      setPostType(type);
      setStep(0);
    };


    // Data
    const [pageSearch, setPageSearch] = useState("");
    const [pages, setPages] = useState([]);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState("");

    const [pageA, setPageA] = useState(null);
    const [testTitle, setTestTitle] = useState("");

    const [bMode, setBMode] = useState("duplicate"); // 'duplicate' | 'existing'

    // Existing-B SEO protection (recommended): noindex + canonical-to-A while used in a test.
    const [seoSafeExistingB, setSeoSafeExistingB] = useState(true);
    const [pageBSearch, setPageBSearch] = useState("");
    const [pagesB, setPagesB] = useState([]);
    const [pageB, setPageB] = useState(null);
    const [hasEditedB, setHasEditedB] = useState(false);
    const [tempBDraftId, setTempBDraftId] = useState(null);
    const [requiresBEdit, setRequiresBEdit] = useState(false);

    // Used to force-refresh the Version B preview iframe (existing-mode only)
    const [previewBNonce, setPreviewBNonce] = useState(0);

    const [goal, setGoal] = useState("");

    // Decision rule (min impressions + min conversions, or manual)
    // fast     = 25 impressions + 3 conversions
    // balanced = 50 impressions + 5 conversions
    // precise  = 75 impressions + 10 conversions
    // manual   = never auto-declares a winner
    const [decisionRule, setDecisionRule] = useState("balanced");

    const [productPreviewToken, setProductPreviewToken] = useState("");
    const [links, setLinks] = useState(""); // comma-separated
    const [prettyPicks, setPrettyPicks] = useState([]);
    const [showManualTargets, setShowManualTargets] = useState(false);
    const [conversionChosen, setConversionChosen] = useState(false);

    // Clicks: where do we track them?
    // "on_test_pages" → Version A/B
    // "other_page"    → a different page you pick
    const [clickScope, setClickScope] = useState("on_test_pages");

    // Pick click targets on a different page
    const [goalPageSearch, setGoalPageSearch] = useState("");
    const [goalPages, setGoalPages] = useState([]);
    const [goalPageLoading, setGoalPageLoading] = useState(false);
    const [goalPage, setGoalPage] = useState(null);

    // Product-only: Version B overrides entered on the "Review versions" step
    const [productBTitle, setProductBTitle] = useState("");
    const [productBPrice, setProductBPrice] = useState("");
    const [productBSalePrice, setProductBSalePrice] = useState("");
    const [productBShortDesc, setProductBShortDesc] = useState("");
    const [productBLongDesc, setProductBLongDesc] = useState("");
    const [productBImageUrl, setProductBImageUrl] = useState("");
    const [productBImageId, setProductBImageId] = useState("");
    const [productBGalleryUrls, setProductBGalleryUrls] = useState("");
    const [productBGalleryIds, setProductBGalleryIds] = useState("");

    const [showProductBTitle, setShowProductBTitle] = useState(false);
    const [showProductBPrice, setShowProductBPrice] = useState(false);
    const [showProductBSalePrice, setShowProductBSalePrice] = useState(false);
    const [showProductBShortDesc, setShowProductBShortDesc] = useState(false);
    const [showProductBLongDesc, setShowProductBLongDesc] = useState(false);
    const [showProductBImage, setShowProductBImage] = useState(false);
    const [showProductBGallery, setShowProductBGallery] = useState(false);

    // Track whether we've already pre-filled Version B descriptions from Version A
    const shortHydratedRef = useRef(false);
    const longHydratedRef = useRef(false);

    // ─────────────────────────────────────────────────────────────
    // Wizard telemetry (session + friction)
    // ─────────────────────────────────────────────────────────────
    const ABTK_WIZ_UI = "pt-wizard-1.2.0";
    const ABTK_WIZ_LS_KEY = "abtk_pt_wizard_session";

    const tlmSessionIdRef = useRef("");
    const tlmStartedAtRef = useRef(0);
    const tlmNavDirRef = useRef("start"); // start|next|back|jump

    const tlmMs = () => abtkSafeInt(Date.now() - (tlmStartedAtRef.current || Date.now()), 0, 86400000);

    const tlmDecisionMode = () => (String(decisionRule || "") === "manual" ? "manual" : "auto");

    const tlmLinksCount = () =>
      (links || "")
        .split(",")
        .map((s) => s.trim())
        .filter(Boolean).length;

    const tlmStepKey = () => {
      // Stable step keys (do NOT depend on step titles)
      if (step === 0) return "select_type";
      if (step === 1) return "select_control";

      if (postType === "product") {
        if (step === 2) return "review_versions";
        if (step === 3) return "choose_conversion_type";
        if (goal === "clicks" && step === 4) return "select_click_targets";
        return "summary";
      }

      // pages/posts
      if (step === 2) return "version_b_source";
      if (step === 3) return "review_versions";
      if (step === 4) return "choose_conversion_type";
      if (goal === "clicks" && step === 5) return "select_click_targets";
      return "summary";
    };

    const tlmBase = () => ({
      session_id: tlmSessionIdRef.current,
      ms: tlmMs(),

      // Receiver already has columns for these
      kind: String(postType || ""),
      goal: String(goal || ""),
      decision_mode: tlmDecisionMode(),
      decision_rule: String(decisionRule || ""),

      // Useful context for analysis (still anonymous)
      b_mode: String(bMode || ""),
      seo_safe_existing_b: bMode === "existing" ? (seoSafeExistingB ? 1 : 0) : 1,

      // Counts only (no URLs/titles)
      links_count: tlmLinksCount(),
      has_b: pageB ? 1 : 0,
      edited_b: hasEditedB ? 1 : 0,
      conversion_chosen: conversionChosen ? 1 : 0,
      click_scope: String(clickScope || ""),
    });

    const tlmSend = (event, extra = {}) =>
      abtkSendTelemetry(event, { ...tlmBase(), ...(extra && typeof extra === "object" ? extra : {}) });

    const tlmPersist = (patch = {}) => {
      try {
        const curRaw = window.localStorage.getItem(ABTK_WIZ_LS_KEY);
        const cur = curRaw ? JSON.parse(curRaw) : {};

        // IMPORTANT: spread `cur` first so it can’t overwrite the current session_id
        const next = {
          ...cur,

          session_id: tlmSessionIdRef.current,
          ui: ABTK_WIZ_UI,
          last_seen: Date.now(),
          step: tlmStepKey(),
          ms: tlmMs(),
          completed: false,
          kind: String(postType || ""),
          goal: String(goal || ""),

          ...patch,
        };

        window.localStorage.setItem(ABTK_WIZ_LS_KEY, JSON.stringify(next));
      } catch (_) {}
    };

    // On mount: close stale session as "abandoned", start a new session, emit session_start
    useEffect(() => {
      if (tlmSessionIdRef.current) return;

      const now = Date.now();

      // If a prior session exists and is stale, mark it abandoned
      try {
        const prevRaw = window.localStorage.getItem(ABTK_WIZ_LS_KEY);
        const prev = prevRaw ? JSON.parse(prevRaw) : null;

        if (
          prev &&
          prev.session_id &&
          !prev.completed &&
          prev.last_seen &&
          now - Number(prev.last_seen) > 30 * 60 * 1000 // 30 min stale = abandoned
        ) {
          abtkSendTelemetry("pt_wizard_result", {
            session_id: String(prev.session_id),
            result: "abandoned",
            step: String(prev.step || ""),
            ms: abtkSafeInt(prev.ms || 0, 0, 86400000),
            kind: String(prev.kind || ""),
            goal: String(prev.goal || ""),
          });
        }
      } catch (_) {}

      tlmSessionIdRef.current = abtkMakeSessionId();
      tlmStartedAtRef.current = now;

      // Existing one-shot wizard milestone (opt-in gated in PHP helper)
      abtkSendTelemetry("pt_wizard_opened", {});

      // New: session start (per-open)
      tlmSend("pt_wizard_session_start", { ui: ABTK_WIZ_UI, step: tlmStepKey() });

      tlmPersist({ completed: false });
    }, []);

    useEffect(() => {
      if (!tlmSessionIdRef.current) return;
      const t = setInterval(() => {
        tlmPersist({});
      }, 20000); // every 20s
      return () => clearInterval(t);
    }, []);

    // Step view event (fires whenever step changes)
    useEffect(() => {
      if (!tlmSessionIdRef.current) return;

      const stepKey = tlmStepKey();
      const dir = String(tlmNavDirRef.current || "jump");

      tlmSend("pt_wizard_step", {
        step: stepKey,
        step_index: abtkSafeInt(step, 0, 50),
        direction: dir,
      });

      tlmNavDirRef.current = "jump";
      tlmPersist({ step: stepKey });
    }, [step]);

    // Action breadcrumbs (lightweight, change-based)
    const tlmPrevRef = useRef({
      postType: "",
      pageA: 0,
      bMode: "",
      pageB: 0,
      hasEditedB: 0,
      goal: "",
      clickScope: "",
      linksCount: 0,
      seoSafe: 1,
      decisionRule: "",
    });

    useEffect(() => {
      const cur = {
        postType: String(postType || ""),
        pageA: pageA && pageA.id ? Number(pageA.id) : 0,
        bMode: String(bMode || ""),
        pageB: pageB && pageB.id ? Number(pageB.id) : 0,
        hasEditedB: hasEditedB ? 1 : 0,
        goal: String(goal || ""),
        clickScope: String(clickScope || ""),
        linksCount: tlmLinksCount(),
        seoSafe: bMode === "existing" ? (seoSafeExistingB ? 1 : 0) : 1,
        decisionRule: String(decisionRule || ""),
      };

      const prev = tlmPrevRef.current || {};

      if (cur.postType && cur.postType !== prev.postType) {
        tlmSend("pt_wizard_action", { action: "select_type", value: cur.postType, step: tlmStepKey() });
      }
      if (cur.pageA && cur.pageA !== prev.pageA) {
        tlmSend("pt_wizard_action", { action: "select_control", value: 1, step: tlmStepKey() });
      }
      if (cur.bMode && cur.bMode !== prev.bMode) {
        tlmSend("pt_wizard_action", { action: "select_b_mode", value: cur.bMode, step: tlmStepKey() });
      }
      if (cur.pageB && cur.pageB !== prev.pageB) {
        tlmSend("pt_wizard_action", { action: "select_b", value: 1, step: tlmStepKey() });
      }
      if (cur.hasEditedB && cur.hasEditedB !== prev.hasEditedB) {
        tlmSend("pt_wizard_action", { action: "edit_b_opened", value: 1, step: tlmStepKey() });
      }
      if (cur.goal && cur.goal !== prev.goal) {
        tlmSend("pt_wizard_action", { action: "select_goal", value: cur.goal, step: tlmStepKey() });
      }
      if (cur.clickScope && cur.clickScope !== prev.clickScope) {
        tlmSend("pt_wizard_action", { action: "select_click_scope", value: cur.clickScope, step: tlmStepKey() });
      }
      if (cur.linksCount !== prev.linksCount) {
        tlmSend("pt_wizard_action", { action: "targets_count_changed", value: cur.linksCount, step: tlmStepKey() });
      }
      if (cur.seoSafe !== prev.seoSafe) {
        tlmSend("pt_wizard_action", { action: "toggle_seo_safe", value: cur.seoSafe, step: tlmStepKey() });
      }
      if (cur.decisionRule && cur.decisionRule !== prev.decisionRule) {
        tlmSend("pt_wizard_action", { action: "select_decision_rule", value: cur.decisionRule, step: tlmStepKey() });
      }

      tlmPrevRef.current = cur;
    }, [
      postType,
      pageA && pageA.id,
      bMode,
      pageB && pageB.id,
      hasEditedB,
      goal,
      clickScope,
      links,
      seoSafeExistingB,
      decisionRule,
    ]);

    // Fetch lists
    useEffect(() => {
      if (!postType) {
        setPages([]);
        return;
      }
      setLoading(true);
      fetchPages(postType, pageSearch)
        .then(setPages)
        .catch((err) => {
          console.error("abtestkit wizard fetch failed:", err);
          setError("Could not load items. Please try a narrower search.");
        })
        .finally(() => setLoading(false));
    }, [pageSearch, postType]);

    useEffect(() => {
      if (bMode !== "existing" || !postType) {
        setPagesB([]);
        return;
      }
      setLoading(true);
      fetchPages(postType, pageBSearch)
        .then((list) => {
          // exclude chosen A
          setPagesB(list.filter((p) => !pageA || p.id !== pageA.id));
        })
        .catch(() => {})
        .finally(() => setLoading(false));
    }, [bMode, pageBSearch, pageA, postType]);

    // Only reset "edited" status when the user explicitly changes the source of B
    // i.e. switching between duplicate <-> existing mode.
    // DO NOT reset when pageB changes (because creating a draft will always change it).
    useEffect(() => {
      // Switching B source should clear B selection/draft and any “edited” state.
      setHasEditedB(false);
      setPageB(null);
      setTempBDraftId(null);
      setPageBSearch("");
      setPagesB([]);
    }, [bMode]);

    // When a duplicate Version B page/post has been created but the test
    // has not yet been saved, warn the user if they try to navigate
    // away from the wizard and offer to delete the duplicate.
    useEffect(() => {
      if (!tempBDraftId) return;
      if (postType !== "page" && postType !== "post" && postType !== "product") return;

      const root = document.getElementById("abtestkit-pt-wizard-root");

      const handleClickAway = (e) => {
        const target = e.target;
        if (root && root.contains(target)) return;

        const link =
          target && target.closest
            ? target.closest("a[href]")
            : null;
        if (!link) return;

        // Let modified clicks (new tab, etc.) behave normally.
        if (
          e.metaKey ||
          e.ctrlKey ||
          e.shiftKey ||
          e.altKey ||
          link.target === "_blank"
        ) {
          return;
        }

        e.preventDefault();

        const msg =
          "You've duplicated a page but haven't finished building this test.\n\n" +
          "If you leave now, the duplicate Version B page/post will be deleted and any unsaved changes in the wizard will be lost.\n\n" +
          "Press OK to delete Version B and leave the wizard.\n" +
          "Press Cancel to stay in the wizard.";

        const confirmDelete = window.confirm(msg);

        // Cancel = stay in the wizard (do nothing)
        if (!confirmDelete) {
          tlmSend("pt_wizard_action", { action: "leave_cancelled", value: 1, step: tlmStepKey() });
          return;
        }

        // Confirmed leave: count this as an abandoned session (explicit)
        tlmSend("pt_wizard_result", { result: "abandoned", step: tlmStepKey() });
        tlmPersist({ completed: true, result: "abandoned" });

        const go = () => {
          setTempBDraftId(null);
          window.location.href = link.href;
        };

        // OK = delete duplicate then leave
        const path =
          postType === "page"
            ? `/wp/v2/pages/${tempBDraftId}?force=true`
            : postType === "post"
            ? `/wp/v2/posts/${tempBDraftId}?force=true`
            : `/wp/v2/product/${tempBDraftId}?force=true`;

        apiFetch({ path, method: "DELETE" })
          .catch(() => {
            // If deletion fails, still navigate away.
          })
          .finally(go);
      };

      document.addEventListener("click", handleClickAway, true);
      return () => {
        document.removeEventListener("click", handleClickAway, true);
      };
    }, [tempBDraftId, postType]);

    // Step 0: must choose test type (page, post or product)
    const canNext0 =
      postType === "page" || postType === "post" || postType === "product";


    // Step 1: must pick Version A
    const canNext1 = !!pageA;

    // Keep a sensible default title based on Version A, but don't overwrite a manual title
    useEffect(() => {
      if (!pageA) {
        setTestTitle("");
        return;
      }

      setTestTitle((current) => {
        const trimmed = String(current || "").trim();
        if (trimmed !== "") return current;
        return decodeEntities(pageA.title || "");
      });
    }, [pageA && pageA.id]);

    // If Version A changes, Version B must be reset (prevents mismatched A/B pairs)
    useEffect(() => {
      setPageB(null);
      setTempBDraftId(null);
      setHasEditedB(false);
      setPageBSearch("");
      setPagesB([]);

      // Also clear conversion config + targets, because they’re tied to the chosen pages
      setGoal("");
      setConversionChosen(false);
      setClickScope("on_test_pages");
      setLinks("");
      setPrettyPicks([]);
      setShowManualTargets(false);
      setGoalPage(null);
      setGoalPages([]);
      setGoalPageSearch("");

      // Reset product preview token + hydration guards (safe even for non-product)
      setProductPreviewToken("");
      shortHydratedRef.current = false;
      longHydratedRef.current = false;
    }, [pageA && pageA.id]);

    // Keep the pretty list in sync with the CSV (manual edits / clears)
    useEffect(() => {
      const tokens = (links || "")
        .split(",")
        .map((s) => s.trim())
        .filter(Boolean);

      if (tokens.length === 0) {
        // If the field is cleared, clear the pretty cards too
        setPrettyPicks([]);
        return;
      }

      const norm = (s) => (s || "").trim().replace(/\/+$/, "");
      const tokenSet = new Set(tokens);

      setPrettyPicks((prev) =>
        prev.filter((p) => {
          const sel = p.selector || "";
          const hrefBase = p.href ? norm(p.href) : "";
          // Accept if the token CSV still contains either the selector
          // or a matching href form (with or without trailing star)
          if (sel && tokenSet.has(sel)) return true;
          if (hrefBase && (tokenSet.has(hrefBase) || tokenSet.has(hrefBase + "*"))) return true;
          return false;
        })
      );
    }, [links]);

    // Shared handler for click picks from Version A, Version B, or other pages
    const handlePick = (picked) => {
      // Prefer URLs (hrefs) when available, they survive redesigns
      const list = (links || "")
        .split(",")
        .map((s) => s.trim())
        .filter(Boolean);

      let token = "";

      if (picked.href && picked.href.length > 1) {
        // Make it domain-independent, add wildcard for safety
        token = picked.href.replace(/\/+$/, "") + "*";
      } else if (picked.selector) {
        // Fallback for elements without links (pure <button> / custom clickable)
        token = picked.selector;
      }

      if (token && !list.includes(token)) {
        list.push(token);
        setLinks(list.join(", "));
      }

      // Maintain pretty list display
      setPrettyPicks((prev) => {
        const key = (x) => (x.selector || "") + "||" + (x.href || "");
        const seen = new Set(prev.map(key));
        if (!seen.has(key(picked))) {
          return [...prev, picked];
        }
        return prev;
      });
    };

    // Fetch other pages when clickScope is "other_page" (non-product tests only)
    useEffect(() => {
      if (postType === "product") return;
      if (goal !== "clicks") return;
      if (clickScope !== "other_page") return;

      const q = (goalPageSearch || "").trim();

      setGoalPageLoading(true);
      // We always search normal pages here, regardless of the test type.
      fetchPages("page", q)
        .then((list) => {
          setGoalPages(list || []);
        })
        .catch(() => {})
        .finally(() => setGoalPageLoading(false));
    }, [postType, goal, clickScope, goalPageSearch]);

    // Step 2 (Version B source):
    // - If using an existing page/product, you must pick Version B.
    // - If duplicating, you can move on and create/edit B on the next step.
    const canNext2 = bMode === "existing" ? !!pageB : true;

    // Step 3 (Review versions):
    // - For pages:
    //     • duplicate: must have created B AND clicked "Edit" once.
    //     • existing: just need a valid B selected.
    // - For products:
    //     • no physical B page; user must change at least one Version B field.
    const hasProductOverrides =
      (productBTitle && productBTitle.trim() !== "") ||
      (productBPrice && productBPrice.trim() !== "") ||
      (productBSalePrice && productBSalePrice.trim() !== "") ||
      (productBShortDesc && productBShortDesc.trim() !== "") ||
      (productBLongDesc && productBLongDesc.trim() !== "") ||
      (productBImageUrl && productBImageUrl.trim() !== "") ||
      (productBGalleryUrls && productBGalleryUrls.trim() !== "") ||
      (Array.isArray(productBGalleryIds) && productBGalleryIds.length > 0);

    // Step 3 (Review versions):
    // - Pages/posts:
    //     • duplicate: must have created B AND clicked “Edit …” once (hasEditedB).
    //     • existing: just need a valid B selected.
    // - Products:
    //     • must have at least one Version B override filled in.
    const canNext3 =
      postType === "product"
        ? !!pageB && hasEditedB
        : bMode === "duplicate"
        ? !!pageB && hasEditedB
        : !!pageB;

    // Step 4: Choose conversion type – must choose a goal
    const canNext4 = conversionChosen;

    // Step 5: Conversion goal – if clicks, require at least one target;
    // if form or add_to_cart, always OK
    const canNext5 =
      goal === "form" ||
      goal === "add_to_cart" ||
      goal === "purchase" ||
      (goal === "clicks" && (links || "").trim().length > 0);


    // Step 6: Summary – final step, no "Next"
    const canNext6 = false;

    const onCreate = (start) => {
      setError("");

      // Telemetry: create attempt
      tlmSend("pt_wizard_create_attempt", {
        step: tlmStepKey(),
        started: start ? 1 : 0,
      });
      // Base payload used for normal page tests
      // Map decision rules -> thresholds
      const decisionMap = {
        fast:     { min_impressions: 25, min_conversions: 3,  decision_mode: "auto" },
        balanced: { min_impressions: 50, min_conversions: 5,  decision_mode: "auto" },
        precise:  { min_impressions: 75, min_conversions: 10, decision_mode: "auto" },
        manual:   { min_impressions: 0,  min_conversions: 0,  decision_mode: "manual" },
      };

      const chosen = decisionMap[String(decisionRule || "balanced")] || decisionMap.balanced;

      const payload = {
        control_id: pageA.id,
        test_title: String(testTitle || "").trim(),
        b_mode: bMode,
        b_page_id: bMode === "existing" ? (pageB && pageB.id) : 0,

        // Existing page as B: optional SEO protection toggle
        seo_safe_existing_b: bMode === "existing" ? !!seoSafeExistingB : true,
        start: !!start,
        split: 50,
        goal,

        // NEW: decision rule + thresholds
        decision_rule: String(decisionRule || "balanced"),
        decision_mode: String(chosen.decision_mode || "auto"),
        min_impressions: Number.isFinite(chosen.min_impressions) ? chosen.min_impressions : 50,
        min_conversions: Number.isFinite(chosen.min_conversions) ? chosen.min_conversions : 5,

        links: links.split(",").map((s) => s.trim()).filter(Boolean),
      };

      // For WooCommerce product tests we never create a physical Version B product.
      // Instead we send field-level overrides for a "virtual" Version B.
      if (postType === "product") {
        payload.b_mode = "duplicate";
        payload.b_page_id = pageB && pageB.id ? pageB.id : 0;
      }

      apiFetch({
        path: "/abtestkit/v1/pt/create",
        method: "POST",
        headers: { "X-WP-Nonce": cfg.nonce, "Content-Type": "application/json" },
        data: payload,
      })
        .then((res) => {
          if (!res || !res.ok) {
            const code = (res && res.error) || "create_failed";

            // Build a friendlier message per error code
            let msg;
            switch (code) {
              case "conflict_running": {
                const base =
                  (res && res.info && res.info.message) ||
                  "You can’t start this test because one or both pages are already used by another running test.";
                const list =
                  res &&
                  res.info &&
                  Array.isArray(res.info.conflicts) &&
                  res.info.conflicts.length
                    ? ` Conflicting test ID: ${res.info.conflicts.join(", ")}.`
                    : "";
                msg = base + list;
                break;
              }
              case "invalid_control":
                msg = "Please select a valid page for Version A (control).";
                break;
              case "invalid_mode":
                msg =
                  "Choose how to create Version B (duplicate this page or use an existing page).";
                break;
              case "create_failed":
                msg = "Couldn’t create the test. Please try again.";
                break;
              default:
                msg = "Something went wrong while creating the test.";
            }

            const err = new Error(msg);
            err.code = code;
            err.info = res && res.info;
            throw err;
          }

          // Telemetry: create succeeded + session completed
          Promise.all([
            tlmSend("pt_wizard_create_succeeded", { step: tlmStepKey(), started: start ? 1 : 0 }),
            tlmSend("pt_wizard_result", { result: "completed", step: tlmStepKey(), started: start ? 1 : 0 }),
          ]).finally(() => {
            tlmPersist({ completed: true, result: "completed" });
            window.location.href = res.redirect || cfg.dashboard;
          });
        })
        .catch((e) => {
        tlmSend("pt_wizard_create_failed", {
          step: tlmStepKey(),
          error_code: String((e && e.code) ? e.code : "create_failed"),
        });
        setError(e.message || "Couldn’t create the test.");
      });
    };

    const createBDraftNow = () => {
      if (!pageA) return Promise.resolve();
      setLoading(true);
      setError("");

      return apiFetch({
        path: "/abtestkit/v1/pt/duplicate",
        method: "POST",
        headers: {
          "X-WP-Nonce": cfg.nonce,
          "Content-Type": "application/json",
        },
        data: { control_id: pageA.id },
      })
        .then((res) => {
          if (!res || !res.ok || !res.page) {
            throw new Error(res && res.error ? res.error : "duplicate_failed");
          }

          setPageB(res.page);

          if (postType === "page" || postType === "post") {
            setTempBDraftId(res.page.id);
          }

          // A new draft B should REQUIRE an edit before Next.
          setHasEditedB(false);

          // Telemetry: variation created
          tlmSend("pt_wizard_action", { action: "duplicate_created", value: 1, step: tlmStepKey() });
        })
        .catch((e) => {
          tlmSend("pt_wizard_action", {
            action: "duplicate_failed",
            value: 1,
            step: tlmStepKey(),
            error_code: String((e && e.message) ? e.message : "duplicate_failed").slice(0, 64),
          });
          setError(e.message || "Failed to create variation");
        })
        .finally(() => setLoading(false));
    };

    /* Step 0 – Select test type (Page, Post or WooCommerce product) */
    const stepType = h(Fragment, null, [
      h("p", null, "What would you like to test?"),
      h(
        "div",
        {
          style: {
            display: "grid",
            gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
            gap: 16,
            marginTop: 8,
          },
        },
        [
          // PAGE CARD
          h(
            Card,
            {
              onClick: () => selectTestType("page"),
              style: {
                cursor: "pointer",
                border:
                  postType === "page"
                    ? "2px solid #2271b1"
                    : "1px solid #dcdcde",
                boxShadow:
                  postType === "page"
                    ? "0 0 0 1px #2271b133"
                    : "0 1px 2px rgba(0,0,0,0.04)",
              },
            },
            h(CardBody, null, [
              h("h3", { style: { margin: 0 } }, "Page"),
              h(
                "p",
                { style: { marginTop: 4, color: "#50575e" } },
                "Any normal WordPress page, like Home, Pricing, or Contact."
              ),
              h(
                "p",
                { style: { marginTop: 4, color: "#6c7781", fontSize: 12 } },
                "Perfect for landing pages, content pages, and lead magnets."
              ),
            ])
          ),

          // POST CARD
          h(
            Card,
            {
              onClick: () => selectTestType("post"),
              style: {
                cursor: "pointer",
                border:
                  postType === "post"
                    ? "2px solid #2271b1"
                    : "1px solid #dcdcde",
                boxShadow:
                  postType === "post"
                    ? "0 0 0 1px #2271b133"
                    : "0 1px 2px rgba(0,0,0,0.04)",
              },
            },
            h(CardBody, null, [
              h("h3", { style: { margin: 0 } }, "Post"),
              h(
                "p",
                { style: { marginTop: 4, color: "#50575e" } },
                "Blog posts and articles in your main post feed."
              ),
              h(
                "p",
                { style: { marginTop: 4, color: "#6c7781", fontSize: 12 } },
                "Ideal for headlines, intros, and featured image experiments."
              ),
            ])
          ),

          // PRODUCT CARD
          h(
            Card,
            {
              onClick: () => selectTestType("product"),
              style: {
                cursor: "pointer",
                border:
                  postType === "product"
                    ? "2px solid #2271b1"
                    : "1px solid #dcdcde",
                boxShadow:
                  postType === "product"
                    ? "0 0 0 1px #2271b133"
                    : "0 1px 2px rgba(0,0,0,0.04)",
              },
            },
            h(CardBody, null, [
              h("h3", { style: { margin: 0 } }, "WooCommerce product"),
              h(
                "p",
                { style: { marginTop: 4, color: "#50575e" } },
                "Optimize product pages."
              ),
              h(
                "p",
                { style: { marginTop: 4, color: "#6c7781", fontSize: 12 } },
                "Test titles, descriptions, images & more."
              ),
            ])
          ),
        ]
      ),
    ]);

    /* Step 1 – Select page/post/product (Version A / control) */
    const step0 = h(Fragment, null, [
      h(
        "p",
        null,
        postType === "product"
          ? "Select a WooCommerce product to test."
          : postType === "post"
          ? "Select a post to test."
          : "Select a page to test."
      ),
      h(SearchControl, {
        label:
          postType === "product"
            ? "Search products"
            : postType === "post"
            ? "Search posts"
            : "Search pages",
        value: pageSearch,
        onChange: setPageSearch,
        placeholder:
          postType === "product"
            ? "Search products…"
            : postType === "post"
            ? "Search posts…"
            : "Search pages…",
      }),
      loading && h(Spinner),
      h(PageTable, {
        pages,
        selectedId: pageA && pageA.id,
        onSelect: (p) => setPageA(p),
        empty:
          postType === "product"
            ? "No matching products."
            : postType === "post"
            ? "No matching posts."
            : "No matching pages.",
      }),
    ]);


    /* Step 1 – Version B source (duplicate vs existing) */
    const step1 =
      postType === "product"
        ? h(
            Fragment,
            null,
            [
              h(
                "p",
                null,
                "For WooCommerce product tests, abtestkit keeps a single real product."
              ),
              h(
                "p",
                { style: { marginTop: 6, color: "#6c7781" } },
                "This keeps your SKU, inventory & orders safe."
              ),
            ]
          )
        : h(Fragment, null, [
            h(RadioControl, {
              label: "How would you like to create Version B?",
              selected: bMode,
              options: [
                { label: "Create a variation of this page", value: "duplicate" },
                { label: "Use an existing page as Version B", value: "existing" },
              ],
              onChange: setBMode,
            }),
            bMode === "existing" &&
              h(Fragment, null, [

                h(
                  "div",
                  { style: { marginTop: 12 } },
                  h(ToggleControl, {
                    label: "SEO-safe mode (recommended)",
                    checked: !!seoSafeExistingB,
                    onChange: () => setSeoSafeExistingB((v) => !v),
                    help: seoSafeExistingB
                      ? "Version B will be hidden from search engines & the frontend of the website while this test is active."
                      : "Version B SEO & frontend visibility will be left unchanged (advanced).",
                  })
                ),
                // Add some breathing room under the radio control
                h(
                  "div",
                  { style: { marginTop: 12 } },
                  h(SearchControl, {
                    label:
                    postType === "product"
                      ? "Search products for Version B"
                      : postType === "post"
                      ? "Search posts for Version B"
                      : "Search pages for Version B",
                    value: pageBSearch,
                    onChange: setPageBSearch,
                    placeholder: "Search…",
                  })
                ),

                loading && h(Spinner),
                h(PageTable, {
                  pages: pagesB,
                  selectedId: pageB && pageB.id,
                  onSelect: (p) => setPageB(p),
                  empty: "Search for a page to use as Version B.",
                }),
              ]),
          ]);

              // Derived attributes for WooCommerce products (Version A / control)
              const productMeta =
                postType === "product" && pageA && pageA.product ? pageA.product : null;

              const productATitle = pageA ? pageA.title : "";
              const productAPrice =
                productMeta && productMeta.regular_price ? productMeta.regular_price : "";
              const productASale =
                productMeta && productMeta.sale_price ? productMeta.sale_price : "";
              const productAShort =
                productMeta && productMeta.short_description
                  ? productMeta.short_description
                  : "";
              const productALong =
                productMeta && productMeta.description
                  ? productMeta.description
                  : "";
              const productAImageUrl =
                productMeta && productMeta.image_url ? productMeta.image_url : "";
              const productAGalleryUrls =
                productMeta &&
                Array.isArray(productMeta.gallery_urls) &&
                productMeta.gallery_urls.length
                  ? productMeta.gallery_urls.join(", ")
                  : "";

              // Plain-text versions for placeholders
              const productAShortPlain = stripHtml(productAShort);
              const productALongPlain = stripHtml(productALong);

              // Prefill Version B editors with Version A content once (but let the user clear them)
              useEffect(() => {
                if (
                  postType === "product" &&
                  productAShort &&
                  !shortHydratedRef.current &&
                  !productBShortDesc
                ) {
                  setProductBShortDesc(productAShort);
                  shortHydratedRef.current = true;
                }
              }, [postType, productAShort, productBShortDesc]);

              useEffect(() => {
                if (
                  postType === "product" &&
                  productALong &&
                  !longHydratedRef.current &&
                  !productBLongDesc
                ) {
                  setProductBLongDesc(productALong);
                  longHydratedRef.current = true;
                }
              }, [postType, productALong, productBLongDesc]);
                  
/* Step 2 – Review versions */
const step2 = h(Fragment, null, [
  postType !== "product" &&
h("p", null, [
  'Click “Edit page” to make changes in a new tab, save and ',
  h("strong", null, "return here"),
  ".",
]),


  h(
    "div",
    { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 } },
    [
      h(
        Card,
        null,
        h(CardBody, null, [
          h("h3", null, "Version A (Control)"),
          pageA ? h("p", null, decodeEntities(`${pageA.title}`)) : null,
          pageA
            ? h(PreviewPane, {
                pageId: pageA.id,
                label: "Preview: Version A",
                viewBase: abtestkit_PT.viewBase,
              })
            : null,
        ])
      ),

      h(
        Card,
        null,
        h(CardBody, null, [
          // Duplicate mode: we auto-create the variation when entering this step
          bMode === "duplicate" && !pageB
            ? h(Fragment, null, [
                h("p", null, "Creating your variation (Version B)…"),
                loading ? h(Spinner) : null,
                h(
                  "p",
                  { style: { marginTop: 8, color: "#6c7781", fontSize: 12 } },
                  "Once it’s ready, click “Edit product” and make at least one change before continuing."
                ),
              ])
            : null,

          // If we have B (either selected existing or just created), show title/button
          ((bMode === "existing" && pageB) || (bMode === "duplicate" && pageB)) &&
            h(Fragment, null, [
              h(
                "div",
                {
                  style: {
                    display: "flex",
                    justifyContent: "space-between",
                    alignItems: "baseline",
                    marginBottom: "4px",
                    paddingBottom: "0",
                  },
                },
                [
                  h("div", null, [
                    h("h3", null, "Version B"),
                    pageB ? h("p", null, decodeEntities(`${pageB.title}`)) : null,
                  ]),

                  h(
                    Button,
                    {
                      href: cfg.editBase + pageB.id + "&action=edit",
                      target: "_blank",
                      onClick: () => setHasEditedB(true),
                      style: {
                        background: "#2271b1",
                        color: "#ffffff",
                        borderRadius: "14px",
                        padding: "6px 14px",
                        border: "1px solid #1b5f8c",
                        fontWeight: 600,
                        cursor: "pointer",
                        marginTop: "2px",
                        whiteSpace: "nowrap",
                      },
                    },
                    postType === "product" ? "Edit Version B" : "Edit Version B"
                  ),
                ]
              ),

              // Only show the Version B iframe when using an existing page/post.
              // (Duplicate mode: remove the iframe entirely.)
              postType !== "product" &&
                bMode === "existing" &&
                h(Fragment, null, [
                  h(PreviewPane, {
                    pageId: pageB.id,
                    label: "Preview: Version B",
                    viewBase: abtestkit_PT.viewBase,
                    extraQuery: previewBNonce ? `&abtestkit_r=${previewBNonce}` : "",
                  }),

                  // Refresh button under the iframe (existing-mode only)
                  h(
                    "div",
                    { style: { marginTop: 8, display: "flex", justifyContent: "flex-end" } },
                    h(
                      Button,
                      {
                        isSecondary: true,
                        onClick: () => setPreviewBNonce(Date.now()),
                      },
                      "Refresh preview"
                    )
                  ),
                ]),

              bMode === "duplicate" &&
                pageB &&
                !hasEditedB &&
                h(
                  "p",
                  { style: { marginTop: 8, color: "#6c7781", fontSize: 12 } },
                  "Click “Edit Version B” above and make at least one change before continuing."
                ),
            ]),
        ])
      ),
    ]
  ),

  error &&
    h(
      Notice,
      { status: "error", isDismissible: false, style: { marginTop: 12 } },
      error
    ),
]);


/* Step 3 – Choose conversion type (cards) */
const step3 = h(Fragment, null, [
  h("p", null, "What do you want to count as a conversion on this page?"),
  h(
    "div",
    {
      style: {
        display: "grid",
        gridTemplateColumns: "1fr 1fr",
        gap: 16,
        marginTop: 8,
      },
    },
    [
      // CLICKS CARD
      h(
        Card,
        {
          onClick: () => {
            setGoal("clicks");
            setConversionChosen(true);
            setClickScope("on_test_pages");
          },
          style: {
            cursor: "pointer",
            border:
              goal === "clicks" ? "2px solid #2271b1" : "1px solid #dcdcde",
            boxShadow:
              goal === "clicks" ? "0 0 0 1px #2271b1" : "none",
          },
        },
        h(CardBody, null, [
          h("h3", null, "Clicks"),
          h(
            "p",
            { style: { marginTop: 4, color: "#50575e" } },
            "Track clicks on buttons, links, or any clickable element."
          ),
          h(
            "p",
            { style: { marginTop: 4, color: "#6c7781", fontSize: 12 } },
            "Best for CTAs, navigation, and click-throughs."
          ),
        ])
      ),

      // FORMS CARD
      h(
        Card,
        {
          onClick: () => {
            setGoal("form");
            setConversionChosen(true);
          },
          style: {
            cursor: "pointer",
            border:
              goal === "form" ? "2px solid #2271b1" : "1px solid #dcdcde",
            boxShadow:
              goal === "form" ? "0 0 0 1px #2271b1" : "none",
          },
        },
        h(CardBody, null, [
          h("h3", null, "Form submissions"),
          h(
            "p",
            { style: { marginTop: 4, color: "#50575e" } },
            "Count a conversion when a form on the page is successfully submitted."
          ),
          h(
            "p",
            { style: { marginTop: 4, color: "#6c7781", fontSize: 12 } },
            "Best for lead gen, contact, or signup forms."
          ),
        ])
      ),

      // ADD TO CART CARD – only show for WooCommerce products
      postType === "product" &&
        h(
          Card,
          {
            onClick: () => {
              setGoal("add_to_cart");
              setConversionChosen(true);
            },
            style: {
              cursor: "pointer",
              border:
                goal === "add_to_cart"
                  ? "2px solid #2271b1"
                  : "1px solid #dcdcde",
              boxShadow:
                goal === "add_to_cart" ? "0 0 0 1px #2271b1" : "none",
            },
          },
          h(CardBody, null, [
            h("h3", null, "Add to cart"),
            h(
              "p",
              { style: { marginTop: 4, color: "#50575e" } },
              "Count a conversion when this product is successfully added to the cart."
            ),
            h(
              "p",
              { style: { marginTop: 4, color: "#6c7781", fontSize: 12 } },
              "Best for product intent and basket-rate tests."
            ),
          ])
        ),

      // ORDERS (REVENUE) CARD – only show for WooCommerce products
      postType === "product" &&
        h(
          Card,
          {
            onClick: () => {
              setGoal("purchase");
              setConversionChosen(true);
            },
            style: {
              cursor: "pointer",
              border:
                goal === "purchase"
                  ? "2px solid #2271b1"
                  : "1px solid #dcdcde",
              boxShadow:
                goal === "purchase" ? "0 0 0 1px #2271b1" : "none",
            },
          },
          h(CardBody, null, [
            h("h3", null, "Orders (Revenue)"),
            h(
              "p",
              { style: { marginTop: 4, color: "#50575e" } },
              "Count a conversion when an order containing this product is placed, and track revenue from that product."
            ),
            h(
              "p",
              { style: { marginTop: 4, color: "#6c7781", fontSize: 12 } },
              "Best for completed sales, revenue per visitor, and revenue per customer."
            ),
          ])
        ),
    ]
  ),
  h(
    "p",
    { style: { marginTop: 12, color: "#6c7781" } },
    "You can fine-tune the details on the next step."
  ),
]);

/* Step 4 – Conversion goal (existing logic, now extended for A/B + other page) */
const step4 = h(Fragment, null, [
  h("p", null, "Fine-tune how we count a conversion."),

  // Short reminder of what you've chosen
  goal === "clicks" &&
    h(
      "p",
      { style: { marginTop: 0, color: "#6c7781" } },
      "You're counting a conversion when someone clicks one of the targets you choose below."
    ),

      goal === "clicks" &&
    h(RadioControl, {
      label: "Where are the clicks you want to track?",
      selected: clickScope,
      options: [
        {
          label: "On this test page (Version A/B)",
          value: "on_test_pages",
        },
        {
          label: "On a different page",
          value: "other_page",
        },
      ],
      onChange: setClickScope,
    }),

  goal === "form" &&
    h(
      "p",
      { style: { marginTop: 0, color: "#6c7781" } },
      "You're counting a conversion when a form on this page is successfully submitted."
    ),

  goal === "add_to_cart" &&
    h(
      "p",
      { style: { marginTop: 0, color: "#6c7781" } },
      "You're counting a conversion when this product is successfully added to the cart."
    ),

  goal === "purchase" &&
    h(
      "p",
      { style: { marginTop: 0, color: "#6c7781" } },
      "You're counting a conversion when an order containing this product is placed. Revenue will also be tracked for this test."
    ),

  // Click pickers for Version A + Version B (for page tests)
  goal === "clicks" &&
  clickScope === "on_test_pages" &&
    pageA &&
    h(
      "div",
      {
        style: {
          marginTop: 12,
          display: "grid",
          gridTemplateColumns:
            postType !== "product" && pageB ? "1fr 1fr" : "1fr",
          gap: 16,
        },
      },
      [
        h(
          "div",
          null,
          [
            h(
              "h4",
              { style: { marginTop: 0, marginBottom: 8 } },
              "Pick targets on Version A"
            ),
            h(ElementPicker, {
              pageId: pageA.id,
              viewBase: abtestkit_PT.viewBase,
              goal,
              label: "Version A",
              selected: (links || "")
                .split(",")
                .map((s) => s.trim())
                .filter(Boolean),
              onWarn: (msg) => {
                console.warn("[abtestkit picker]", msg);
              },
              onPick: handlePick,
            }),
          ]
        ),
        postType !== "product" &&
          pageB &&
          h(
            "div",
            null,
            [
              h(
                "h4",
                { style: { marginTop: 0, marginBottom: 8 } },
                "Pick targets on Version B"
              ),
              h(ElementPicker, {
                pageId: pageB.id,
                viewBase: abtestkit_PT.viewBase,
                goal,
                label: "Version B",
                selected: (links || "")
                  .split(",")
                  .map((s) => s.trim())
                  .filter(Boolean),
                onWarn: (msg) => {
                  console.warn("[abtestkit picker]", msg);
                },
                onPick: handlePick,
              }),
            ]
          ),
      ]
    ),

      // "Clicks on a different page" flow (page tests only)
    goal === "clicks" &&
      clickScope === "other_page" &&
      postType !== "product" &&
      h(
        "div",
      {
        style: {
          marginTop: 16,
          paddingTop: 8,
          borderTop: "1px solid #dcdcde",
        },
      },
      [
        h(
          "h4",
          { style: { marginTop: 0, marginBottom: 8 } },
          "Pick the page where the conversion click happens"
        ),
        h(SearchControl, {
          label: "Search pages for your conversion page",
          value: goalPageSearch,
          onChange: setGoalPageSearch,
          placeholder: "Search by title…",
        }),
        goalPageLoading && h(Spinner),
        h(PageTable, {
          pages: goalPages,
          selectedId: goalPage && goalPage.id,
          onSelect: (p) => setGoalPage(p),
          empty: "No matching pages.",
        }),
        goalPage &&
          h(
            "div",
            { style: { marginTop: 12 } },
            [
              h(
                "p",
                {
                  style: {
                    margin: "0 0 6px",
                    fontSize: 12,
                    color: "#6c7781",
                  },
                },
                "Now click a target in the preview to track conversions from this page."
              ),
              h(ElementPicker, {
                pageId: goalPage.id,
                viewBase: abtestkit_PT.viewBase,
                goal,
                label: goalPage.title || "Conversion page",
                selected: (links || "")
                  .split(",")
                  .map((s) => s.trim())
                  .filter(Boolean),
                onWarn: (msg) => {
                  console.warn("[abtestkit picker]", msg);
                },
                onPick: handlePick,
              }),
            ]
          ),
      ]
    ),

  // Advanced manual field (hidden under a small "Advanced" dropdown)
  goal === "clicks" &&
    h(
      "div",
      { style: { marginTop: 12, marginBottom: 4 } },
      [
        h(
          Button,
          {
            isSecondary: true,
            isSmall: true,
            onClick: () => setShowManualTargets((s) => !s),
          },
          showManualTargets ? "Hide advanced" : "Advanced"
        ),
        showManualTargets &&
          h(
            "div",
            {
              style: {
                marginTop: 6,
                padding: "8px 10px",
                background: "#f6f7f7",
                borderRadius: 4,
                border: "1px solid #dcdcde",
              },
            },
            [
              h(
                "p",
                {
                  style: {
                    margin: "0 0 6px",
                    fontSize: 12,
                    color: "#6c7781",
                  },
                },
                "Manually pick target URLs or CSS selectors (advanced)."
              ),
              h(TextControl, {
                className: "abtestkit-targets-input",
                label:
                  "Targets (comma-separated URLs or CSS selectors)",
                help:
                  "Advanced users only — the click picker above fills this automatically.",
                value: links,
                onChange: setLinks,
                placeholder: "",
              }),
            ]
          ),
      ]
    ),

  // Pretty list of picked targets (cards) – BELOW the manual box
  goal === "clicks" &&
    h(PrettyPickedList, {
      picks: prettyPicks,
      onRemove: (p) => {
        // Remove from the visible list
        setPrettyPicks((prev) =>
          prev.filter(
            (x) =>
              (x.selector || "") + "||" + (x.href || "") !==
              (p.selector || "") + "||" + (p.href || "")
          )
        );

        // Also remove corresponding tokens from the CSV (selector and/or href)
        const norm = (s) =>
          (s || "").trim().replace(/\/+$/, "");
        const raw = (links || "")
          .split(",")
          .map((s) => s.trim())
          .filter(Boolean);

        const removeSet = new Set();
        if (p.selector) removeSet.add(p.selector);

        if (p.href) {
          const base = norm(p.href);
          // tokens might be stored as "path?x=y*" or "path?x=y"
          removeSet.add(base);
          removeSet.add(base + "*");
        }

        const filtered = raw.filter((t) => {
          const tNormNoStar = norm(t.replace(/\*$/, ""));
          // remove if exact match, wildcard match, or normalized match
          if (removeSet.has(t)) return false;
          if (removeSet.has(tNormNoStar)) return false;
          if (removeSet.has(tNormNoStar + "*")) return false;
          return true;
        });

        setLinks(filtered.join(", "));
      },
    }),
]);

/* Step 6 – Summary */

// Default-friendly labels for the test title field
const defaultSuggestedTitle =
  decodeEntities(
    (pageA && pageA.title) ? pageA.title : ""
  );

const summaryTestTitle =
  String(testTitle || "").trim() || defaultSuggestedTitle;

// Preview URLs for Version A/B in a new tab (no tracking / no impressions)
const previewUrlA =
  pageA && pageA.id
    ? `${abtestkit_PT.viewBase}${pageA.id}&abtestkit_preview=1${
        postType === "product" ? "&abtestkit_force=A" : ""
      }`
    : "";

// For product tests, Version B needs a temporary preview token (before the test exists)
// otherwise it will look identical to Version A.
const productNeedsPreviewToken =
  postType === "product" &&
  !!(
    (productBTitle && productBTitle.trim() !== "") ||
    (productBPrice && productBPrice.trim() !== "") ||
    (productBSalePrice && productBSalePrice.trim() !== "") ||
    (productBShortDesc && productBShortDesc.trim() !== "") ||
    (productBLongDesc && productBLongDesc.trim() !== "") ||
    (productBImageUrl && productBImageUrl.trim() !== "") ||
    (Number.isInteger(productBImageId) && productBImageId > 0) ||
    (productBGalleryUrls &&
      productBGalleryUrls
        .split(",")
        .map((s) => s.trim())
        .filter(Boolean).length > 0) ||
    (Array.isArray(productBGalleryIds) && productBGalleryIds.length > 0)
  );

const previewUrlB =
  postType === "product"
    ? (pageA && pageA.id
        ? `${abtestkit_PT.viewBase}${pageA.id}&abtestkit_preview=1&abtestkit_force=B`
        : "")
    : (pageB && pageB.id
        ? `${abtestkit_PT.viewBase}${pageB.id}&abtestkit_preview=1`
        : "");

const step5 = h(Fragment, null, [
h(
  Card,
  { style: { marginBottom: 16 } },
    h(CardBody, null, [
      h("h3", { style: { marginTop: 0, marginBottom: 12 } }, "Test title"),
h(
  "div",
  {
    style: {
      position: "relative",
      maxWidth: "520px",
    },
  },
  [
h(
  "div",
  {
    style: {
      maxWidth: "520px",
    },
  },
  [
    h(
      "div",
      {
        style: {
          position: "relative",
        },
      },
      [
        h("input", {
         className: "abtestkit-test-title-input",
          type: "text",
          value: testTitle,
          onChange: (e) => setTestTitle(e.target.value),
          placeholder: defaultSuggestedTitle || "e.g. Product page hero CTA test",
          style: {
            width: "100%",
            height: "40px",
            padding: "0 42px 0 12px",
            border: "1px solid #8c8f94",
            borderRadius: "2px",
            fontSize: "16px",
            lineHeight: "1.4",
            boxSizing: "border-box",
            background: "#fff",
          },
        }),

        h(
          "button",
          {
            type: "button",
            onClick: () => {
              const input = document.querySelector(".abtestkit-test-title-input");
              if (input) input.focus();
            },
            style: {
              position: "absolute",
              right: "10px",
              top: "50%",
              transform: "translateY(-50%)",
              border: "0",
              background: "transparent",
              padding: 0,
              margin: 0,
              width: "18px",
              height: "18px",
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
              cursor: "pointer",
              color: "#2271b1",
            },
            "aria-label": "Edit test title",
            title: "Edit test title",
          },
          h("span", {
            className: "dashicons dashicons-edit",
            style: {
              fontSize: "16px",
              width: "16px",
              height: "16px",
            },
          })
        ),
      ]
    ),

    h(
      "p",
      {
        style: {
          marginTop: "8px",
          marginBottom: 0,
          color: "#6c7781",
          fontSize: "12px",
        },
      },
      "Give your test a name to help you identify it later."
    ),
  ]
),
  ]
),
    ])
  ),
  h("p", null, "Review and confirm:"),
  h(
    Card,
    null,
    h(CardBody, null, [
      h(ListItem, {
        label: "Test title",
        value: summaryTestTitle || "—",
      }),
      h(ListItem, {
        label: "Test type",
        value:
          postType === "product"
            ? "WooCommerce product"
            : postType === "post"
            ? "Post"
            : "Page",
      }),


      h(ListItem, {
        label: "Version A (Control)",
        value: pageA ? decodeEntities(`${pageA.title}`) : "—",
      }),
      h(ListItem, {
        label: postType === "product" ? "Version B" : "Version B Source",
        value:
          postType === "product"
            ? h(
                "div",
                {
                  style: {
                    display: "flex",
                    flexDirection: "column",
                    alignItems: "flex-start", // left-align Version B details
                    lineHeight: "1.4",
                    gap: "2px",
                  },
                },
                (() => {
                  const parts = [];

                  if (productBTitle && productBTitle.trim() !== "") {
                    parts.push(`Title: ${productBTitle.trim()}`);
                  }

                  if (productBPrice && productBPrice.trim() !== "") {
                    parts.push(`Regular price: ${productBPrice.trim()}`);
                  }

                  if (productBSalePrice && productBSalePrice.trim() !== "") {
                    parts.push(`Sale price: ${productBSalePrice.trim()}`);
                  }

                  if (productBShortDesc && productBShortDesc.trim() !== "") {
                    parts.push("Short description changed");
                  }

                  if (productBLongDesc && productBLongDesc.trim() !== "") {
                    parts.push("Description changed");
                  }

                  if (productBImageUrl && productBImageUrl.trim() !== "") {
                    parts.push("Product image changed");
                  }

                  if (productBGalleryUrls && productBGalleryUrls.trim() !== "") {
                    parts.push("Gallery images changed");
                  }

                  if (!parts.length) {
                    return ["Same product (no overrides set)"];
                  }

                  return parts.map((p, i) =>
                    h(
                      "div",
                      { key: i, style: { whiteSpace: "nowrap" } },
                      p
                    )
                  );
                })()
              )
            : bMode === "duplicate"
            ? "Duplicate Version A"
            : pageB
            ? `${pageB.title}`
            : "—",
      }),
      // Human-friendly goal label
      h(ListItem, {
        label: "Conversion Goal",
        value:
          goal === "form"
            ? "Form submission"
            : goal === "add_to_cart"
            ? "Add to cart"
            : goal === "purchase"
            ? "Orders (Revenue)"
            : "Clicks (buttons/links/other)",
      }),

      // where clicks are tracked
      goal === "clicks"
        ? h(ListItem, {
            label: "Clicks tracked on",
            value:
              clickScope === "other_page"
                ? (goalPage && goalPage.title
                    ? `Different page: ${goalPage.title}`
                    : "A different page")
                : "This test page (Version A/B)",
          })
        : null,

      // Existing: list the targets
      goal === "clicks"
        ? h(ListItem, {
            label: "Click Targets (URLs/selectors)",
            value: links || "—",
          })
        : null,

      h(ListItem, { label: "Split", value: "50% to Version B" }),

      h(
        "div",
        { style: { marginTop: 12 } },
        h(SelectControl, {
          label: "AUTO TEST RULE",
          value: String(decisionRule),
          options: [
            { label: "Fast, less precise (25 impressions + 3 conversions)", value: "fast" },
            { label: "Balanced (50 impressions + 5 conversions)", value: "balanced" },
            { label: "Precise (75 impressions + 10 conversions)", value: "precise" },
            { label: "Manual (never auto-confirm a winner)", value: "manual" },
          ],
          onChange: (v) => setDecisionRule(v),
          help:
            String(decisionRule) === "manual"
              ? "Manual mode never auto-confirms a winner. You can apply the current leader from the dashboard."
              : "A winner will not be declared until both the impressions and conversions minimums are met.",
        })
      ),

      // Unified preview links row – continuous underline across text + icon
      (previewUrlA || previewUrlB) &&
        h(
          "div",
          {
            style: {
              marginTop: 12,
              display: "flex",
              justifyContent: "flex-end",
              alignItems: "center",
              gap: 24,
              fontSize: 12,
            },
          },
          [
            previewUrlA &&
              h(
                "a",
                {
                  href: previewUrlA,
                  target: "_blank",
                  rel: "noopener noreferrer",
                  style: {
                    textDecoration: "none", // we'll draw our own underline
                  },
                },
                h(
                  "span",
                  {
                    style: {
                      display: "inline-flex",
                      alignItems: "center",
                      gap: 4,
                      borderBottom: "1px solid #2271b1",
                      paddingBottom: 1,
                    },
                  },
                  [
                    "Preview Version A",
                    h("span", {
                      className: "dashicons dashicons-external",
                      style: {
                        fontSize: 14,
                      },
                    }),
                  ]
                )
              ),

            previewUrlB &&
              h(
                "a",
                {
                  href: previewUrlB,
                  target: "_blank",
                  rel: "noopener noreferrer",
                  style: {
                    textDecoration: "none",
                  },
                },
                h(
                  "span",
                  {
                    style: {
                      display: "inline-flex",
                      alignItems: "center",
                      gap: 4,
                      borderBottom: "1px solid #2271b1",
                      paddingBottom: 1,
                    },
                  },
                  [
                    "Preview Version B",
                    h("span", {
                      className: "dashicons dashicons-external",
                      style: {
                        fontSize: 14,
                      },
                    }),
                  ]
                )
              ),
          ]
        ),
    ])
  ),
  error && h(Notice, { status: "error", isDismissible: false }, error),
]);

    // ── Updated steps array ─────────────────────────
    // For WooCommerce products we skip the "Version B source" step.
    let steps;
    if (postType === "product") {
      steps = [
        {
          title: "Select test type",
          content: stepType,
          canNext: canNext0,
        },
        {
          title: "Select product",
          content: step0,
          canNext: canNext1,
        },
        {
          title: "Review versions",
          content: step2,
          canNext: canNext3,
        },
        {
          title: "Choose conversion type",
          content: step3,
          canNext: canNext4,
        },
      ]
        .concat(
          goal === "clicks"
            ? [
                {
                  title: "Select click targets",
                  content: step4,
                  canNext: canNext5,
                },
              ]
            : []
        )
        .concat([
          {
            title: "Summary",
            content: step5,
            canNext: canNext6,
          },
        ]);
    } else {
      // Default flow for normal pages/posts – keep all 7 steps
      steps = [
        {
          title: "Select test type",
          content: stepType,
          canNext: canNext0,
        },
        {
          title: postType === "post" ? "Select post" : "Select page",
          content: step0,
          canNext: canNext1,
        },
        {
          title: "Version B source",
          content: step1,
          canNext: canNext2,
        },
        {
          title: "Review versions",
          content: step2,
          canNext: canNext3,
        },
        {
          title: "Choose conversion type",
          content: step3,
          canNext: canNext4,
        },
      ]
        .concat(
          goal === "clicks"
            ? [
                {
                  title: "Select click targets",
                  content: step4,
                  canNext: canNext5,
                },
              ]
            : []
        )
        .concat([
          {
            title: "Summary",
            content: step5,
            canNext: canNext6,
          },
        ]);
    }

    // If the user changes conversion type, the number of steps can change.
    // Clamp the current step so we never point past the end of the steps array.
    useEffect(() => {
      const max = steps.length - 1;
      if (step > max) setStep(max);
    }, [goal, postType, steps.length]);

    // Next button state and hover helper

    const isLastStep = step === steps.length - 1;
    const canGoNext = steps[step].canNext;
    const nextDisabled = isLastStep || !canGoNext;

    // Tooltip hints and blocking logic (index-based = reliable)
    let nextTitle = "";

    // Determine the absolute index of key steps (varies when click-target step is hidden)
    const showClickTargetsStep = goal === "clicks";

    const reviewStepIndex = postType === "product" ? 2 : 3;
    const conversionTypeIndex = postType === "product" ? 3 : 4;

    // Only exists when goal === "clicks"
    const conversionGoalIndex = showClickTargetsStep
      ? (postType === "product" ? 4 : 5)
      : -1;

    // Auto-create the variation (Version B) when entering Review versions in "duplicate" mode
    const autoCreatedBRef = useRef(false);

    useEffect(() => {
      // Only pages/posts use physical B pages
      // if (postType === "product") return;

      // Only in duplicate mode
      if (bMode !== "duplicate") {
        autoCreatedBRef.current = false;
        return;
      }

      // Only when we're on the Review Versions step
      if (step !== reviewStepIndex) return;

      // If B already exists or we're already creating it, do nothing
      if (pageB || loading) return;

      // Prevent double-creation on rerenders
      if (autoCreatedBRef.current) return;
      autoCreatedBRef.current = true;

      createBDraftNow();
    }, [step, reviewStepIndex, postType, bMode, pageB, loading]);

    // Review Versions blocking
    if (step === reviewStepIndex && !canGoNext) {
      if (postType === "product") {
        nextTitle = "Edit at least one Version B field";
      } else {
        nextTitle = "Edit Version B first";
      }
    }

    // Conversion type blocking
    else if (step === conversionTypeIndex && !canGoNext) {
      nextTitle = "Choose a conversion goal";
    }

    // Click targets blocking (only when that step exists)
    else if (conversionGoalIndex !== -1 && step === conversionGoalIndex && goal === "clicks" && !canGoNext) {
      nextTitle = "Select at least one click target";
    }

    // Hide the left-hand stepper on the first screen so the
    // page-type cards can use the full width.
    const showStepper = step > 0 && !!postType;
    const layoutStyle = showStepper
      ? {
          display: "grid",
          gridTemplateColumns: "172px 1fr",
          gap: 12,
          alignItems: "start",
        }
      : {
          display: "block",
        };

    return h(
      "div",
      {
        style: layoutStyle,
      },
      [
        // Left stepper (hidden until a page/product type is chosen)
        showStepper
          ? h(
              "div",
              null,
              steps.map((s, i) =>
                h(Step, { key: i, title: s.title, index: i, current: step })
              )
            )
          : null,

        // Right content
        h("div", null, [
          h(Card, null, h(CardBody, null, [
            h("h2", { style: { marginTop: 0 } }, steps[step].title),
            steps[step].content,
            h("div", { style: { display: "flex", justifyContent: "space-between", marginTop: 16 } }, [
              // BACK
              h(
                Button,
                {
                  isSecondary: true,
                  disabled: step === 0,
onClick: () => {
                    const isClickGoal = goal === "clicks";

                    // This is the step where click targets are selected
                    const clickTargetStep = postType === "product" ? 4 : 5;

                    const leavingClickTargets = isClickGoal && step === clickTargetStep;

                    const hasTargets =
                      (links && links.trim().length > 0) ||
                      (prettyPicks && prettyPicks.length > 0);

                    if (leavingClickTargets && hasTargets) {
                      const ok = window.confirm(
                        "Going back will clear your selected click targets.\n\nPress OK to continue."
                      );

                      if (!ok) return;

                      // Clear click targets + related state
                      setLinks("");
                      setPrettyPicks([]);
                      setShowManualTargets(false);

                      // Clear 'other page' selection if used
                      setGoalPage(null);
                      setGoalPages([]);
                      setGoalPageSearch("");

                      tlmSend("pt_wizard_action", { action: "cleared_targets_on_back", value: 1, step: tlmStepKey() });
                    }

                    tlmNavDirRef.current = "back";
                    setStep(Math.max(0, step - 1));

                    setTimeout(() => {
                      window.scrollTo({ top: 0, behavior: "auto" });
                    }, 0);
                  },
                },
                "Back"
              ),

              // RIGHT SIDE
              isLastStep
                ? h(
                    "span",
                    { style: { display: "inline-flex", gap: 8 } },
                    [
                      h(
                        Button,
                        {
                          isSecondary: true,
                          onClick: () => onCreate(false),
                        },
                        "Save as draft"
                      ),
                      h(
                        Button,
                        {
                          isPrimary: true,
                          onClick: () => onCreate(true),
                        },
                        "Start test"
                      ),
                    ]
                  )
                : h(
                    Tooltip,
                    { text: (nextDisabled && nextTitle) ? nextTitle : "", position: "top" },
                    h(
                      "span",
                      {
                        style: {
                          display: "inline-block",
                          cursor: nextDisabled ? "not-allowed" : "pointer",
                        },
                        onClick: () => {
                          if (nextDisabled) {
                            // Blocked-next telemetry (reason code)
                            let reason = "blocked";
                            const sk = tlmStepKey();

                            if (sk === "select_type") reason = "missing_test_type";
                            else if (sk === "select_control") reason = "missing_control";
                            else if (sk === "version_b_source" && bMode === "existing" && !pageB) reason = "missing_version_b";
                            else if (sk === "review_versions" && !hasEditedB) reason = "edit_version_b_required";
                            else if (sk === "choose_conversion_type" && !conversionChosen) reason = "missing_goal";
                            else if (sk === "select_click_targets" && goal === "clicks" && tlmLinksCount() < 1) reason = "missing_click_targets";

                            tlmSend("pt_wizard_blocked", {
                              step: sk,
                              error_code: reason,
                            });

                            return;
                          }

                          tlmNavDirRef.current = "next";
                          setStep(step + 1);

                          setTimeout(() => {
                            window.scrollTo({ top: 0, behavior: "auto" });
                          }, 0);
                        },
                      },
                      h(
                        Button,
                        {
                          isPrimary: true,
                          disabled: nextDisabled,
                          style: nextDisabled ? { pointerEvents: "none" } : undefined, // wrapper handles click tracking
                          title: (nextDisabled && nextTitle) ? nextTitle : undefined,
                          "aria-disabled": nextDisabled ? "true" : undefined,
                        },
                        "Next"
                      )
                    )
                  ),
            ]),
          ])),
          h(TipsPanel, { postType, step }),
        ]),
      ]
    );
  }

  wp.element.render(h(Wizard), document.getElementById("abtestkit-pt-wizard-root"));
})(window.wp);
