"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var blueprint_exports = {};
__export(blueprint_exports, {
  BlueprintInvariantError: () => BlueprintInvariantError,
  applyPositionHintToNamelessElements: () => applyPositionHintToNamelessElements,
  buildMap: () => buildMap,
  buildOutline: () => buildOutline,
  buildPageBlueprint: () => buildPageBlueprint,
  deriveLogicalName: () => deriveLogicalName,
  resolveAccessibleName: () => resolveAccessibleName,
  resolveTemplate: () => resolveTemplate,
  resolveXpath: () => resolveXpath,
  validateElement: () => validateElement,
  validateRepeatingElement: () => validateRepeatingElement,
  xpathLiteralNode: () => xpathLiteralNode
});
module.exports = __toCommonJS(blueprint_exports);
var import_serialization = require("./serialization");
var import_sectionGrouper = require("./sectionGrouper");
var import_logicalNameResolver = require("./logicalNameResolver");
var import_slug = require("./slug");
class BlueprintInvariantError extends Error {
  constructor(message) {
    super(message);
    this.name = "BlueprintInvariantError";
  }
}
const CANONICAL_ARIA_ROLES = /* @__PURE__ */ new Set([
  "button",
  "link",
  "textbox",
  "searchbox",
  "combobox",
  "checkbox",
  "radio",
  "switch",
  "slider",
  "spinbutton",
  "tab",
  "tabpanel",
  "menuitem",
  "menu",
  "menubar",
  "option",
  "listbox",
  "heading",
  "banner",
  "navigation",
  "main",
  "dialog",
  "alertdialog",
  "region",
  "form",
  "search",
  "toolbar",
  "tree",
  "treeitem",
  "grid",
  "gridcell",
  "row",
  "columnheader",
  "rowheader"
]);
const ROLE_SUFFIX = {
  button: "btn",
  link: "link",
  textbox: "input",
  searchbox: "input",
  combobox: "select",
  checkbox: "checkbox",
  radio: "radio",
  heading: "heading",
  tab: "tab",
  menuitem: "item",
  switch: "toggle",
  spinbutton: "input",
  slider: "slider",
  option: "option"
};
function deriveLogicalName(role, accessibleName) {
  const suffix = ROLE_SUFFIX[role] ?? role.toLowerCase();
  const base = (0, import_slug.slug)(accessibleName);
  if (!base) return "";
  const baseTokens = base.split("_");
  if (baseTokens[baseTokens.length - 1] === suffix) baseTokens.pop();
  if (baseTokens[0] === suffix) baseTokens.shift();
  const cleanedBase = baseTokens.join("_");
  if (!cleanedBase) return "";
  return `${cleanedBase}_${suffix}`;
}
function applyPositionHintToNamelessElements(raws) {
  const sameRoleCountsBySection = /* @__PURE__ */ new Map();
  return raws.map(({ role, accessibleName, sectionName }) => {
    const derived = deriveLogicalName(role, accessibleName);
    if (derived) return derived;
    const key = `${sectionName}::${role}`;
    const next = (sameRoleCountsBySection.get(key) ?? 0) + 1;
    sameRoleCountsBySection.set(key, next);
    return `${role}_${next}`;
  });
}
function xpathLiteralNode(value) {
  if (!value.includes('"'))
    return `"${value}"`;
  if (!value.includes("'"))
    return `'${value}'`;
  const parts = value.split('"');
  const pieces = [];
  for (let i = 0; i < parts.length; i++) {
    if (parts[i].length > 0) {
      const seg = parts[i];
      pieces.push(seg.includes("'") ? `"${seg}"` : `'${seg}'`);
    }
    if (i < parts.length - 1)
      pieces.push(`'"'`);
  }
  return pieces.length === 1 ? pieces[0] : `concat(${pieces.join(", ")})`;
}
async function domEvaluationScript() {
  const DESTRUCTIVE_VERBS = /(delete|remove|destroy|submit|send|confirm|place\s*order|checkout|pay|purchase|buy|charge|cancel|close\s*account|deactivate|publish|approve|reject|logout|sign\s*out|save|create|update|add|edit|clear|reset|duplicate|clone|enable|disable|toggle|import|export|upload|download|invite|subscribe|unsubscribe|favorite|unfavorite|follow|unfollow|rename|archive|restore)/i;
  const VIEW_VERBS = /(view|show|open|see|preview|browse|navigate|go\s*to|back|next|previous|search|filter|sort|paginate|expand|collapse|select|choose|display|refresh|reload|close|dismiss|cancel\s*close|copy)/i;
  const SAFE_LANDMARK_DEFAULTS = /* @__PURE__ */ new Set(["navigation", "banner", "footer", "contentinfo"]);
  const LIVE_REGION_ROLES = /* @__PURE__ */ new Set(["status", "alert", "log", "marquee"]);
  function computeFramePath(el) {
    let node = el;
    while (node) {
      if (node instanceof ShadowRoot)
        return [];
      node = node.parentNode || node.host;
      if (node === document)
        break;
    }
    return [];
  }
  function hasShadowRootAncestor(el) {
    let node = el;
    while (node) {
      if (node instanceof ShadowRoot)
        return true;
      node = node.parentNode;
    }
    return false;
  }
  function idUnique(id) {
    try {
      return document.querySelectorAll(`#${CSS.escape(id)}`).length === 1;
    } catch {
      return false;
    }
  }
  function testIdUnique(testId) {
    try {
      return document.querySelectorAll(`[data-testid=${CSS.escape(testId)}]`).length === 1;
    } catch {
      return false;
    }
  }
  function xpathLiteral(value) {
    if (!value.includes('"'))
      return `"${value}"`;
    if (!value.includes("'"))
      return `'${value}'`;
    const parts = value.split('"');
    const pieces = [];
    for (let i = 0; i < parts.length; i++) {
      if (parts[i].length > 0) {
        const seg = parts[i];
        pieces.push(seg.includes("'") ? `"${seg}"` : `'${seg}'`);
      }
      if (i < parts.length - 1)
        pieces.push(`'"'`);
    }
    return pieces.length === 1 ? pieces[0] : `concat(${pieces.join(", ")})`;
  }
  function getXPath(el) {
    const testId = el.getAttribute("data-testid");
    if (testId && testIdUnique(testId))
      return `//*[@data-testid=${xpathLiteral(testId)}]`;
    if (el.id && idUnique(el.id))
      return `//*[@id=${xpathLiteral(el.id)}]`;
    const parts = [];
    let node = el;
    while (node && node.nodeType === Node.ELEMENT_NODE) {
      if (node.id && idUnique(node.id)) {
        parts.unshift(`*[@id=${xpathLiteral(node.id)}]`);
        break;
      }
      const parent = node.parentElement;
      const tagName = node.tagName.toLowerCase();
      if (parent) {
        const sameTagSiblings = Array.from(parent.children).filter((s) => s.tagName === node.tagName);
        if (sameTagSiblings.length > 1) {
          const idx = sameTagSiblings.indexOf(node) + 1;
          parts.unshift(`${tagName}[${idx}]`);
        } else {
          parts.unshift(tagName);
        }
      } else {
        parts.unshift(tagName);
      }
      node = parent;
    }
    return "//" + parts.join("/");
  }
  const NAME_FROM_CONTENT_PROHIBITED = /* @__PURE__ */ new Set([
    "combobox",
    "listbox",
    "menu",
    "menubar",
    "tree",
    "treeitem",
    "tablist",
    "tabpanel",
    "dialog",
    "alertdialog",
    "grid",
    "rowgroup",
    "group",
    "region",
    "form"
  ]);
  function getAccessibleName(el, role) {
    const ariaLabel = el.getAttribute("aria-label");
    if (ariaLabel && ariaLabel.trim())
      return ariaLabel.trim();
    const labelledBy = el.getAttribute("aria-labelledby");
    if (labelledBy) {
      const names = labelledBy.split(/\s+/).map((id) => document.getElementById(id)?.textContent?.trim()).filter(Boolean);
      if (names.length)
        return names.join(" ");
    }
    const title = el.getAttribute("title");
    if (title && title.trim())
      return title.trim();
    const placeholder = el.getAttribute("placeholder");
    if (placeholder && placeholder.trim())
      return placeholder.trim();
    const alt = el.getAttribute("alt");
    if (alt && alt.trim())
      return alt.trim();
    if (role && NAME_FROM_CONTENT_PROHIBITED.has(role))
      return "";
    const text = el.textContent?.trim().replace(/\s+/g, " ").slice(0, 60);
    if (text)
      return text;
    if (NAMELESS_INTERACTIVE_ICON_ROLES.has(role)) {
      const iconName = findDescendantIconName(el);
      if (iconName)
        return iconName;
    }
    return "";
  }
  const NAMELESS_INTERACTIVE_ICON_ROLES = /* @__PURE__ */ new Set([
    "button",
    "link",
    "menuitem",
    "tab",
    "switch",
    "checkbox",
    "radio",
    "option"
  ]);
  function findDescendantIconName(el) {
    const descendants = el.querySelectorAll(
      '[data-icon], svg > title, use[href*="#icon-"], use[*|href*="#icon-"]'
    );
    const cap = Math.min(descendants.length, 6);
    for (let i = 0; i < cap; i++) {
      const d = descendants[i];
      const dataIcon = d.getAttribute("data-icon");
      if (dataIcon && dataIcon.trim())
        return dataIcon.trim();
      if (d.tagName.toLowerCase() === "title") {
        const txt = d.textContent?.trim();
        if (txt) return txt;
      }
      const href = d.getAttribute("href") || d.getAttribute("xlink:href");
      if (href) {
        const match = href.match(/#icon-(.+)$/i);
        if (match) return match[1];
      }
    }
    return null;
  }
  const NATIVE_TAG_TO_ROLE = {
    button: "button",
    a: "link",
    select: "combobox",
    textarea: "textbox"
  };
  function getRole(el) {
    const explicit = el.getAttribute("role");
    const tag = el.tagName.toLowerCase();
    if (explicit) {
      const nativeFromTag = NATIVE_TAG_TO_ROLE[tag];
      if (nativeFromTag === explicit)
        return { role: explicit, widgetType: "native" };
      if (tag === "input") {
        const type = (el.getAttribute("type") || "text").toLowerCase();
        if (type === "checkbox" && explicit === "checkbox" || type === "radio" && explicit === "radio" || type === "search" && explicit === "searchbox")
          return { role: explicit, widgetType: "native" };
      }
      return { role: explicit, widgetType: "custom" };
    }
    if (NATIVE_TAG_TO_ROLE[tag])
      return { role: NATIVE_TAG_TO_ROLE[tag], widgetType: "native" };
    if (tag === "input") {
      const type = (el.getAttribute("type") || "text").toLowerCase();
      if (type === "checkbox")
        return { role: "checkbox", widgetType: "native" };
      if (type === "radio")
        return { role: "radio", widgetType: "native" };
      if (type === "search")
        return { role: "searchbox", widgetType: "native" };
      if (type === "submit" || type === "reset" || type === "button")
        return { role: "button", widgetType: "native" };
      return { role: "textbox", widgetType: "native" };
    }
    if (/^h[1-6]$/.test(tag))
      return { role: "heading", widgetType: "native" };
    return { role: tag, widgetType: "unknown" };
  }
  function inheritsRole(el, wantedRole) {
    let node = el;
    while (node) {
      if (node.getAttribute && node.getAttribute("role") === wantedRole)
        return true;
      const tag = node.tagName?.toLowerCase();
      if (wantedRole === "navigation" && tag === "nav")
        return true;
      node = node.parentElement;
    }
    return false;
  }
  function isInsideForm(el) {
    let node = el;
    while (node) {
      const tag = node.tagName?.toLowerCase();
      if (tag === "form" || node.getAttribute?.("role") === "form")
        return true;
      node = node.parentElement;
    }
    return false;
  }
  function nearestLandmark(el) {
    let node = el;
    while (node) {
      const tag = node.tagName?.toLowerCase();
      const role = node.getAttribute?.("role");
      if (tag === "nav" || role === "navigation")
        return "navigation";
      if (tag === "header" || role === "banner")
        return "banner";
      if (tag === "footer" || role === "contentinfo")
        return "contentinfo";
      if (tag === "aside" || role === "complementary")
        return "complementary";
      if (tag === "main" || role === "main")
        return "main";
      if (tag === "form" || role === "form")
        return "form";
      if (role === "dialog" || tag === "dialog" || role === "alertdialog")
        return "dialog";
      node = node.parentElement;
    }
    return null;
  }
  function getMutability(el, role, accessibleName) {
    if (inheritsRole(el, "navigation"))
      return "immutable";
    if (role === "link")
      return "immutable";
    if (role === "heading")
      return "immutable";
    if (role === "searchbox" || role === "tab" || role === "tabpanel")
      return "immutable";
    if (role === "menuitem") {
      if (DESTRUCTIVE_VERBS.test(accessibleName))
        return "mutable";
      return "immutable";
    }
    if (DESTRUCTIVE_VERBS.test(accessibleName))
      return "mutable";
    if (role === "button" && VIEW_VERBS.test(accessibleName))
      return "immutable";
    if (isInsideForm(el) && (role === "textbox" || role === "checkbox" || role === "radio" || role === "combobox" || role === "spinbutton" || role === "slider" || role === "button"))
      return "mutable";
    const landmark = nearestLandmark(el);
    if (landmark && SAFE_LANDMARK_DEFAULTS.has(landmark))
      return "immutable";
    if (el.tagName?.toLowerCase() === "input") {
      const type = (el.getAttribute("type") || "").toLowerCase();
      if (type === "submit" || type === "reset")
        return "mutable";
    }
    return "unknown";
  }
  const INTERACTIVE_SELECTOR = [
    "button",
    "a[href]",
    'input:not([type="hidden"])',
    "select",
    "textarea",
    '[role="button"]',
    '[role="link"]',
    '[role="checkbox"]',
    '[role="radio"]',
    '[role="combobox"]',
    '[role="searchbox"]',
    '[role="tab"]',
    '[role="menuitem"]',
    '[role="switch"]',
    '[role="option"]',
    // Phase B additions — required for blueprintDiff transient tagging and
    // iframe fingerprinting. See design doc §3.3, §3.5.
    '[role="status"]',
    '[role="alert"]',
    '[role="log"]',
    '[role="marquee"]',
    "iframe",
    // Headings — captured for grounding test assertions like
    // `expect(getByRole('heading', { name: 'X' })).toBeVisible()`. Headings
    // remain non-interactive but are routinely cited in UI tests; the
    // blueprint must include them for the citation invariant to ground.
    "h1",
    "h2",
    "h3",
    "h4",
    "h5",
    "h6",
    '[role="heading"]'
  ].join(", ");
  const LANDMARKS = [
    { selector: 'nav, [role="navigation"]', landmark: "navigation", nameHint: "navbar" },
    { selector: "header:not(main header):not(article header):not(section header)", landmark: "banner", nameHint: "header" },
    { selector: 'main, [role="main"]', landmark: "main", nameHint: "main" },
    { selector: 'footer, [role="contentinfo"]', landmark: "footer", nameHint: "footer" },
    { selector: 'aside, [role="complementary"]', landmark: "sidebar", nameHint: "sidebar" },
    { selector: 'dialog, [role="dialog"]', landmark: "dialog", nameHint: "dialog" },
    { selector: '[role="alertdialog"]', landmark: "dialog", nameHint: "alert_dialog" },
    { selector: 'form, [role="form"]', landmark: "form", nameHint: "form" },
    { selector: '[role="search"]', landmark: "search", nameHint: "search" },
    // Class/id heuristics
    { selector: '[class*="modal"]:not(dialog):not([role="dialog"])', landmark: "dialog", nameHint: "modal" },
    { selector: '[class*="sidebar"]:not(aside)', landmark: "sidebar", nameHint: "sidebar" },
    { selector: '[id*="navbar"],[class*="navbar"],[id*="nav-bar"],[class*="nav-bar"]', landmark: "navigation", nameHint: "navbar" },
    { selector: '[class*="toolbar"]', landmark: "toolbar", nameHint: "toolbar" }
  ];
  const ANCESTOR_ID_MAX_DEPTH = 5;
  const HEADING_ANCESTRY_MAX_DEPTH = 10;
  let anchorKeyCounter = 0;
  const nodeToAnchorKey = /* @__PURE__ */ new WeakMap();
  function getAnchorKey(el) {
    let key = nodeToAnchorKey.get(el);
    if (!key) {
      key = `ak${++anchorKeyCounter}`;
      nodeToAnchorKey.set(el, key);
    }
    return key;
  }
  function isDynamicIdClient(id) {
    if (/^react-aria\d+/.test(id)) return true;
    if (/^mui-\d+/.test(id)) return true;
    if (/^(mat|cdk)-[a-z]+-\d+$/.test(id)) return true;
    if (/[-_]\d+$/.test(id)) return true;
    if (/\d{2,}[_-][a-zA-Z]/.test(id)) return true;
    if (/^\d+$/.test(id)) return true;
    if (/\d{4,}$/.test(id)) return true;
    if (/[-_][0-9a-f]{6,}$/i.test(id)) return true;
    const m = id.match(/[-_]([0-9a-f]{3,5})$/i);
    if (m && /[0-9]/.test(m[1])) return true;
    if (/[a-zA-Z][0-9]{3,}$/.test(id)) return true;
    if (id.includes(":")) return true;
    if (/^.+__search_[a-zA-Z0-9]{4,}$/.test(id)) return true;
    return false;
  }
  function getNearestAncestorId(el) {
    let cur = el.parentElement;
    let depth = 0;
    while (cur && depth < ANCESTOR_ID_MAX_DEPTH) {
      const testId = cur.getAttribute("data-testid");
      if (testId) return testId;
      const id = cur.getAttribute("id");
      if (id && !isDynamicIdClient(id)) return id;
      cur = cur.parentElement;
      depth++;
    }
    return null;
  }
  const CONTEXT_TEXT_MAX_ENTRIES = 10;
  const CONTEXT_TEXT_MAX_LEN = 100;
  const ROW_ANCESTOR_MAX_DEPTH = 6;
  const ROW_TAG_NAMES = /* @__PURE__ */ new Set(["TR", "LI"]);
  const ROW_ROLES = /* @__PURE__ */ new Set(["row", "listitem"]);
  function findRowAncestor(el) {
    let cur = el.parentElement;
    let depth = 0;
    while (cur && depth < ROW_ANCESTOR_MAX_DEPTH) {
      if (ROW_TAG_NAMES.has(cur.tagName)) return cur;
      const role = cur.getAttribute("role");
      if (role && ROW_ROLES.has(role)) return cur;
      cur = cur.parentElement;
      depth++;
    }
    return el.parentElement ?? el;
  }
  function getRowContextText(el) {
    const row = findRowAncestor(el);
    const entries = [];
    const walker = document.createTreeWalker(row, NodeFilter.SHOW_TEXT, {
      acceptNode(node) {
        let p = node.parentElement;
        while (p && p !== row) {
          if (p.matches && p.matches(INTERACTIVE_SELECTOR))
            return NodeFilter.FILTER_REJECT;
          p = p.parentElement;
        }
        return NodeFilter.FILTER_ACCEPT;
      }
    });
    let cur;
    while (cur = walker.nextNode()) {
      const text = (cur.textContent ?? "").replace(/\s+/g, " ").trim();
      if (!text) continue;
      if (text.length > CONTEXT_TEXT_MAX_LEN)
        entries.push(text.slice(0, CONTEXT_TEXT_MAX_LEN - 1) + "\u2026");
      else
        entries.push(text);
      if (entries.length >= CONTEXT_TEXT_MAX_ENTRIES) break;
    }
    return entries;
  }
  const BEM_ISH = /^[a-z][a-z-]+(__[a-z-]+)?(--[a-z-]+)?$/;
  const FRAMEWORK_PREFIXES = ["data-radix-", "data-headlessui-", "MuiSelect-", "ant-select-"];
  const TAILWIND_UTILITY = /^(bg|text|p|m|w|h|flex|grid|border|rounded|shadow)-|^(hover|focus|md|lg|sm|xl):/;
  const CSS_MODULE_HASH = /_[a-z0-9]{5,}$/;
  const NUMERIC_INDEX_SUFFIX = /-\d+$/;
  const STATE_TOKENS = /* @__PURE__ */ new Set(["is-active", "is-open", "expanded", "selected", "active", "open"]);
  function isStructuralClass(cls) {
    if (!cls) return false;
    if (FRAMEWORK_PREFIXES.some((p) => cls.startsWith(p))) return true;
    if (STATE_TOKENS.has(cls)) return false;
    if (TAILWIND_UTILITY.test(cls)) return false;
    if (CSS_MODULE_HASH.test(cls)) return false;
    if (NUMERIC_INDEX_SUFFIX.test(cls)) return false;
    return BEM_ISH.test(cls);
  }
  function normalizeClassesClient(className) {
    if (!className) return "";
    const tokens = className.split(/\s+/).filter(Boolean).filter(isStructuralClass);
    return Array.from(new Set(tokens)).sort().join(" ");
  }
  function extractTuple(element) {
    const tag = element.tagName.toLowerCase();
    const role = element.getAttribute("role") ?? "";
    const ariaHasPopup = element.getAttribute("aria-haspopup") ?? "";
    const classPattern = normalizeClassesClient(element.getAttribute("class") ?? "");
    const dataAttrKeys = [];
    for (const attr of Array.from(element.attributes)) {
      if (attr.name.startsWith("data-"))
        dataAttrKeys.push(attr.name);
    }
    dataAttrKeys.sort();
    return { tag, role, ariaHasPopup, classPattern, dataAttrKeys };
  }
  function walkTwoLevels(root) {
    const out = [];
    for (const c of Array.from(root.children)) {
      out.push(c);
      for (const g of Array.from(c.children)) {
        out.push(g);
        if (out.length >= 20) return out;
      }
    }
    return out;
  }
  function detectPortal(element) {
    const controlsId = element.getAttribute("aria-controls");
    if (controlsId) {
      const target = document.getElementById(controlsId);
      if (target && !element.contains(target))
        return target;
    }
    let found = 0;
    const walker = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_ELEMENT);
    let cur;
    while (cur = walker.nextNode()) {
      const desc = cur;
      if (element.contains(desc))
        continue;
      for (const attr of Array.from(desc.attributes)) {
        if (attr.name.startsWith("data-radix-") || attr.name.startsWith("data-headlessui-"))
          return desc;
      }
      if (++found >= 20)
        break;
    }
    return null;
  }
  function canonicalJson(v) {
    if (v === null || typeof v !== "object") return JSON.stringify(v);
    if (Array.isArray(v)) return "[" + v.map(canonicalJson).join(",") + "]";
    const o = v;
    const keys = Object.keys(o).sort();
    return "{" + keys.map((k) => JSON.stringify(k) + ":" + canonicalJson(o[k])).join(",") + "}";
  }
  async function computeElementFingerprint(element) {
    const root = extractTuple(element);
    const descTuples = [];
    const seen = /* @__PURE__ */ new Set();
    for (const d of walkTwoLevels(element)) {
      const t = extractTuple(d);
      const key = canonicalJson(t);
      if (seen.has(key)) continue;
      seen.add(key);
      descTuples.push(t);
      if (descTuples.length >= 3) break;
    }
    const portalEl = detectPortal(element);
    const portal = portalEl ? extractTuple(portalEl) : null;
    const input = { root, descendants: descTuples, portal };
    const bytes = new TextEncoder().encode(canonicalJson(input));
    const buf = await crypto.subtle.digest("SHA-256", bytes);
    const arr = Array.from(new Uint8Array(buf));
    return arr.map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16);
  }
  const SECTIONING_TAG_NAMES = /* @__PURE__ */ new Set(["section", "article", "aside", "nav"]);
  function getSectioningElementAccessibleName(sectioningEl) {
    const labelledBy = sectioningEl.getAttribute("aria-labelledby");
    if (labelledBy) {
      const names = labelledBy.split(/\s+/).map((id) => document.getElementById(id)?.textContent?.trim()).filter(Boolean);
      if (names.length) return names.join(" ");
    }
    const ariaLabel = sectioningEl.getAttribute("aria-label");
    if (ariaLabel && ariaLabel.trim()) return ariaLabel.trim();
    const heading = sectioningEl.querySelector("h1, h2, h3, h4, h5, h6");
    if (heading) return heading.textContent?.trim() ?? null;
    return null;
  }
  function getNearestLandmarkAncestor(el) {
    for (const { selector, landmark, nameHint } of LANDMARKS) {
      const ancestor = el.closest(selector);
      if (ancestor) return { landmark, nameHint, anchorKey: getAnchorKey(ancestor) };
    }
    return null;
  }
  function getNearestSectioningAncestor(el) {
    let cur = el.parentElement;
    while (cur) {
      const tag = cur.tagName.toLowerCase();
      if (SECTIONING_TAG_NAMES.has(tag)) {
        return {
          tagName: tag,
          accessibleName: getSectioningElementAccessibleName(cur),
          anchorKey: getAnchorKey(cur)
        };
      }
      cur = cur.parentElement;
    }
    return null;
  }
  function getHeadingAncestry(el) {
    const outermostFirst = [];
    let cur = el.parentElement;
    let depth = 0;
    while (cur && depth < HEADING_ANCESTRY_MAX_DEPTH) {
      let sib = cur.previousElementSibling;
      while (sib) {
        const tag2 = sib.tagName.toLowerCase();
        const match = tag2.match(/^h([1-6])$/);
        if (match) {
          const level = parseInt(match[1], 10);
          const text = sib.textContent?.trim() ?? "";
          outermostFirst.unshift({ level, text, anchorKey: getAnchorKey(sib) });
          break;
        }
        sib = sib.previousElementSibling;
      }
      const tag = cur.tagName.toLowerCase();
      if (SECTIONING_TAG_NAMES.has(tag)) break;
      cur = cur.parentElement;
      depth++;
    }
    return outermostFirst;
  }
  function getSectionAncestry(el) {
    return {
      landmarkAncestor: getNearestLandmarkAncestor(el),
      sectioningAncestor: getNearestSectioningAncestor(el),
      headingAncestry: getHeadingAncestry(el)
    };
  }
  async function captureElement(el) {
    const { role, widgetType } = getRole(el);
    const accessibleName = getAccessibleName(el, role);
    if (!accessibleName && !LIVE_REGION_ROLES.has(role))
      return null;
    const idAttr = el.getAttribute("id");
    const testIdAttr = el.getAttribute("data-testid");
    const fingerprint = widgetType === "native" ? null : await computeElementFingerprint(el);
    const stableIdValue = idAttr && idUnique(idAttr) && !isDynamicIdClient(idAttr) ? idAttr : null;
    return {
      role,
      accessibleName,
      xpath: getXPath(el),
      mutability: getMutability(el, role, accessibleName),
      widgetType,
      framePath: computeFramePath(el),
      shadowRoot: hasShadowRootAncestor(el),
      stableId: stableIdValue,
      testId: testIdAttr || null,
      fingerprint,
      nearestAncestorId: getNearestAncestorId(el),
      sectionAncestry: getSectionAncestry(el),
      contextText: getRowContextText(el)
    };
  }
  const allInteractive = document.querySelectorAll(INTERACTIVE_SELECTOR);
  const elements = [];
  for (const el of Array.from(allInteractive)) {
    const captured = await captureElement(el);
    if (captured)
      elements.push(captured);
  }
  let hashAccum = 0;
  for (const el of elements) {
    for (let i = 0; i < el.accessibleName.length; i++)
      hashAccum = (hashAccum << 5) - hashAccum + el.accessibleName.charCodeAt(i) | 0;
  }
  const pageHash = `${elements.length}:${hashAccum}`;
  return { elements, pageHash };
}
async function buildPageBlueprint(page) {
  const [pageUrl, evalResult] = await Promise.all([
    page.url(),
    page.evaluate(domEvaluationScript)
  ]);
  const unknownRoles = /* @__PURE__ */ new Set();
  for (const el of evalResult.elements) {
    if (!CANONICAL_ARIA_ROLES.has(el.role))
      unknownRoles.add(el.role);
  }
  if (unknownRoles.size > 0) {
    console.warn(`[dom-analyzer] Unknown ARIA roles encountered (not in canonical WAI-ARIA set): ${[...unknownRoles].join(", ")}.`);
  }
  const grouperInputs = evalResult.elements.map((el, idx) => ({
    id: `el-${idx}`,
    ancestry: el.sectionAncestry
  }));
  const { assignments: sectionNameByElementId, sections: resolvedSections } = (0, import_sectionGrouper.resolveSections)(grouperInputs);
  const sectionNameByRawElement = /* @__PURE__ */ new Map();
  evalResult.elements.forEach((el, idx) => {
    sectionNameByRawElement.set(el, sectionNameByElementId[`el-${idx}`]);
  });
  const elementsBySection = /* @__PURE__ */ new Map();
  for (const el of evalResult.elements) {
    const sectionName = sectionNameByRawElement.get(el) ?? "page";
    if (!elementsBySection.has(sectionName))
      elementsBySection.set(sectionName, []);
    elementsBySection.get(sectionName).push(el);
  }
  const derivedNamesByElement = /* @__PURE__ */ new Map();
  for (const [sectionName, elsInSection] of elementsBySection.entries()) {
    const raws = elsInSection.map((el) => ({
      role: el.role,
      accessibleName: el.accessibleName,
      sectionName
    }));
    const names = applyPositionHintToNamelessElements(raws);
    elsInSection.forEach((el, i) => derivedNamesByElement.set(el, names[i]));
  }
  const collisionInputs = evalResult.elements.map((el) => ({
    initialName: derivedNamesByElement.get(el) ?? deriveLogicalName(el.role, el.accessibleName),
    sectionName: sectionNameByRawElement.get(el) ?? "page",
    testId: el.testId,
    stableId: el.stableId,
    // already-filtered: idUnique + !isDynamicIdClient
    nearestAncestorId: el.nearestAncestorId
  }));
  const finalNames = (0, import_logicalNameResolver.resolveCollisions)(collisionInputs);
  const finalNameByElement = /* @__PURE__ */ new Map();
  evalResult.elements.forEach((el, i) => finalNameByElement.set(el, finalNames[i]));
  const sections = [];
  for (const sectionInfo of resolvedSections) {
    const rawsInSection = elementsBySection.get(sectionInfo.name) ?? [];
    if (rawsInSection.length === 0)
      continue;
    const { singular, repeating } = collapseRepeatingElements(rawsInSection);
    const singularElements = [];
    for (const raw of singular) {
      const logicalName = finalNameByElement.get(raw);
      if (!raw.xpath.startsWith("//")) {
        throw new BlueprintInvariantError(
          `BlueprintElement '${logicalName}' has non-absolute xpath: '${raw.xpath}'. All xpaths must start with '//' (document-rooted descendant-or-self).`
        );
      }
      validateElement({ logicalName, widgetType: raw.widgetType, fingerprint: raw.fingerprint });
      singularElements.push({
        logicalName,
        role: raw.role,
        accessibleName: raw.accessibleName,
        xpath: raw.xpath,
        mutability: raw.mutability,
        widgetType: raw.widgetType,
        framePath: raw.framePath,
        shadowRoot: raw.shadowRoot,
        stableId: raw.stableId,
        testId: raw.testId,
        fingerprint: raw.fingerprint
      });
    }
    const repeatingElements = [];
    for (const rep of repeating) {
      validateElement({ logicalName: rep.logicalName, widgetType: rep.widgetType, fingerprint: rep.fingerprint });
      validateRepeatingElement(rep);
      repeatingElements.push(rep);
    }
    if (singularElements.length === 0 && repeatingElements.length === 0)
      continue;
    sections.push({
      name: sectionInfo.name,
      landmark: sectionInfo.landmark,
      landmarkOther: sectionInfo.landmarkOther,
      elements: singularElements,
      repeatingElements
    });
  }
  return {
    schemaVersion: import_serialization.SCHEMA_VERSION,
    url: (0, import_serialization.normalizeUrl)(pageUrl),
    capturedAt: (0, import_serialization.validateTimestamp)((0, import_serialization.nowTimestamp)()),
    pageHash: evalResult.pageHash,
    sections
  };
}
function collapseRepeatingElements(elements) {
  const singular = [];
  const repeating = [];
  const groups = /* @__PURE__ */ new Map();
  for (const el of elements) {
    const key = `${el.role}::${prefixKey(el.accessibleName)}`;
    const arr = groups.get(key) ?? [];
    arr.push(el);
    groups.set(key, arr);
  }
  for (const group of groups.values()) {
    if (group.length < 2) {
      singular.push(...group);
      continue;
    }
    const analysis = analyseGroup(group);
    if (!analysis) {
      singular.push(...group);
      continue;
    }
    repeating.push(analysis);
  }
  return { singular, repeating };
}
function prefixKey(name) {
  const words = name.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
  return words.slice(0, 2).join("_");
}
function analyseGroup(group) {
  const xpathTemplate = deriveXpathTemplate(group);
  if (xpathTemplate) {
    const variableNameResult = analyseByVariableName(group, xpathTemplate);
    if (variableNameResult)
      return variableNameResult;
  }
  const testIdResult = analyseByVaryingTestId(group);
  if (testIdResult)
    return testIdResult;
  return null;
}
function analyseByVariableName(group, xpathTemplate) {
  const tokenLists = group.map((el) => el.accessibleName.split(/\s+/));
  const tokenLengths = new Set(tokenLists.map((t) => t.length));
  if (tokenLengths.size !== 1)
    return null;
  const length = tokenLists[0].length;
  const templateTokens = [];
  const variableIndices = [];
  for (let i = 0; i < length; i++) {
    const first = tokenLists[0][i];
    const allSame = tokenLists.every((t) => t[i] === first);
    if (allSame) {
      templateTokens.push(first);
    } else {
      templateTokens.push(`__VAR__${variableIndices.length}__`);
      variableIndices.push(i);
    }
  }
  if (variableIndices.length === 0)
    return null;
  if (variableIndices.length > 2)
    return null;
  const paramNames = variableIndices.map((_, i) => `param${i}`);
  const firstVarIdx = variableIndices[0];
  const contextBefore = firstVarIdx > 0 ? templateTokens[firstVarIdx - 1] : "";
  const contextAfter = firstVarIdx < length - 1 ? templateTokens[firstVarIdx + 1] : "";
  const firstParamName = guessParamName(contextBefore, contextAfter) || "id";
  paramNames[0] = firstParamName;
  if (paramNames.length > 1)
    paramNames[1] = "value";
  const accessibleNameTemplate = templateTokens.map((t) => {
    const m = t.match(/^__VAR__(\d+)__$/);
    return m ? `{${paramNames[parseInt(m[1], 10)]}}` : t;
  }).join(" ");
  const parameters = [...paramNames, "row"];
  const indexParameter = "row";
  const items = group.map((el, itemIdx) => {
    const tokens = tokenLists[itemIdx];
    const params = {};
    variableIndices.forEach((tokenIdx, paramIdx) => {
      params[paramNames[paramIdx]] = tokens[tokenIdx];
    });
    params["row"] = String(itemIdx + 1);
    return {
      parameters: params,
      ...el.contextText.length > 0 ? { contextText: el.contextText } : {}
    };
  });
  const rep = group[0];
  const nameForLogical = templateTokens.map((t) => t.match(/^__VAR__\d+__$/) ? "" : t).join(" ").trim();
  const baseLogicalName = deriveLogicalName(rep.role, nameForLogical);
  return {
    logicalName: baseLogicalName,
    role: rep.role,
    accessibleNameTemplate,
    parameters,
    xpathPattern: xpathTemplate,
    indexParameter,
    items,
    mutability: rep.mutability,
    widgetType: rep.widgetType,
    framePath: rep.framePath,
    shadowRoot: rep.shadowRoot,
    stableId: null,
    testId: null,
    // Repeating-group shortcut: members share widgetType by construction
    // (same role + similar structure), so group[0]'s fingerprint is representative.
    fingerprint: rep.fingerprint
  };
}
function analyseByVaryingTestId(group) {
  if (group.some((el) => !el.testId))
    return null;
  const firstName = group[0].accessibleName;
  if (!group.every((el) => el.accessibleName === firstName))
    return null;
  const testIds = group.map((el) => el.testId);
  const commonPrefix = longestCommonPrefix(testIds);
  const commonSuffix = longestCommonSuffix(testIds);
  const slugs = [];
  for (const tid of testIds) {
    const middle = tid.slice(commonPrefix.length, tid.length - commonSuffix.length);
    if (!middle)
      return null;
    slugs.push(middle);
  }
  if (new Set(slugs).size !== slugs.length)
    return null;
  const firstToken = firstName.toLowerCase().split(/\s+/)[0];
  const slugParamName = guessParamName(firstToken, "") || "itemSlug";
  const testIdPattern = `${commonPrefix}{${slugParamName}}${commonSuffix}`;
  const xpathBySlug = `//*[@data-testid=${xpathLiteralNode(testIdPattern)}]`;
  const parameters = [slugParamName];
  const indexParameter = slugParamName;
  const accessibleNameTemplate = firstName;
  const items = group.map((el, itemIdx) => ({
    parameters: {
      [slugParamName]: slugs[itemIdx]
    },
    ...el.contextText.length > 0 ? { contextText: el.contextText } : {}
  }));
  const rep = group[0];
  const baseLogicalName = deriveLogicalName(rep.role, firstName);
  return {
    logicalName: baseLogicalName,
    role: rep.role,
    accessibleNameTemplate,
    parameters,
    xpathPattern: xpathBySlug,
    indexParameter,
    items,
    mutability: rep.mutability,
    widgetType: rep.widgetType,
    framePath: rep.framePath,
    shadowRoot: rep.shadowRoot,
    stableId: null,
    testId: null,
    // Repeating-group shortcut: members share widgetType by construction
    // (same role + similar structure), so group[0]'s fingerprint is representative.
    fingerprint: rep.fingerprint
  };
}
function longestCommonPrefix(strs) {
  if (strs.length === 0)
    return "";
  let prefix = strs[0];
  for (const s of strs.slice(1)) {
    let i = 0;
    while (i < prefix.length && i < s.length && prefix[i] === s[i])
      i++;
    prefix = prefix.slice(0, i);
    if (!prefix)
      break;
  }
  return prefix;
}
function longestCommonSuffix(strs) {
  if (strs.length === 0)
    return "";
  let suffix = strs[0];
  for (const s of strs.slice(1)) {
    let i = 0;
    while (i < suffix.length && i < s.length && suffix[suffix.length - 1 - i] === s[s.length - 1 - i])
      i++;
    suffix = suffix.slice(suffix.length - i);
    if (!suffix)
      break;
  }
  return suffix;
}
function deriveXpathTemplate(group) {
  const xpaths = group.map((el) => el.xpath);
  const splits = xpaths.map((xp) => xp.split("/"));
  const lens = new Set(splits.map((s) => s.length));
  if (lens.size !== 1)
    return null;
  const length = splits[0].length;
  const template = [];
  let varyingSegmentCount = 0;
  for (let i = 0; i < length; i++) {
    const values = splits.map((s) => s[i]);
    const unique = new Set(values);
    if (unique.size === 1) {
      template.push(values[0]);
    } else {
      const match = values[0].match(/^([a-z0-9]+)\[(\d+)\](.*)$/);
      if (!match)
        return null;
      const tagPrefix = match[1];
      const suffix = match[3];
      for (const v of values) {
        const m = v.match(/^([a-z0-9]+)\[(\d+)\](.*)$/);
        if (!m || m[1] !== tagPrefix || m[3] !== suffix)
          return null;
      }
      template.push(`${tagPrefix}[{row}]${suffix}`);
      varyingSegmentCount++;
    }
  }
  if (varyingSegmentCount === 0 || varyingSegmentCount > 1)
    return null;
  return template.join("/");
}
function guessParamName(before, after) {
  const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
  const tokens = [normalize(before), normalize(after)].filter(Boolean);
  const NOUNS = {
    order: "orderId",
    orders: "orderId",
    product: "productId",
    products: "productId",
    user: "userId",
    users: "userId",
    tab: "tabName",
    row: "rowIndex",
    item: "itemId",
    items: "itemId"
  };
  for (const tok of tokens) {
    if (NOUNS[tok])
      return NOUNS[tok];
  }
  return null;
}
const TEMPLATE_TOKEN_RE = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
function extractTokens(template) {
  const tokens = /* @__PURE__ */ new Set();
  let match;
  const re = new RegExp(TEMPLATE_TOKEN_RE);
  while ((match = re.exec(template)) !== null)
    tokens.add(match[1]);
  return tokens;
}
function setsEqual(a, b) {
  if (a.size !== b.size)
    return false;
  for (const v of a) {
    if (!b.has(v))
      return false;
  }
  return true;
}
function validateElement(el) {
  if ((el.widgetType === "custom" || el.widgetType === "unknown") && el.fingerprint === null) {
    throw new Error(
      `fingerprint required for widgetType='${el.widgetType}' on element '${el.logicalName}'`
    );
  }
}
function validateRepeatingElement(rep) {
  if (!rep.xpathPattern.startsWith("//")) {
    throw new BlueprintInvariantError(
      `RepeatingBlueprintElement '${rep.logicalName}' has non-absolute xpathPattern: '${rep.xpathPattern}'. All xpaths must start with '//' (document-rooted descendant-or-self).`
    );
  }
  const declared = new Set(rep.parameters);
  const tokensFromName = extractTokens(rep.accessibleNameTemplate);
  const tokensFromXpath = extractTokens(rep.xpathPattern);
  const allTokens = /* @__PURE__ */ new Set([...tokensFromName, ...tokensFromXpath]);
  if (!setsEqual(allTokens, declared)) {
    const missing = [...declared].filter((p) => !allTokens.has(p));
    const extra = [...allTokens].filter((t) => !declared.has(t));
    throw new Error(
      `Repeating element "${rep.logicalName}" has template/parameters mismatch. Declared parameters: [${rep.parameters.join(", ")}]. Tokens in templates: [${[...allTokens].join(", ")}]. ` + (missing.length ? `Missing in templates: [${missing.join(", ")}]. ` : "") + (extra.length ? `Extra tokens in templates: [${extra.join(", ")}].` : "")
    );
  }
  if (!declared.has(rep.indexParameter))
    throw new Error(`Repeating element "${rep.logicalName}" indexParameter "${rep.indexParameter}" not in parameters [${rep.parameters.join(", ")}]`);
  for (let i = 0; i < rep.items.length; i++) {
    const itemKeys = new Set(Object.keys(rep.items[i].parameters));
    if (!setsEqual(itemKeys, declared)) {
      const missing = [...declared].filter((p) => !itemKeys.has(p));
      const extra = [...itemKeys].filter((k) => !declared.has(k));
      throw new Error(
        `Repeating element "${rep.logicalName}" item[${i}] has parameter-key mismatch. Declared: [${rep.parameters.join(", ")}]. Item keys: [${[...itemKeys].join(", ")}]. ` + (missing.length ? `Missing: [${missing.join(", ")}]. ` : "") + (extra.length ? `Extra: [${extra.join(", ")}].` : "")
      );
    }
  }
}
function resolveTemplate(template, params) {
  return template.replace(TEMPLATE_TOKEN_RE, (_, name) => {
    if (!(name in params))
      throw new Error(`resolveTemplate: no value for {${name}} in params keys [${Object.keys(params).join(", ")}]`);
    return params[name];
  });
}
function resolveXpath(rep, params) {
  return resolveTemplate(rep.xpathPattern, params);
}
function resolveAccessibleName(rep, params) {
  return resolveTemplate(rep.accessibleNameTemplate, params);
}
function buildMap(blueprint) {
  const map = {};
  for (const section of blueprint.sections) {
    for (const el of section.elements)
      map[el.logicalName] = el.xpath;
    for (const rep of section.repeatingElements)
      map[rep.logicalName] = rep.xpathPattern;
  }
  return map;
}
function buildOutline(blueprint) {
  const lines = [];
  for (const section of blueprint.sections) {
    const landmarkLabel = section.landmark === "other" && section.landmarkOther ? section.landmarkOther : section.landmark;
    lines.push(`${section.name} (role: ${landmarkLabel})`);
    const total = section.elements.length + section.repeatingElements.length;
    let idx = 0;
    for (const el of section.elements) {
      idx++;
      const branch = idx === total ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
      lines.push(`  ${branch} ${el.logicalName} [${el.mutability}]`);
    }
    for (const rep of section.repeatingElements) {
      idx++;
      const branch = idx === total ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
      lines.push(`  ${branch} ${rep.logicalName} [repeating \xD7 ${rep.items.length}]`);
      lines.push(`        template: "${rep.accessibleNameTemplate}"`);
    }
    lines.push("");
  }
  return lines.join("\n").trimEnd();
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  BlueprintInvariantError,
  applyPositionHintToNamelessElements,
  buildMap,
  buildOutline,
  buildPageBlueprint,
  deriveLogicalName,
  resolveAccessibleName,
  resolveTemplate,
  resolveXpath,
  validateElement,
  validateRepeatingElement,
  xpathLiteralNode
});
