(function findElements(target, root) {
    const INVALID_SELECTOR = 'invalid selector';
    const INVALID_ARGUMENT = 'invalid argument';
    const NO_SUCH_ELEMENT = 'no such element';
    function botError(code, message) {
        const err = new Error(message);
        err['code'] = code;
        return err;
    }
    function cssEscapeId(s) {
        return s.replace(/[\s'"\\#.:;,!?+<>=~*^$|%&@`{}\-/\[\]()]/g, (c) => '\\' + c);
    }
    function classNameMany(target, root) {
        if (!target) {
            throw botError(INVALID_SELECTOR, 'No class name specified');
        }
        target = target.trim();
        if (target.indexOf(' ') !== -1) {
            throw botError(INVALID_SELECTOR, 'Compound class names not permitted');
        }
        try {
            return Array.from(root.querySelectorAll('.' + target.replace(/\\/g, '\\\\').replace(/\./g, '\\.')));
        }
        catch (_e) {
            throw botError(INVALID_SELECTOR, 'An invalid or illegal class name was specified');
        }
    }
    function cssMany(target, root) {
        try {
            return Array.from(root.querySelectorAll(target));
        }
        catch (_e) {
            throw botError(INVALID_SELECTOR, 'An invalid or illegal CSS selector was specified: ' + target);
        }
    }
    function idMany(target, root) {
        if (!target) {
            return [];
        }
        if (!/^\d/.test(target)) {
            try {
                return Array.from(root.querySelectorAll('#' + cssEscapeId(target)));
            }
            catch (_e) {
                return [];
            }
        }
        return Array.from(root.querySelectorAll('*')).filter(el => el.getAttribute('id') === target);
    }
    function getLinkText(el) {
        const text = el.innerText !== undefined ? el.innerText : el.textContent || '';
        return text.replace(/^[\s]+|[\s]+$/g, '');
    }
    function linkTextMany(target, root, partial) {
        return Array.from(root.querySelectorAll('a')).filter(el => {
            const text = getLinkText(el);
            return partial ? text.indexOf(target) !== -1 : text === target;
        });
    }
    function nameMany(target, root) {
        return Array.from(root.querySelectorAll('[name="' + target.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"]'));
    }
    function tagNameMany(target, root) {
        if (target === '') {
            throw botError(INVALID_SELECTOR, 'Unable to locate an element with the tagName ""');
        }
        return Array.from(root.getElementsByTagName(target));
    }
    const DEFAULT_NS_RESOLVER = (function () {
        const ns = { svg: 'http://www.w3.org/2000/svg' };
        return (prefix) => ns[prefix] || null;
    })();
    const ORDERED_NODE_SNAPSHOT_TYPE = 7;
    function xpathMany(target, root) {
        const doc = root.documentElement ? root : root.ownerDocument;
        if (!doc.documentElement) {
            return [];
        }
        try {
            let resolver = null;
            if (target.indexOf(':') !== -1) {
                const reversedNs = {};
                const allNodes = doc.getElementsByTagName('*');
                for (let i = 0; i < allNodes.length; i++) {
                    const n = allNodes[i];
                    const ns = n.namespaceURI;
                    if (ns && !reversedNs[ns]) {
                        let prefix = n.lookupPrefix(ns);
                        if (!prefix) {
                            const m = ns.match('.*/(\\w+)/?$');
                            prefix = m ? m[1] : 'xhtml';
                        }
                        reversedNs[ns] = prefix;
                    }
                }
                const namespaces = {};
                for (const key in reversedNs) {
                    namespaces[reversedNs[key]] = key;
                }
                resolver = (prefix) => namespaces[prefix || ''] || null;
            }
            let result = null;
            try {
                result = doc.evaluate(target, root, resolver, ORDERED_NODE_SNAPSHOT_TYPE, null);
            }
            catch (te) {
                if (te.name === 'TypeError') {
                    const fallback = doc.createNSResolver ? doc.createNSResolver(doc.documentElement) : DEFAULT_NS_RESOLVER;
                    result = doc.evaluate(target, root, fallback, ORDERED_NODE_SNAPSHOT_TYPE, null);
                }
                else {
                    throw te;
                }
            }
            if (!result) {
                return [];
            }
            const results = [];
            for (let i = 0; i < result.snapshotLength; i++) {
                const node = result.snapshotItem(i);
                if (!node || node.nodeType !== Node.ELEMENT_NODE) {
                    throw botError(INVALID_SELECTOR, 'The result of the xpath expression "' + target + '" is: ' + node + '. It should be an element.');
                }
                results.push(node);
            }
            return results;
        }
        catch (ex) {
            if (ex.name === 'NS_ERROR_ILLEGAL_VALUE') {
                return [];
            }
            if (ex.code === INVALID_SELECTOR) {
                throw ex;
            }
            throw botError(INVALID_SELECTOR, 'Unable to locate an element with the xpath expression ' + target + ' because of the following error:\n' + ex);
        }
    }
    function getClientRect(element) {
        const r = element.getBoundingClientRect();
        return { left: r.left, top: r.top, width: r.width, height: r.height };
    }
    function resolveAnchor(selector) {
        if (selector instanceof Element) {
            return selector;
        }
        if (typeof selector === 'function') {
            return resolveAnchor(selector());
        }
        if (selector && typeof selector === 'object') {
            const found = findElements(selector);
            if (!found.length) {
                throw botError(NO_SUCH_ELEMENT, 'No element has been found by ' + JSON.stringify(selector));
            }
            return found[0];
        }
        throw botError(INVALID_ARGUMENT, 'Selector is of wrong type: ' + JSON.stringify(selector));
    }
    function makeProximityFilter(selector, test) {
        return (candidate) => test(getClientRect(resolveAnchor(selector)), getClientRect(candidate));
    }
    const RELATIVE_STRATEGIES = {
        above: (sel) => makeProximityFilter(sel, (a, c) => c.top + c.height <= a.top),
        below: (sel) => makeProximityFilter(sel, (a, c) => c.top >= a.top + a.height),
        left: (sel) => makeProximityFilter(sel, (a, c) => c.left + c.width <= a.left),
        right: (sel) => makeProximityFilter(sel, (a, c) => c.left >= a.left + a.width),
        near: (sel, distArg) => {
            const distFromSelector = typeof sel['distance'] === 'number'
                ? sel['distance']
                : 0;
            const distance = typeof distArg === 'number' && distArg > 0 ? distArg : distFromSelector || 50;
            return (candidate) => {
                const anchor = resolveAnchor(sel);
                if (anchor === candidate)
                    return false;
                const a = getClientRect(anchor);
                const c = getClientRect(candidate);
                const expanded = { left: a.left - distance, top: a.top - distance, width: a.width + distance * 2, height: a.height + distance * 2 };
                return (c.left < expanded.left + expanded.width &&
                    c.left + c.width > expanded.left &&
                    c.top < expanded.top + expanded.height &&
                    c.top + c.height > expanded.top);
            };
        },
        straightAbove: (sel) => makeProximityFilter(sel, (a, c) => c.left < a.left + a.width && c.left + c.width > a.left && c.top + c.height <= a.top),
        straightBelow: (sel) => makeProximityFilter(sel, (a, c) => c.left < a.left + a.width && c.left + c.width > a.left && c.top >= a.top + a.height),
        straightLeft: (sel) => makeProximityFilter(sel, (a, c) => c.top < a.top + a.height && c.top + c.height > a.top && c.left + c.width <= a.left),
        straightRight: (sel) => makeProximityFilter(sel, (a, c) => c.top < a.top + a.height && c.top + c.height > a.top && c.left >= a.left + a.width),
    };
    function sortByProximity(anchor, elements) {
        const ar = getClientRect(anchor);
        const acx = ar.left + Math.max(1, ar.width) / 2;
        const acy = ar.top + Math.max(1, ar.height) / 2;
        return elements.slice().sort((a, b) => {
            const ra = getClientRect(a);
            const rb = getClientRect(b);
            const da = Math.sqrt(Math.pow(acx - (ra.left + Math.max(1, ra.width) / 2), 2) + Math.pow(acy - (ra.top + Math.max(1, ra.height) / 2), 2));
            const db = Math.sqrt(Math.pow(acx - (rb.left + Math.max(1, rb.width) / 2), 2) + Math.pow(acy - (rb.top + Math.max(1, rb.height) / 2), 2));
            return da - db;
        });
    }
    function relativeMany(target, root) {
        if (!Object.prototype.hasOwnProperty.call(target, 'root') || !Object.prototype.hasOwnProperty.call(target, 'filters')) {
            throw botError(INVALID_ARGUMENT, 'Locator not suitable for relative locators: ' + JSON.stringify(target));
        }
        const filters = target['filters'];
        if (!Array.isArray(filters)) {
            throw botError(INVALID_ARGUMENT, 'Targets should be an array: ' + JSON.stringify(target));
        }
        let elements;
        const rootTarget = target['root'];
        if (rootTarget instanceof Element) {
            elements = [rootTarget];
        }
        else {
            elements = findElements(rootTarget, root);
        }
        if (!elements.length) {
            return [];
        }
        const matched = elements.filter(el => {
            if (!el)
                return false;
            return filters.every(filter => {
                const strategy = RELATIVE_STRATEGIES[filter.kind];
                if (!strategy) {
                    throw botError(INVALID_ARGUMENT, 'Cannot find filter suitable for ' + filter.kind);
                }
                return strategy(...filter.args)(el);
            });
        });
        const finalFilter = filters[filters.length - 1];
        if (!finalFilter || !RELATIVE_STRATEGIES[finalFilter.kind]) {
            return matched;
        }
        const lastAnchor = resolveAnchor(finalFilter.args[0]);
        return sortByProximity(lastAnchor, matched);
    }
    const actualRoot = root || document;
    const keys = Object.keys(target).filter(k => Object.prototype.hasOwnProperty.call(target, k));
    if (!keys.length) {
        throw botError(INVALID_ARGUMENT, 'Unsupported locator strategy: (empty)');
    }
    const key = keys[0];
    const value = target[key];
    switch (key) {
        case 'className':
        case 'class name':
            return classNameMany(value, actualRoot);
        case 'css':
        case 'css selector':
            return cssMany(value, actualRoot);
        case 'id':
            return idMany(value, actualRoot);
        case 'linkText':
        case 'link text':
            return linkTextMany(value, actualRoot, false);
        case 'partialLinkText':
        case 'partial link text':
            return linkTextMany(value, actualRoot, true);
        case 'name':
            return nameMany(value, actualRoot);
        case 'tagName':
        case 'tag name':
            return tagNameMany(value, actualRoot);
        case 'xpath':
            return xpathMany(value, actualRoot);
        case 'relative':
            return relativeMany(value, actualRoot);
        default:
            throw botError(INVALID_ARGUMENT, 'Unsupported locator strategy: ' + key);
    }
})