///|
/// Locate nodes with CSS selector and serialize as BiDi node values.
fn BidiProtocol::locate_nodes_css(
  self : BidiProtocol,
  ctx_id : String,
  selector : String,
  start_node_shared_ids : Array[String],
  max_node_count : Int,
  serialization_options_json : String,
) -> Json {
  if start_node_shared_ids.length() == 0 {
    match self.manager.get_session(ctx_id) {
      Some(session) => {
        let doc = session.get_tree().get_document()
        match session.get_tree().query_selector_all(doc, selector) {
          Ok(nodes) => if nodes.length() == 0 { return Json::array([]) }
          Err(_) => ()
        }
      }
      None => ()
    }
  }

  let selector_literal = "\"" + escape_js_string(selector) + "\""
  let start_nodes_literal = js_string_array_literal(start_node_shared_ids)
  let max_count_literal = max_node_count.to_string()
  let expression = "(() => {\n" +
    "  const __selector = String(" +
    selector_literal +
    " || '').trim();\n" +
    "  const __ids = " +
    start_nodes_literal +
    ";\n" +
    "  const __max = " +
    max_count_literal +
    ";\n" +
    locate_nodes_js_shared_roots_from_ids() +
    locate_nodes_js_to_array() +
    locate_nodes_js_collect_element_children() +
    (
      #|  const __attr = (__node, __name) => {
      #|    let __v = null;
      #|    try {
      #|      if (__node && typeof __node.getAttribute === 'function') {
      #|        __v = __node.getAttribute(__name);
      #|      }
      #|    } catch (_e) {}
      #|    if ((__v === null || __v === undefined) && __node && __node._attrs) {
      #|      __v = __node._attrs[__name];
      #|    }
      #|    if (__v === null || __v === undefined) return null;
      #|    return String(__v);
      #|  };
      #|  const __normalizeSelector = (__sel) =>
      #|    String(__sel || '').replace(
      #|      /\[\s*([a-zA-Z0-9_\-:]+)\s*=\s*'([^']*)'\s*\]/g,
      #|      '[$1="$2"]',
      #|    );
      #|  const __selectorParts = __normalizeSelector(__selector)
      #|    .split(/\s+/)
      #|    .filter(Boolean);
      #|  const __matchesSimple = (__node, __simpleRaw) => {
      #|    if (!__node || __node.nodeType !== 1) return false;
      #|    let __simple = String(__simpleRaw || '').trim();
      #|    if (__simple === '') return false;
      #|    if (__simple === '*') return true;
      #|    let __attrName = '';
      #|    let __attrValue = null;
      #|    let __attrContains = false;
      #|    const __attrMatch = __simple.match(
      #|      /^(.*)\[([a-zA-Z0-9_\-:]+)(\*)?="([^"]*)"\]$/,
      #|    );
      #|    if (__attrMatch) {
      #|      __simple = String(__attrMatch[1] || '').trim();
      #|      __attrName = String(__attrMatch[2] || '');
      #|      __attrContains = !!__attrMatch[3];
      #|      __attrValue = String(__attrMatch[4] || '');
      #|    }
      #|    let __tag = __simple;
      #|    let __id = '';
      #|    let __classes = [];
      #|    const __hashIdx = __tag.indexOf('#');
      #|    if (__hashIdx >= 0) {
      #|      __id = __tag.slice(__hashIdx + 1);
      #|      __tag = __tag.slice(0, __hashIdx);
      #|    }
      #|    if (__tag.indexOf('.') >= 0) {
      #|      const __parts = __tag.split('.');
      #|      __tag = __parts.shift() || '';
      #|      __classes = __parts.filter(Boolean);
      #|    } else if (__tag === '' && __simple.startsWith('.')) {
      #|      __classes = __simple.slice(1).split('.').filter(Boolean);
      #|    }
      #|    if (
      #|      __tag !== '' &&
      #|      String(__node.localName || __node.tagName || '').toLowerCase() !==
      #|        __tag.toLowerCase()
      #|    ) {
      #|      return false;
      #|    }
      #|    if (__id !== '') {
      #|      const __nodeId = __attr(__node, 'id');
      #|      if (__nodeId !== __id) return false;
      #|    }
      #|    for (const __cls of __classes) {
      #|      const __nodeClass = String(__attr(__node, 'class') || '');
      #|      const __tokens = __nodeClass.split(/\s+/).filter(Boolean);
      #|      if (__tokens.indexOf(__cls) === -1) return false;
      #|    }
      #|    if (__attrName !== '') {
      #|      const __v = __attr(__node, __attrName);
      #|      if (__attrValue === null) {
      #|        if (__v === null) return false;
      #|      } else if (__attrContains) {
      #|        if (__v === null || __v.indexOf(__attrValue) === -1) return false;
      #|      } else if (__v !== __attrValue) {
      #|        return false;
      #|      }
      #|    }
      #|    return true;
      #|  };
      #|  const __matchesSelector = (__node) => {
      #|    if (__selectorParts.length === 0) return false;
      #|    let __current = __node;
      #|    let __idx = __selectorParts.length - 1;
      #|    if (!__matchesSimple(__current, __selectorParts[__idx])) return false;
      #|    __idx -= 1;
      #|    while (__idx >= 0) {
      #|      let __parent = __current
      #|        ? __current._parent ||
      #|          __current.parentElement ||
      #|          __current.parentNode ||
      #|          null
      #|        : null;
      #|      let __found = false;
      #|      while (__parent) {
      #|        if (
      #|          __parent.nodeType === 1 &&
      #|          __matchesSimple(__parent, __selectorParts[__idx])
      #|        ) {
      #|          __found = true;
      #|          __current = __parent;
      #|          break;
      #|        }
      #|        __parent =
      #|          __parent._parent || __parent.parentElement || __parent.parentNode || null;
      #|      }
      #|      if (!__found) return false;
      #|      __idx -= 1;
      #|    }
      #|    return true;
      #|  };
    ) +
    locate_nodes_js_unique_result_push() +
    (
      #|  const __walk = (__node) => {
      #|    if (!__node) return false;
      #|    const __children = __collectChildren(__node);
      #|    for (const __child of __children) {
      #|      if (__matchesSelector(__child)) {
      #|        __push(__child);
      #|        if (__max > 0 && __result.length >= __max) return true;
      #|      }
      #|      if (__walk(__child)) return true;
      #|    }
      #|    return false;
      #|  };
      #|  for (const __root of __roots) {
      #|    if (!__root) continue;
      #|    let __start = null;
      #|    if (__root.nodeType === 9) {
      #|      __start = __root.documentElement || __root.body || null;
      #|    } else if (__root.nodeType === 1 || __root.nodeType === 11) {
      #|      __start = __root;
      #|    }
      #|    if (!__start) continue;
      #|    if (__matchesSelector(__start)) {
      #|      __push(__start);
      #|      if (__max > 0 && __result.length >= __max) {
      #|        return __result.slice(0, __max);
      #|      }
      #|    }
      #|    if (__walk(__start)) return __result.slice(0, __max);
      #|  }
      #|  return __max > 0 ? __result.slice(0, __max) : __result;
      #|})()
    )
  self.evaluate_locate_expression(
    ctx_id, expression, serialization_options_json,
  )
}