"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 _formatVariantSelector = require("../util/formatVariantSelector");
var _nameClass = require("../util/nameClass");
var _dataTypes = require("../util/dataTypes");
var _isValidArbitraryValue = _interopRequireDefault(require("../util/isValidArbitraryValue"));
function _interopRequireDefault(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
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, lastIndex = Infinity) {
    if (lastIndex < 0) {
        return;
    }
    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) {
        return;
    }
    let prefix = candidate.slice(0, dashIdx);
    let modifier = candidate.slice(dashIdx + 1);
    yield [
        prefix,
        modifier
    ];
    yield* candidatePermutations(candidate, 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()
                ]
            });
            container.walkRules((r)=>{
                r.selector = (0, _prefixSelector).default(context.tailwindConfig.prefix, r.selector);
            });
            match[1] = container.nodes[0];
        }
    }
    return matches;
}
function applyImportant(matches) {
    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)=>{
                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;
    }
    if (context.variantMap.has(variant)) {
        let variantFunctionTuples = context.variantMap.get(variant);
        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] of variantFunctionTuples){
                let clone = container.clone();
                let collectedFormats = [];
                let originals = new Map();
                function prepareBackup() {
                    if (originals.size > 0) return; // Already prepared, chicken out
                    clone.walkRules((rule)=>originals.set(rule, 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);
                    }
                });
                if (typeof ruleWithVariant === 'string') {
                    collectedFormats.push(ruleWithVariant);
                }
                if (ruleWithVariant === null) {
                    continue;
                }
                // We filled the `originals`, 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 (originals.size > 0) {
                    clone.walkRules((rule)=>{
                        if (!originals.has(rule)) return;
                        let before = originals.get(rule);
                        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;
                    });
                }
                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);
}
function isParsableCssValue(property, value) {
    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;
    if (candidatePrefix[twConfigPrefixLen] === '-') {
        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) {
    return input.split(new RegExp(`\\${separator}(?![^[]*\\])`, 'g'));
}
function* resolveMatches(candidate, context) {
    let separator = context.tailwindConfig.separator;
    let [classCandidate, ...variants] = splitWithSeparator(candidate, separator).reverse();
    let important = false;
    if (classCandidate.startsWith('!')) {
        important = true;
        classCandidate = classCandidate.slice(1);
    }
    // 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);
            }
        }
        // Only keep the result of the very first plugin if we are dealing with
        // arbitrary values, to protect against ambiguity.
        if (isArbitraryValue(modifier) && 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 = applyPrefix(matches.flat(), context);
        if (important) {
            matches = applyImportant(matches, context);
        }
        for (let variant of variants){
            matches = applyVariant(variant, matches, context);
        }
        for (let match1 of matches){
            // 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)=>{
                    if (inKeyframes(rule)) return;
                    rule.selector = (0, _formatVariantSelector).finalizeSelector(finalFormat, {
                        selector: rule.selector,
                        candidate,
                        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;
