"use strict";
/**
 * Copyright (c) Microsoft Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseSelector = exports.selectorsV2EngineNames = exports.selectorsV2Enabled = void 0;
const cssParser_1 = require("./cssParser");
function selectorsV2Enabled() {
    return true;
}
exports.selectorsV2Enabled = selectorsV2Enabled;
function selectorsV2EngineNames() {
    return ['not', 'is', 'where', 'has', 'scope', 'light', 'visible', 'text-matches', 'text-is'];
}
exports.selectorsV2EngineNames = selectorsV2EngineNames;
function parseSelector(selector, customNames) {
    const v1 = parseSelectorV1(selector);
    const names = new Set();
    for (const { name } of v1.parts) {
        names.add(name);
        if (!customNames.has(name))
            throw new Error(`Unknown engine "${name}" while parsing selector ${selector}`);
    }
    if (!selectorsV2Enabled()) {
        return {
            v1,
            names: Array.from(names),
        };
    }
    const chain = (from, to) => {
        const result = { simples: [] };
        for (const part of v1.parts.slice(from, to)) {
            let name = part.name;
            let wrapInLight = false;
            if (['css:light', 'xpath:light', 'text:light', 'id:light', 'data-testid:light', 'data-test-id:light', 'data-test:light'].includes(name)) {
                wrapInLight = true;
                name = name.substring(0, name.indexOf(':'));
            }
            let simple;
            if (name === 'css') {
                const parsed = cssParser_1.parseCSS(part.body, customNames);
                parsed.names.forEach(name => names.add(name));
                simple = callWith('is', parsed.selector);
            }
            else if (name === 'text') {
                simple = textSelectorToSimple(part.body);
                if (result.simples.length)
                    result.simples[result.simples.length - 1].combinator = '>=';
            }
            else {
                simple = callWith(name, [part.body]);
            }
            if (wrapInLight)
                simple = callWith('light', [simpleToComplex(simple)]);
            result.simples.push({ selector: simple, combinator: '' });
        }
        return result;
    };
    const capture = v1.capture === undefined ? v1.parts.length - 1 : v1.capture;
    const result = chain(0, capture + 1);
    if (capture + 1 < v1.parts.length) {
        const has = chain(capture + 1, v1.parts.length);
        const last = result.simples[result.simples.length - 1];
        last.selector.functions.push({ name: 'has', args: [has] });
    }
    return { v2: [result], names: Array.from(names) };
}
exports.parseSelector = parseSelector;
function callWith(name, args) {
    return { functions: [{ name, args }] };
}
function simpleToComplex(simple) {
    return { simples: [{ selector: simple, combinator: '' }] };
}
function textSelectorToSimple(selector) {
    function unescape(s) {
        if (!s.includes('\\'))
            return s;
        const r = [];
        let i = 0;
        while (i < s.length) {
            if (s[i] === '\\' && i + 1 < s.length)
                i++;
            r.push(s[i++]);
        }
        return r.join('');
    }
    function escapeRegExp(s) {
        return s.replace(/[.*+\?^${}()|[\]\\]/g, '\\$&').replace(/-/g, '\\x2d');
    }
    let functionName = 'text-matches';
    let args;
    if (selector.length > 1 && selector[0] === '"' && selector[selector.length - 1] === '"') {
        args = ['^' + escapeRegExp(unescape(selector.substring(1, selector.length - 1))) + '$'];
    }
    else if (selector.length > 1 && selector[0] === "'" && selector[selector.length - 1] === "'") {
        args = ['^' + escapeRegExp(unescape(selector.substring(1, selector.length - 1))) + '$'];
    }
    else if (selector[0] === '/' && selector.lastIndexOf('/') > 0) {
        const lastSlash = selector.lastIndexOf('/');
        args = [selector.substring(1, lastSlash), selector.substring(lastSlash + 1)];
    }
    else {
        functionName = 'text';
        args = [selector];
    }
    return callWith(functionName, args);
}
function parseSelectorV1(selector) {
    let index = 0;
    let quote;
    let start = 0;
    const result = { parts: [] };
    const append = () => {
        const part = selector.substring(start, index).trim();
        const eqIndex = part.indexOf('=');
        let name;
        let body;
        if (eqIndex !== -1 && part.substring(0, eqIndex).trim().match(/^[a-zA-Z_0-9-+:*]+$/)) {
            name = part.substring(0, eqIndex).trim();
            body = part.substring(eqIndex + 1);
        }
        else if (part.length > 1 && part[0] === '"' && part[part.length - 1] === '"') {
            name = 'text';
            body = part;
        }
        else if (part.length > 1 && part[0] === "'" && part[part.length - 1] === "'") {
            name = 'text';
            body = part;
        }
        else if (/^\(*\/\//.test(part) || part.startsWith('..')) {
            // If selector starts with '//' or '//' prefixed with multiple opening
            // parenthesis, consider xpath. @see https://github.com/microsoft/playwright/issues/817
            // If selector starts with '..', consider xpath as well.
            name = 'xpath';
            body = part;
        }
        else {
            name = 'css';
            body = part;
        }
        let capture = false;
        if (name[0] === '*') {
            capture = true;
            name = name.substring(1);
        }
        result.parts.push({ name, body });
        if (capture) {
            if (result.capture !== undefined)
                throw new Error(`Only one of the selectors can capture using * modifier`);
            result.capture = result.parts.length - 1;
        }
    };
    if (!selector.includes('>>')) {
        index = selector.length;
        append();
        return result;
    }
    while (index < selector.length) {
        const c = selector[index];
        if (c === '\\' && index + 1 < selector.length) {
            index += 2;
        }
        else if (c === quote) {
            quote = undefined;
            index++;
        }
        else if (!quote && (c === '"' || c === '\'' || c === '`')) {
            quote = c;
            index++;
        }
        else if (!quote && c === '>' && selector[index + 1] === '>') {
            append();
            index += 2;
            start = index;
        }
        else {
            index++;
        }
    }
    append();
    return result;
}
//# sourceMappingURL=selectorParser.js.map