"use strict";
var import_blueprint = require("./blueprint");
var import_serialization = require("./serialization");
const cases = [];
function test(name, run) {
  cases.push({ name, run });
}
function assertEqual(actual, expected, msg) {
  const a = JSON.stringify(actual);
  const e = JSON.stringify(expected);
  if (a !== e)
    throw new Error(`${msg ?? "assertEqual"} \u2014 expected ${e}, got ${a}`);
}
function assertThrows(fn, substring) {
  let threw = false;
  let message = "";
  try {
    fn();
  } catch (e) {
    threw = true;
    message = e.message;
  }
  if (!threw)
    throw new Error("expected function to throw");
  if (substring && !message.includes(substring))
    throw new Error(`thrown message "${message}" does not include "${substring}"`);
}
test("deriveLogicalName: button with simple name", () => {
  assertEqual((0, import_blueprint.deriveLogicalName)("button", "Add Product"), "add_product_btn");
});
test("deriveLogicalName: link with preposition", () => {
  assertEqual((0, import_blueprint.deriveLogicalName)("link", "Navigate to Orders page"), "navigate_to_orders_page_link");
});
test("deriveLogicalName: textbox maps to _input suffix", () => {
  assertEqual((0, import_blueprint.deriveLogicalName)("textbox", "Search"), "search_input");
});
test("deriveLogicalName: strips special chars", () => {
  assertEqual((0, import_blueprint.deriveLogicalName)("button", "Add & Remove!!!"), "add_remove_btn");
});
test("deriveLogicalName: trailing role-suffix token stripped", () => {
  assertEqual((0, import_blueprint.deriveLogicalName)("combobox", "Select Yes No"), "yes_no_select");
});
test("deriveLogicalName: leading role-suffix token stripped", () => {
  assertEqual((0, import_blueprint.deriveLogicalName)("combobox", "Select Country"), "country_select");
});
test("deriveLogicalName: mid-position role-suffix preserved", () => {
  assertEqual((0, import_blueprint.deriveLogicalName)("combobox", "Next: Select Country"), "next_select_country_select");
});
test("deriveLogicalName: base-equals-suffix returns empty string", () => {
  assertEqual((0, import_blueprint.deriveLogicalName)("combobox", "Select"), "");
});
test("deriveLogicalName: empty accessibleName returns empty string", () => {
  assertEqual((0, import_blueprint.deriveLogicalName)("combobox", ""), "");
});
test("deriveLogicalName: button + accessibleName unchanged by dedup", () => {
  assertEqual((0, import_blueprint.deriveLogicalName)("button", "Save Changes"), "save_changes_btn");
});
test("deriveLogicalName: CJK accessibleName preserved", () => {
  assertEqual((0, import_blueprint.deriveLogicalName)("link", "\u8CC7\u7523\u7BA1\u7406"), "\u8CC7\u7523\u7BA1\u7406_link");
  assertEqual((0, import_blueprint.deriveLogicalName)("button", "\u4E00\u62EC\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9"), "\u4E00\u62EC\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9_btn");
});
test("deriveLogicalName: Cyrillic lowercased and preserved", () => {
  assertEqual((0, import_blueprint.deriveLogicalName)("button", "\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C"), "\u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C_btn");
});
test("deriveLogicalName: mixed CJK + ASCII digits", () => {
  assertEqual((0, import_blueprint.deriveLogicalName)("button", "\u901A\u77E5 (1063)"), "\u901A\u77E5_1063_btn");
});
test("deriveLogicalName: accented Latin preserved", () => {
  assertEqual((0, import_blueprint.deriveLogicalName)("link", "r\xE9sum\xE9 caf\xE9"), "r\xE9sum\xE9_caf\xE9_link");
});
test("applyPositionHintToNamelessElements: CJK-named elements no longer fall back to role_N", () => {
  const raws = [
    { role: "link", accessibleName: "\u8CC7\u7523\u60C5\u5831", sectionName: "sidebar" },
    { role: "link", accessibleName: "\u4E00\u62EC\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9", sectionName: "sidebar" }
  ];
  const names = (0, import_blueprint.applyPositionHintToNamelessElements)(raws);
  assertEqual(names, ["\u8CC7\u7523\u60C5\u5831_link", "\u4E00\u62EC\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9_link"]);
});
test("applyPositionHintToNamelessElements: single nameless same-role element gets role_1", () => {
  const raws = [
    { role: "combobox", accessibleName: "", sectionName: "main" }
  ];
  const names = (0, import_blueprint.applyPositionHintToNamelessElements)(raws);
  assertEqual(names, ["combobox_1"]);
});
test("applyPositionHintToNamelessElements: three nameless same-role same-section \u2192 combobox_1/2/3", () => {
  const raws = [
    { role: "combobox", accessibleName: "", sectionName: "main" },
    { role: "combobox", accessibleName: "", sectionName: "main" },
    { role: "combobox", accessibleName: "", sectionName: "main" }
  ];
  const names = (0, import_blueprint.applyPositionHintToNamelessElements)(raws);
  assertEqual(names, ["combobox_1", "combobox_2", "combobox_3"]);
});
test("applyPositionHintToNamelessElements: same role across different sections numbered independently", () => {
  const raws = [
    { role: "combobox", accessibleName: "", sectionName: "header" },
    { role: "combobox", accessibleName: "", sectionName: "main" }
  ];
  const names = (0, import_blueprint.applyPositionHintToNamelessElements)(raws);
  assertEqual(names, ["combobox_1", "combobox_1"]);
});
test("applyPositionHintToNamelessElements: mixed named + nameless; nameless numbered ignoring named", () => {
  const raws = [
    { role: "combobox", accessibleName: "Country", sectionName: "main" },
    { role: "combobox", accessibleName: "", sectionName: "main" },
    { role: "combobox", accessibleName: "", sectionName: "main" }
  ];
  const names = (0, import_blueprint.applyPositionHintToNamelessElements)(raws);
  assertEqual(names[0], "country_select");
  assertEqual(names[1], "combobox_1");
  assertEqual(names[2], "combobox_2");
});
test("resolveTemplate: single placeholder", () => {
  assertEqual(
    (0, import_blueprint.resolveTemplate)("View details for order {orderId}", { orderId: "12" }),
    "View details for order 12"
  );
});
test("resolveTemplate: multiple placeholders", () => {
  assertEqual(
    (0, import_blueprint.resolveTemplate)("{greeting} {name}", { greeting: "Hello", name: "World" }),
    "Hello World"
  );
});
test("resolveTemplate: throws on missing parameter", () => {
  assertThrows(() => (0, import_blueprint.resolveTemplate)("{foo} {bar}", { foo: "1" }), "no value for {bar}");
});
test("resolveXpath / resolveAccessibleName on a RepeatingBlueprintElement", () => {
  const rep = {
    logicalName: "view_details_for_order_btn",
    role: "button",
    accessibleNameTemplate: "View details for order {orderId}",
    parameters: ["orderId", "row"],
    xpathPattern: '//*[@id="root"]/div/div[3]/div/div/div[{row}]/button',
    indexParameter: "row",
    items: [
      { parameters: { orderId: "12", row: "1" } },
      { parameters: { orderId: "11", row: "2" } }
    ],
    mutability: "immutable",
    widgetType: "native",
    framePath: [],
    shadowRoot: false,
    stableId: null,
    testId: null,
    fingerprint: null
  };
  assertEqual((0, import_blueprint.resolveAccessibleName)(rep, rep.items[0].parameters), "View details for order 12");
  assertEqual(
    (0, import_blueprint.resolveXpath)(rep, rep.items[1].parameters),
    '//*[@id="root"]/div/div[3]/div/div/div[2]/button'
  );
});
function makeValidRepeating() {
  return {
    logicalName: "view_details_for_order_btn",
    role: "button",
    accessibleNameTemplate: "View details for order {orderId}",
    parameters: ["orderId", "row"],
    xpathPattern: '//*[@id="root"]/div[{row}]/button',
    indexParameter: "row",
    items: [
      { parameters: { orderId: "12", row: "1" } },
      { parameters: { orderId: "11", row: "2" } }
    ],
    mutability: "immutable",
    widgetType: "native",
    framePath: [],
    shadowRoot: false,
    stableId: null,
    testId: null,
    fingerprint: null
  };
}
test("validateRepeatingElement: happy path", () => {
  const rep = makeValidRepeating();
  (0, import_blueprint.validateRepeatingElement)(rep);
});
test("validateRepeatingElement: template token typo is rejected", () => {
  const rep = makeValidRepeating();
  rep.accessibleNameTemplate = "View details for order {orderID}";
  assertThrows(() => (0, import_blueprint.validateRepeatingElement)(rep), "template/parameters mismatch");
});
test("validateRepeatingElement: indexParameter not in parameters is rejected", () => {
  const rep = makeValidRepeating();
  rep.indexParameter = "notAParameter";
  assertThrows(() => (0, import_blueprint.validateRepeatingElement)(rep), "indexParameter");
});
test("validateRepeatingElement: missing token in xpath is rejected", () => {
  const rep = makeValidRepeating();
  rep.xpathPattern = '//*[@id="root"]/div/button';
  assertThrows(() => (0, import_blueprint.validateRepeatingElement)(rep), "template/parameters mismatch");
});
test("validateRepeatingElement: item with extra key is rejected", () => {
  const rep = makeValidRepeating();
  rep.items[0].parameters["extra"] = "x";
  assertThrows(() => (0, import_blueprint.validateRepeatingElement)(rep), "parameter-key mismatch");
});
test("validateRepeatingElement: item missing a key is rejected", () => {
  const rep = makeValidRepeating();
  delete rep.items[0].parameters["orderId"];
  assertThrows(() => (0, import_blueprint.validateRepeatingElement)(rep), "parameter-key mismatch");
});
function makeBlueprint() {
  return {
    schemaVersion: import_serialization.SCHEMA_VERSION,
    url: "http://localhost:5173/orders",
    capturedAt: "2026-04-29T14:22:00.000Z",
    pageHash: "12:123",
    sections: [
      {
        name: "main_navigation",
        landmark: "navigation",
        elements: [
          {
            logicalName: "navigate_to_orders_page_link",
            role: "link",
            accessibleName: "Navigate to Orders page",
            xpath: '//*[@id="root"]/div/header/nav/a[2]',
            mutability: "immutable",
            widgetType: "native",
            framePath: [],
            shadowRoot: false,
            stableId: null,
            testId: null,
            fingerprint: null
          }
        ],
        repeatingElements: []
      },
      {
        name: "page",
        landmark: "region",
        elements: [
          {
            logicalName: "add_order_btn",
            role: "button",
            accessibleName: "Add Order",
            xpath: '//*[@id="root"]/div/div[2]/button',
            mutability: "mutable",
            widgetType: "native",
            framePath: [],
            shadowRoot: false,
            stableId: null,
            testId: null,
            fingerprint: null
          }
        ],
        repeatingElements: [makeValidRepeating()]
      }
    ]
  };
}
test("buildMap: flat logicalName \u2192 xpath", () => {
  const bp = makeBlueprint();
  const map = (0, import_blueprint.buildMap)(bp);
  assertEqual(Object.keys(map).sort(), [
    "add_order_btn",
    "navigate_to_orders_page_link",
    "view_details_for_order_btn"
  ]);
  assertEqual(map.add_order_btn, '//*[@id="root"]/div/div[2]/button');
  assertEqual(map.view_details_for_order_btn, '//*[@id="root"]/div[{row}]/button');
});
test("buildOutline: includes sections, singular, and repeating", () => {
  const bp = makeBlueprint();
  const outline = (0, import_blueprint.buildOutline)(bp);
  if (!outline.includes("main_navigation (role: navigation)"))
    throw new Error(`outline missing main_navigation section: ${outline}`);
  if (!outline.includes("navigate_to_orders_page_link [immutable]"))
    throw new Error(`outline missing singular element: ${outline}`);
  if (!outline.includes("add_order_btn [mutable]"))
    throw new Error(`outline missing mutable element: ${outline}`);
  if (!outline.includes("view_details_for_order_btn [repeating \xD7 2]"))
    throw new Error(`outline missing repeating element: ${outline}`);
});
test("normalizeUrl: strips trailing slash", () => {
  assertEqual((0, import_serialization.normalizeUrl)("http://example.com/path/"), "http://example.com/path");
});
test("normalizeUrl: keeps root slash", () => {
  assertEqual((0, import_serialization.normalizeUrl)("http://example.com/"), "http://example.com/");
});
test("normalizeUrl: strips fragment", () => {
  assertEqual((0, import_serialization.normalizeUrl)("http://example.com/path#frag"), "http://example.com/path");
});
test("normalizeUrl: strips default port", () => {
  assertEqual((0, import_serialization.normalizeUrl)("http://example.com:80/path"), "http://example.com/path");
  assertEqual((0, import_serialization.normalizeUrl)("https://example.com:443/path"), "https://example.com/path");
});
test("normalizeUrl: sorts query params", () => {
  assertEqual((0, import_serialization.normalizeUrl)("http://example.com/?b=2&a=1"), "http://example.com/?a=1&b=2");
});
test("normalizeUrl: uppercases percent encoding", () => {
  assertEqual((0, import_serialization.normalizeUrl)("http://example.com/path%2fencoded"), "http://example.com/path%2Fencoded");
});
test("normalizeUrl: all variants collapse to same key", () => {
  const a = (0, import_serialization.normalizeUrl)("http://example.com:80/path/?b=2&a=1#frag");
  const b = (0, import_serialization.normalizeUrl)("http://example.com/path?a=1&b=2");
  assertEqual(a, b);
});
test("validateTimestamp: accepts canonical ISO-8601 UTC", () => {
  const ts = (0, import_serialization.validateTimestamp)("2026-04-29T14:22:00.000Z");
  assertEqual(ts, "2026-04-29T14:22:00.000Z");
});
test("validateTimestamp: rejects non-ISO-8601", () => {
  assertThrows(() => (0, import_serialization.validateTimestamp)("2026-04-29 14:22:00"), "canonical ISO-8601");
});
test("validateTimestamp: rejects garbage", () => {
  assertThrows(() => (0, import_serialization.validateTimestamp)("not a date"), "Invalid ISO-8601");
});
test("nowTimestamp: round-trips through validateTimestamp", () => {
  const ts = (0, import_serialization.nowTimestamp)();
  (0, import_serialization.validateTimestamp)(ts);
});
test("assertSchemaVersion: accepts current version", () => {
  (0, import_serialization.assertSchemaVersion)(import_serialization.SCHEMA_VERSION);
});
test("assertSchemaVersion: rejects unknown version", () => {
  assertThrows(() => (0, import_serialization.assertSchemaVersion)(import_serialization.SCHEMA_VERSION + 1), "Unsupported schemaVersion");
});
test("validateElement: native widget with null fingerprint is valid", () => {
  (0, import_blueprint.validateElement)({ logicalName: "save_btn", widgetType: "native", fingerprint: null });
});
test("validateElement: widgetType=custom requires non-null fingerprint", () => {
  assertThrows(
    () => (0, import_blueprint.validateElement)({ logicalName: "date_picker", widgetType: "custom", fingerprint: null }),
    "fingerprint required"
  );
});
test("validateElement: widgetType=custom with fingerprint passes", () => {
  (0, import_blueprint.validateElement)({ logicalName: "date_picker", widgetType: "custom", fingerprint: "abcdef0123456789" });
});
test("BlueprintInvariantError: non-absolute xpath throws with diagnostic", () => {
  const err = new import_blueprint.BlueprintInvariantError(
    `BlueprintElement 'test_btn' has non-absolute xpath: '/foo/bar'. All xpaths must start with '//' (document-rooted descendant-or-self).`
  );
  assertEqual(err.name, "BlueprintInvariantError");
  if (!err.message.includes("non-absolute xpath"))
    throw new Error("error message missing key phrase");
  if (!err.message.includes("test_btn"))
    throw new Error("error message missing element name");
});
test("xpathLiteralNode: simple ASCII value uses double-quote literal", () => {
  assertEqual((0, import_blueprint.xpathLiteralNode)("foo-bar"), '"foo-bar"');
});
test("xpathLiteralNode: value with double-quote uses single-quote literal", () => {
  assertEqual((0, import_blueprint.xpathLiteralNode)('has "quotes" inside'), `'has "quotes" inside'`);
});
test("xpathLiteralNode: value with single-quote uses double-quote literal", () => {
  assertEqual((0, import_blueprint.xpathLiteralNode)("it's mine"), `"it's mine"`);
});
test("xpathLiteralNode: value with both quotes uses concat()", () => {
  const result = (0, import_blueprint.xpathLiteralNode)(`it"s 'wild'`);
  if (!result.startsWith("concat("))
    throw new Error(`expected concat(...) form, got: ${result}`);
  if (!result.includes(`'"'`))
    throw new Error(`expected literal-double-quote segment, got: ${result}`);
});
test("xpathLiteralNode: empty string", () => {
  assertEqual((0, import_blueprint.xpathLiteralNode)(""), '""');
});
test("xpathLiteralNode: backslash passes through (XPath has no backslash escape)", () => {
  assertEqual((0, import_blueprint.xpathLiteralNode)("path\\with\\slashes"), '"path\\with\\slashes"');
});
test("xpathLiteralNode: template placeholders (curly-brace tokens) preserved", () => {
  assertEqual((0, import_blueprint.xpathLiteralNode)("edit-{itemSlug}-btn"), '"edit-{itemSlug}-btn"');
});
test("xpathLiteralNode: value containing only double-quotes takes single-quote branch", () => {
  assertEqual((0, import_blueprint.xpathLiteralNode)('"foo"'), `'"foo"'`);
});
test("xpathLiteralNode: value with both quotes \u2014 empty segments filtered", () => {
  const result = (0, import_blueprint.xpathLiteralNode)(`"'`);
  if (!result.startsWith("concat("))
    throw new Error(`expected concat() form, got: ${result}`);
  if (result.includes(`''`))
    throw new Error(`concat should not contain empty '' literals, got: ${result}`);
});
let passed = 0;
let failed = 0;
const failures = [];
for (const c of cases) {
  try {
    c.run();
    passed++;
  } catch (e) {
    failed++;
    failures.push({ name: c.name, error: e.message });
  }
}
console.log(`${passed}/${cases.length} passed${failed ? `, ${failed} failed` : ""}`);
for (const f of failures)
  console.error(`  \u2717 ${f.name}: ${f.error}`);
if (failed > 0) {
  process.exit(1);
}
