"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.generateRules = exports.resolveMatches = void 0;
var _postcss = _interopRequireDefault(require("postcss"));
var _postcssSelectorParser = _interopRequireDefault(require("postcss-selector-parser"));
var _parseObjectStyles = _interopRequireDefault(require("../util/parseObjectStyles"));
var _isPlainObject = _interopRequireDefault(require("../util/isPlainObject"));
var _prefixSelector = _interopRequireDefault(require("../util/prefixSelector"));
var _pluginUtils = require("../util/pluginUtils");
var _log = _interopRequireDefault(require("../util/log"));
var sharedState = _interopRequireWildcard(require("./sharedState"));
var _formatVariantSelector = require("../util/formatVariantSelector");
var _nameClass = require("../util/nameClass");
var _dataTypes = require("../util/dataTypes");
var _setupContextUtils = require("./setupContextUtils");
var _isValidArbitraryValue = _interopRequireDefault(require("../util/isValidArbitraryValue"));
var _splitAtTopLevelOnlyJs = require("../util/splitAtTopLevelOnly.js");
var _featureFlags = require("../featureFlags");
function _interopRequireDefault(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
function _getRequireWildcardCache() {
    if (typeof WeakMap !== "function") return null;
    var cache = new WeakMap();
    _getRequireWildcardCache = function() {
        return cache;
    };
    return cache;
}
function _interopRequireWildcard(obj) {
    if (obj && obj.__esModule) {
        return obj;
    }
    if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
        return {
            default: obj
        };
    }
    var cache = _getRequireWildcardCache();
    if (cache && cache.has(obj)) {
        return cache.get(obj);
    }
    var newObj = {};
    var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
    for(var key in obj){
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
            if (desc && (desc.get || desc.set)) {
                Object.defineProperty(newObj, key, desc);
            } else {
                newObj[key] = obj[key];
            }
        }
    }
    newObj.default = obj;
    if (cache) {
        cache.set(obj, newObj);
    }
    return newObj;
}
let classNameParser = (0, _postcssSelectorParser).default((selectors)=>{
    return selectors.first.filter(({ type  })=>type === "class").pop().value;
});
function getClassNameFromSelector(selector) {
    return classNameParser.transformSync(selector);
}
// Generate match permutations for a class candidate, like:
// ['ring-offset-blue', '100']
// ['ring-offset', 'blue-100']
// ['ring', 'offset-blue-100']
// Example with dynamic classes:
// ['grid-cols', '[[linename],1fr,auto]']
// ['grid', 'cols-[[linename],1fr,auto]']
function* candidatePermutations(candidate) {
    let lastIndex = Infinity;
    while(lastIndex >= 0){
        let dashIdx;
        if (lastIndex === Infinity && candidate.endsWith("]")) {
            let bracketIdx = candidate.indexOf("[");
            // If character before `[` isn't a dash or a slash, this isn't a dynamic class
            // eg. string[]
            dashIdx = [
                "-",
                "/"
            ].includes(candidate[bracketIdx - 1]) ? bracketIdx - 1 : -1;
        } else {
            dashIdx = candidate.lastIndexOf("-", lastIndex);
        }
        if (dashIdx < 0) {
            break;
        }
        let prefix = candidate.slice(0, dashIdx);
        let modifier = candidate.slice(dashIdx + 1);
        yield [
            prefix,
            modifier
        ];
        lastIndex = dashIdx - 1;
    }
}
function applyPrefix(matches, context) {
    if (matches.length === 0 || context.tailwindConfig.prefix === "") {
        return matches;
    }
    for (let match of matches){
        let [meta] = match;
        if (meta.options.respectPrefix) {
            let container = _postcss.default.root({
                nodes: [
                    match[1].clone()
                ]
            });
            let classCandidate = match[1].raws.tailwind.classCandidate;
            container.walkRules((r)=>{
                // If this is a negative utility with a dash *before* the prefix we
                // have to ensure that the generated selector matches the candidate
                // Not doing this will cause `-tw-top-1` to generate the class `.tw--top-1`
                // The disconnect between candidate <-> class can cause @apply to hard crash.
                let shouldPrependNegative = classCandidate.startsWith("-");
                r.selector = (0, _prefixSelector).default(context.tailwindConfig.prefix, r.selector, shouldPrependNegative);
            });
            match[1] = container.nodes[0];
        }
    }
    return matches;
}
function applyImportant(matches, classCandidate) {
    if (matches.length === 0) {
        return matches;
    }
    let result = [];
    for (let [meta, rule] of matches){
        let container = _postcss.default.root({
            nodes: [
                rule.clone()
            ]
        });
        container.walkRules((r)=>{
            r.selector = (0, _pluginUtils).updateAllClasses(r.selector, (className)=>{
                if (className === classCandidate) {
                    return `!${className}`;
                }
                return className;
            });
            r.walkDecls((d)=>d.important = true);
        });
        result.push([
            {
                ...meta,
                important: true
            },
            container.nodes[0]
        ]);
    }
    return result;
}
// Takes a list of rule tuples and applies a variant like `hover`, sm`,
// whatever to it. We used to do some extra caching here to avoid generating
// a variant of the same rule more than once, but this was never hit because
// we cache at the entire selector level further up the tree.
//
// Technically you can get a cache hit if you have `hover:focus:text-center`
// and `focus:hover:text-center` in the same project, but it doesn't feel
// worth the complexity for that case.
function applyVariant(variant, matches, context) {
    if (matches.length === 0) {
        return matches;
    }
    let args;
    // Find partial arbitrary variants
    if (variant.endsWith("]") && !variant.startsWith("[")) {
        args = variant.slice(variant.lastIndexOf("[") + 1, -1);
        variant = variant.slice(0, variant.indexOf(args) - 1 /* - */  - 1 /* [ */ );
    }
    // Register arbitrary variants
    if (isArbitraryValue(variant) && !context.variantMap.has(variant)) {
        let selector = (0, _dataTypes).normalize(variant.slice(1, -1));
        if (!(0, _setupContextUtils).isValidVariantFormatString(selector)) {
            return [];
        }
        let fn = (0, _setupContextUtils).parseVariant(selector);
        let sort = Array.from(context.variantOrder.values()).pop() << 1n;
        context.variantMap.set(variant, [
            [
                sort,
                fn
            ]
        ]);
        context.variantOrder.set(variant, sort);
    }
    if (context.variantMap.has(variant)) {
        let variantFunctionTuples = context.variantMap.get(variant).slice();
        let result = [];
        for (let [meta, rule1] of matches){
            // Don't generate variants for user css
            if (meta.layer === "user") {
                continue;
            }
            let container = _postcss.default.root({
                nodes: [
                    rule1.clone()
                ]
            });
            for (let [variantSort, variantFunction, containerFromArray] of variantFunctionTuples){
                let clone = containerFromArray !== null && containerFromArray !== void 0 ? containerFromArray : container.clone();
                let collectedFormats = [];
                function prepareBackup() {
                    // Already prepared, chicken out
                    if (clone.raws.neededBackup) {
                        return;
                    }
                    clone.raws.neededBackup = true;
                    clone.walkRules((rule)=>rule.raws.originalSelector = rule.selector);
                }
                function modifySelectors(modifierFunction) {
                    prepareBackup();
                    clone.each((rule)=>{
                        if (rule.type !== "rule") {
                            return;
                        }
                        rule.selectors = rule.selectors.map((selector)=>{
                            return modifierFunction({
                                get className () {
                                    return getClassNameFromSelector(selector);
                                },
                                selector
                            });
                        });
                    });
                    return clone;
                }
                let ruleWithVariant = variantFunction({
                    // Public API
                    get container () {
                        prepareBackup();
                        return clone;
                    },
                    separator: context.tailwindConfig.separator,
                    modifySelectors,
                    // Private API for now
                    wrap (wrapper) {
                        let nodes = clone.nodes;
                        clone.removeAll();
                        wrapper.append(nodes);
                        clone.append(wrapper);
                    },
                    format (selectorFormat) {
                        collectedFormats.push(selectorFormat);
                    },
                    args
                });
                // It can happen that a list of format strings is returned from within the function. In that
                // case, we have to process them as well. We can use the existing `variantSort`.
                if (Array.isArray(ruleWithVariant)) {
                    for (let [idx, variantFunction] of ruleWithVariant.entries()){
                        // This is a little bit scary since we are pushing to an array of items that we are
                        // currently looping over. However, you can also think of it like a processing queue
                        // where you keep handling jobs until everything is done and each job can queue more
                        // jobs if needed.
                        variantFunctionTuples.push([
                            // TODO: This could have potential bugs if we shift the sort order from variant A far
                            // enough into the sort space of variant B. The chances are low, but if this happens
                            // then this might be the place too look at. One potential solution to this problem is
                            // reserving additional X places for these 'unknown' variants in between.
                            variantSort | BigInt(idx << ruleWithVariant.length),
                            variantFunction,
                            // If the clone has been modified we have to pass that back
                            // though so each rule can use the modified container
                            clone.clone(), 
                        ]);
                    }
                    continue;
                }
                if (typeof ruleWithVariant === "string") {
                    collectedFormats.push(ruleWithVariant);
                }
                if (ruleWithVariant === null) {
                    continue;
                }
                // We had to backup selectors, therefore we assume that somebody touched
                // `container` or `modifySelectors`. Let's see if they did, so that we
                // can restore the selectors, and collect the format strings.
                if (clone.raws.neededBackup) {
                    delete clone.raws.neededBackup;
                    clone.walkRules((rule)=>{
                        let before = rule.raws.originalSelector;
                        if (!before) return;
                        delete rule.raws.originalSelector;
                        if (before === rule.selector) return; // No mutation happened
                        let modified = rule.selector;
                        // Rebuild the base selector, this is what plugin authors would do
                        // as well. E.g.: `${variant}${separator}${className}`.
                        // However, plugin authors probably also prepend or append certain
                        // classes, pseudos, ids, ...
                        let rebuiltBase = (0, _postcssSelectorParser).default((selectors)=>{
                            selectors.walkClasses((classNode)=>{
                                classNode.value = `${variant}${context.tailwindConfig.separator}${classNode.value}`;
                            });
                        }).processSync(before);
                        // Now that we know the original selector, the new selector, and
                        // the rebuild part in between, we can replace the part that plugin
                        // authors need to rebuild with `&`, and eventually store it in the
                        // collectedFormats. Similar to what `format('...')` would do.
                        //
                        // E.g.:
                        //                   variant: foo
                        //                  selector: .markdown > p
                        //      modified (by plugin): .foo .foo\\:markdown > p
                        //    rebuiltBase (internal): .foo\\:markdown > p
                        //                    format: .foo &
                        collectedFormats.push(modified.replace(rebuiltBase, "&"));
                        rule.selector = before;
                    });
                }
                // This tracks the originating layer for the variant
                // For example:
                // .sm:underline {} is a variant of something in the utilities layer
                // .sm:container {} is a variant of the container component
                clone.nodes[0].raws.tailwind = {
                    ...clone.nodes[0].raws.tailwind,
                    parentLayer: meta.layer
                };
                var _collectedFormats;
                let withOffset = [
                    {
                        ...meta,
                        sort: variantSort | meta.sort,
                        collectedFormats: ((_collectedFormats = meta.collectedFormats) !== null && _collectedFormats !== void 0 ? _collectedFormats : []).concat(collectedFormats)
                    },
                    clone.nodes[0], 
                ];
                result.push(withOffset);
            }
        }
        return result;
    }
    return [];
}
function parseRules(rule, cache, options = {}) {
    // PostCSS node
    if (!(0, _isPlainObject).default(rule) && !Array.isArray(rule)) {
        return [
            [
                rule
            ],
            options
        ];
    }
    // Tuple
    if (Array.isArray(rule)) {
        return parseRules(rule[0], cache, rule[1]);
    }
    // Simple object
    if (!cache.has(rule)) {
        cache.set(rule, (0, _parseObjectStyles).default(rule));
    }
    return [
        cache.get(rule),
        options
    ];
}
const IS_VALID_PROPERTY_NAME = /^[a-z_-]/;
function isValidPropName(name) {
    return IS_VALID_PROPERTY_NAME.test(name);
}
/**
 * @param {string} declaration
 * @returns {boolean}
 */ function looksLikeUri(declaration) {
    // Quick bailout for obvious non-urls
    // This doesn't support schemes that don't use a leading // but that's unlikely to be a problem
    if (!declaration.includes("://")) {
        return false;
    }
    try {
        const url = new URL(declaration);
        return url.scheme !== "" && url.host !== "";
    } catch (err) {
        // Definitely not a valid url
        return false;
    }
}
function isParsableNode(node) {
    let isParsable = true;
    node.walkDecls((decl)=>{
        if (!isParsableCssValue(decl.name, decl.value)) {
            isParsable = false;
            return false;
        }
    });
    return isParsable;
}
function isParsableCssValue(property, value) {
    // We don't want to to treat [https://example.com] as a custom property
    // Even though, according to the CSS grammar, it's a totally valid CSS declaration
    // So we short-circuit here by checking if the custom property looks like a url
    if (looksLikeUri(`${property}:${value}`)) {
        return false;
    }
    try {
        _postcss.default.parse(`a{${property}:${value}}`).toResult();
        return true;
    } catch (err) {
        return false;
    }
}
function extractArbitraryProperty(classCandidate, context) {
    var ref;
    let [, property, value] = (ref = classCandidate.match(/^\[([a-zA-Z0-9-_]+):(\S+)\]$/)) !== null && ref !== void 0 ? ref : [];
    if (value === undefined) {
        return null;
    }
    if (!isValidPropName(property)) {
        return null;
    }
    if (!(0, _isValidArbitraryValue).default(value)) {
        return null;
    }
    let normalized = (0, _dataTypes).normalize(value);
    if (!isParsableCssValue(property, normalized)) {
        return null;
    }
    return [
        [
            {
                sort: context.arbitraryPropertiesSort,
                layer: "utilities"
            },
            ()=>({
                    [(0, _nameClass).asClass(classCandidate)]: {
                        [property]: normalized
                    }
                }), 
        ], 
    ];
}
function* resolveMatchedPlugins(classCandidate, context) {
    if (context.candidateRuleMap.has(classCandidate)) {
        yield [
            context.candidateRuleMap.get(classCandidate),
            "DEFAULT"
        ];
    }
    yield* function*(arbitraryPropertyRule) {
        if (arbitraryPropertyRule !== null) {
            yield [
                arbitraryPropertyRule,
                "DEFAULT"
            ];
        }
    }(extractArbitraryProperty(classCandidate, context));
    let candidatePrefix = classCandidate;
    let negative = false;
    const twConfigPrefix = context.tailwindConfig.prefix;
    const twConfigPrefixLen = twConfigPrefix.length;
    const hasMatchingPrefix = candidatePrefix.startsWith(twConfigPrefix) || candidatePrefix.startsWith(`-${twConfigPrefix}`);
    if (candidatePrefix[twConfigPrefixLen] === "-" && hasMatchingPrefix) {
        negative = true;
        candidatePrefix = twConfigPrefix + candidatePrefix.slice(twConfigPrefixLen + 1);
    }
    if (negative && context.candidateRuleMap.has(candidatePrefix)) {
        yield [
            context.candidateRuleMap.get(candidatePrefix),
            "-DEFAULT"
        ];
    }
    for (let [prefix, modifier] of candidatePermutations(candidatePrefix)){
        if (context.candidateRuleMap.has(prefix)) {
            yield [
                context.candidateRuleMap.get(prefix),
                negative ? `-${modifier}` : modifier
            ];
        }
    }
}
function splitWithSeparator(input, separator) {
    if (input === sharedState.NOT_ON_DEMAND) {
        return [
            sharedState.NOT_ON_DEMAND
        ];
    }
    return Array.from((0, _splitAtTopLevelOnlyJs).splitAtTopLevelOnly(input, separator));
}
function* recordCandidates(matches, classCandidate) {
    for (const match of matches){
        match[1].raws.tailwind = {
            ...match[1].raws.tailwind,
            classCandidate
        };
        yield match;
    }
}
function* resolveMatches(candidate, context, original = candidate) {
    let separator = context.tailwindConfig.separator;
    let [classCandidate, ...variants] = splitWithSeparator(candidate, separator).reverse();
    let important = false;
    if (classCandidate.startsWith("!")) {
        important = true;
        classCandidate = classCandidate.slice(1);
    }
    if ((0, _featureFlags).flagEnabled(context.tailwindConfig, "variantGrouping")) {
        if (classCandidate.startsWith("(") && classCandidate.endsWith(")")) {
            let base = variants.slice().reverse().join(separator);
            for (let part of (0, _splitAtTopLevelOnlyJs).splitAtTopLevelOnly(classCandidate.slice(1, -1), ",")){
                yield* resolveMatches(base + separator + part, context, original);
            }
        }
    }
    // TODO: Reintroduce this in ways that doesn't break on false positives
    // function sortAgainst(toSort, against) {
    //   return toSort.slice().sort((a, z) => {
    //     return bigSign(against.get(a)[0] - against.get(z)[0])
    //   })
    // }
    // let sorted = sortAgainst(variants, context.variantMap)
    // if (sorted.toString() !== variants.toString()) {
    //   let corrected = sorted.reverse().concat(classCandidate).join(':')
    //   throw new Error(`Class ${candidate} should be written as ${corrected}`)
    // }
    for (let matchedPlugins of resolveMatchedPlugins(classCandidate, context)){
        let matches = [];
        let typesByMatches = new Map();
        let [plugins, modifier] = matchedPlugins;
        let isOnlyPlugin = plugins.length === 1;
        for (let [sort, plugin] of plugins){
            let matchesPerPlugin = [];
            if (typeof plugin === "function") {
                for (let ruleSet of [].concat(plugin(modifier, {
                    isOnlyPlugin
                }))){
                    let [rules, options] = parseRules(ruleSet, context.postCssNodeCache);
                    for (let rule of rules){
                        matchesPerPlugin.push([
                            {
                                ...sort,
                                options: {
                                    ...sort.options,
                                    ...options
                                }
                            },
                            rule
                        ]);
                    }
                }
            } else if (modifier === "DEFAULT" || modifier === "-DEFAULT") {
                let ruleSet = plugin;
                let [rules, options] = parseRules(ruleSet, context.postCssNodeCache);
                for (let rule of rules){
                    matchesPerPlugin.push([
                        {
                            ...sort,
                            options: {
                                ...sort.options,
                                ...options
                            }
                        },
                        rule
                    ]);
                }
            }
            if (matchesPerPlugin.length > 0) {
                var ref;
                typesByMatches.set(matchesPerPlugin, (ref = sort.options) === null || ref === void 0 ? void 0 : ref.type);
                matches.push(matchesPerPlugin);
            }
        }
        if (isArbitraryValue(modifier)) {
            // When generated arbitrary values are ambiguous, we can't know
            // which to pick so don't generate any utilities for them
            if (matches.length > 1) {
                var ref1;
                let typesPerPlugin = matches.map((match)=>new Set([
                        ...(ref1 = typesByMatches.get(match)) !== null && ref1 !== void 0 ? ref1 : []
                    ]));
                // Remove duplicates, so that we can detect proper unique types for each plugin.
                for (let pluginTypes of typesPerPlugin){
                    for (let type of pluginTypes){
                        let removeFromOwnGroup = false;
                        for (let otherGroup of typesPerPlugin){
                            if (pluginTypes === otherGroup) continue;
                            if (otherGroup.has(type)) {
                                otherGroup.delete(type);
                                removeFromOwnGroup = true;
                            }
                        }
                        if (removeFromOwnGroup) pluginTypes.delete(type);
                    }
                }
                let messages = [];
                for (let [idx, group] of typesPerPlugin.entries()){
                    for (let type of group){
                        let rules = matches[idx].map(([, rule])=>rule).flat().map((rule)=>rule.toString().split("\n").slice(1, -1) // Remove selector and closing '}'
                            .map((line)=>line.trim()).map((x)=>`      ${x}`) // Re-indent
                            .join("\n")).join("\n\n");
                        messages.push(`  Use \`${candidate.replace("[", `[${type}:`)}\` for \`${rules.trim()}\``);
                        break;
                    }
                }
                _log.default.warn([
                    `The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
                    ...messages,
                    `If this is content and not a class, replace it with \`${candidate.replace("[", "&lsqb;").replace("]", "&rsqb;")}\` to silence this warning.`, 
                ]);
                continue;
            }
            matches = matches.map((list)=>list.filter((match)=>isParsableNode(match[1])));
        }
        matches = matches.flat();
        matches = Array.from(recordCandidates(matches, classCandidate));
        matches = applyPrefix(matches, context);
        if (important) {
            matches = applyImportant(matches, classCandidate);
        }
        for (let variant of variants){
            matches = applyVariant(variant, matches, context);
        }
        for (let match1 of matches){
            match1[1].raws.tailwind = {
                ...match1[1].raws.tailwind,
                candidate
            };
            // Apply final format selector
            if (match1[0].collectedFormats) {
                let finalFormat = (0, _formatVariantSelector).formatVariantSelector("&", ...match1[0].collectedFormats);
                let container = _postcss.default.root({
                    nodes: [
                        match1[1].clone()
                    ]
                });
                container.walkRules((rule)=>{
                    var ref;
                    if (inKeyframes(rule)) return;
                    var ref2;
                    rule.selector = (0, _formatVariantSelector).finalizeSelector(finalFormat, {
                        selector: rule.selector,
                        candidate: original,
                        base: candidate.split(new RegExp(`\\${(ref2 = context === null || context === void 0 ? void 0 : (ref = context.tailwindConfig) === null || ref === void 0 ? void 0 : ref.separator) !== null && ref2 !== void 0 ? ref2 : ":"}(?![^[]*\\])`)).pop(),
                        context
                    });
                });
                match1[1] = container.nodes[0];
            }
            yield match1;
        }
    }
}
function inKeyframes(rule) {
    return rule.parent && rule.parent.type === "atrule" && rule.parent.name === "keyframes";
}
function generateRules(candidates, context) {
    let allRules = [];
    for (let candidate of candidates){
        if (context.notClassCache.has(candidate)) {
            continue;
        }
        if (context.classCache.has(candidate)) {
            allRules.push(context.classCache.get(candidate));
            continue;
        }
        let matches = Array.from(resolveMatches(candidate, context));
        if (matches.length === 0) {
            context.notClassCache.add(candidate);
            continue;
        }
        context.classCache.set(candidate, matches);
        allRules.push(matches);
    }
    // Strategy based on `tailwindConfig.important`
    let strategy = ((important)=>{
        if (important === true) {
            return (rule)=>{
                rule.walkDecls((d)=>{
                    if (d.parent.type === "rule" && !inKeyframes(d.parent)) {
                        d.important = true;
                    }
                });
            };
        }
        if (typeof important === "string") {
            return (rule)=>{
                rule.selectors = rule.selectors.map((selector)=>{
                    return `${important} ${selector}`;
                });
            };
        }
    })(context.tailwindConfig.important);
    return allRules.flat(1).map(([{ sort , layer , options  }, rule])=>{
        if (options.respectImportant) {
            if (strategy) {
                let container = _postcss.default.root({
                    nodes: [
                        rule.clone()
                    ]
                });
                container.walkRules((r)=>{
                    if (inKeyframes(r)) {
                        return;
                    }
                    strategy(r);
                });
                rule = container.nodes[0];
            }
        }
        return [
            sort | context.layerOrder[layer],
            rule
        ];
    });
}
function isArbitraryValue(input) {
    return input.startsWith("[") && input.endsWith("]");
}
exports.resolveMatches = resolveMatches;
exports.generateRules = generateRules;
