import cssEscape from './shared/utils/css-escape.mjs';
import Layer from './layer.mjs';
import { VALUE_DELIMITERS, UNIT_REGEX, BASE_UNIT_REGEX } from './common.mjs';

class Rule {
    className;
    RegisteredRule;
    css;
    at;
    priority;
    natives;
    order;
    layer;
    declarations;
    animationNames;
    variableNames;
    constructor(className, RegisteredRule, css){
        this.className = className;
        this.RegisteredRule = RegisteredRule;
        this.css = css;
        this.at = {};
        this.priority = -1;
        this.natives = [];
        this.order = 0;
        this.layer = 0;
        this.resolveValue = (valueComponents, unit, bypassVariableNames, bypassParsing)=>{
            const { functions } = this.css.config;
            let currentValue = '';
            for (const eachValueComponent of valueComponents){
                switch(eachValueComponent.type){
                    case 'function':
                        // eslint-disable-next-line no-case-declarations
                        const functionDefinition = functions && functions[eachValueComponent.name];
                        if (functionDefinition?.transform && !eachValueComponent.bypassTransform) {
                            const result = functionDefinition.transform.call(this, this.resolveValue(eachValueComponent.children, functionDefinition.unit ?? unit, bypassVariableNames, bypassParsing || eachValueComponent.name === 'calc'), bypassVariableNames);
                            currentValue += eachValueComponent.token = eachValueComponent.text = typeof result === 'string' ? result : this.resolveValue(result, functionDefinition?.unit ?? unit, bypassVariableNames, bypassParsing);
                        } else {
                            currentValue += eachValueComponent.token = eachValueComponent.text = eachValueComponent.name + eachValueComponent.symbol + this.resolveValue(eachValueComponent.children, functionDefinition?.unit ?? unit, bypassVariableNames, bypassParsing) + VALUE_DELIMITERS[eachValueComponent.symbol];
                        }
                        break;
                    // todo: 應挪到 parseValues 階段處理才能支援 variables: { x: 'calc(20vw-30px)' } 這種情況，並且解析上可能會比較合理、精簡
                    case 'variable':
                        // eslint-disable-next-line no-case-declarations
                        const variable = this.css.variables[eachValueComponent.name];
                        if (variable) {
                            const handleVariable = (normalHandler, varHandler)=>{
                                if (variable.modes) {
                                    if (this.mode) {
                                        const themeVariable = variable.modes[this.mode] ?? variable;
                                        if (themeVariable?.value) {
                                            normalHandler(themeVariable);
                                        }
                                    } else {
                                        if (!this.variableNames) {
                                            this.variableNames = [];
                                        }
                                        if (!this.variableNames.includes(eachValueComponent.name)) {
                                            this.variableNames.push(eachValueComponent.name);
                                        }
                                        varHandler();
                                    }
                                } else {
                                    normalHandler(variable);
                                }
                            };
                            switch(variable.type){
                                case 'string':
                                    handleVariable((variable)=>{
                                        const valueComponents = [];
                                        this.parseValues(valueComponents, 0, variable.value, unit, '', undefined, bypassParsing, [
                                            ...bypassVariableNames,
                                            eachValueComponent.name
                                        ]);
                                        currentValue += eachValueComponent.text = this.resolveValue(valueComponents, unit, [
                                            ...bypassVariableNames,
                                            eachValueComponent.name
                                        ], bypassParsing);
                                    }, ()=>{
                                        currentValue += eachValueComponent.text = `var(--${eachValueComponent.name})`;
                                    });
                                    break;
                                case 'number':
                                    handleVariable((variable)=>{
                                        if (bypassParsing) {
                                            currentValue += eachValueComponent.text = String(variable.value);
                                        } else {
                                            const valueComponent = this.parseValue(variable.value, unit);
                                            currentValue += eachValueComponent.text = valueComponent.value + (valueComponent.unit ?? '');
                                        }
                                    }, ()=>{
                                        currentValue += eachValueComponent.text = unit ? `calc(var(--${eachValueComponent.name}) / 16 * 1rem)` : `var(--${eachValueComponent.name})`;
                                    });
                                    break;
                                case 'color':
                                    // eslint-disable-next-line no-case-declarations
                                    const alpha = eachValueComponent.alpha ? '/' + eachValueComponent.alpha : '';
                                    handleVariable((variable)=>{
                                        currentValue += eachValueComponent.text = `${variable['space']}(${variable.value}${alpha})`;
                                    }, ()=>{
                                        currentValue += eachValueComponent.text = `${variable.space}(var(--${eachValueComponent.name})${alpha})`;
                                    });
                                    break;
                            }
                        } else {
                            currentValue += eachValueComponent.text = `var(--${eachValueComponent.name}${eachValueComponent.fallback ? ',' + eachValueComponent.fallback : ''})`;
                        }
                        break;
                    case 'separator':
                        currentValue += eachValueComponent.text ? eachValueComponent.text : eachValueComponent.text = eachValueComponent.value;
                        break;
                    case 'number':
                        currentValue += eachValueComponent.text = eachValueComponent.value + (eachValueComponent.unit || '');
                        break;
                    default:
                        currentValue += eachValueComponent.text = eachValueComponent.value;
                        break;
                }
            }
            return currentValue;
        };
        this.parseValues = (currentValueComponents, i, value, unit, endSymbol, parentFunctionName, bypassParsing = false, bypassVariableNames = [])=>{
            const root = parentFunctionName === undefined;
            const isVarFunction = !root && (parentFunctionName.endsWith('$') || parentFunctionName.endsWith('var'));
            const checkIsString = (value)=>value === '\'' || value === '"';
            const isString = checkIsString(endSymbol);
            const separators = [
                ','
            ];
            if (this.definition.separators?.length) {
                separators.push(...this.definition.separators);
            }
            let currentValue = '';
            const parse = ()=>{
                if (currentValue) {
                    let handled = false;
                    if (!isVarFunction || currentValueComponents.length) {
                        const handleVariable = (variableName, alpha)=>{
                            const variable = Object.prototype.hasOwnProperty.call(this.variables, variableName) ? this.variables[variableName] : Object.prototype.hasOwnProperty.call(this.css.variables, variableName) ? this.css.variables[variableName] : undefined;
                            if (variable) {
                                const name = variable.name ?? variableName;
                                if (!bypassVariableNames.includes(name)) {
                                    handled = true;
                                    const valueComponent = {
                                        type: 'variable',
                                        name,
                                        variable: this.css.variables[name],
                                        token: currentValue
                                    };
                                    if (alpha) valueComponent.alpha = alpha;
                                    currentValueComponents.push(valueComponent);
                                }
                            }
                        };
                        handleVariable(currentValue);
                        if (!handled) {
                            const [colorName, alpha] = currentValue.split('/');
                            handleVariable(colorName, alpha);
                        }
                    }
                    if (!handled) {
                        if (!isVarFunction) {
                            const result = BASE_UNIT_REGEX.exec(currentValue);
                            if (result) {
                                currentValue = String(+result[1] * (this.css.config.baseUnit ?? 1));
                            }
                        }
                        if (bypassParsing) {
                            currentValueComponents.push({
                                type: 'string',
                                value: currentValue,
                                token: currentValue
                            });
                        } else {
                            currentValueComponents.push({
                                ...this.parseValue(currentValue, unit),
                                token: currentValue
                            });
                        }
                    }
                    currentValue = '';
                }
            };
            for(; i < value.length; i++){
                const val = value[i];
                if (val === endSymbol) {
                    if (isString) {
                        let count = 0;
                        for(let j = currentValue.length - 1;; j--){
                            if (currentValue[j] !== '\\') break;
                            count++;
                        }
                        if (count % 2) {
                            currentValue += val;
                            continue;
                        } else {
                            parse();
                        }
                    } else {
                        parse();
                    }
                    return i;
                } else if (!isString && val in VALUE_DELIMITERS) {
                    const functionName = currentValue;
                    const newValueComponent = {
                        type: 'function',
                        name: functionName,
                        symbol: val,
                        children: [],
                        token: ''
                    };
                    currentValueComponents.push(newValueComponent);
                    currentValue = '';
                    const functionDefinition = val === '(' ? this.css.config.functions?.[functionName] : undefined;
                    i = this.parseValues(newValueComponent.children, ++i, value, functionDefinition?.unit ?? unit, VALUE_DELIMITERS[val], functionName || parentFunctionName || '', bypassParsing || functionName === 'calc');
                } else if ((val === '|' || val === ' ') && endSymbol !== '}' && (!isString || parentFunctionName === 'path')) {
                    parse();
                    currentValueComponents.push({
                        type: 'separator',
                        value: ' ',
                        token: val
                    });
                } else {
                    if (!isString) {
                        if (val === '.') {
                            if (isNaN(+value[i + 1])) {
                                if (root) break;
                            } else if (value[i - 1] === '-') {
                                currentValue += '0';
                            }
                        } else if (separators.includes(val)) {
                            parse();
                            currentValueComponents.push({
                                type: 'separator',
                                value: val,
                                text: (val === ',' ? '' : ' ') + val + (val === ',' ? '' : ' '),
                                token: val
                            });
                            continue;
                        } else if (root && (val === '#' && (currentValue || currentValueComponents.length && currentValueComponents[currentValueComponents.length - 1]['type'] !== 'separator') || [
                            '!',
                            '*',
                            '>',
                            '+',
                            '~',
                            ':',
                            '[',
                            '@',
                            '_'
                        ].includes(val))) {
                            break;
                        }
                    }
                    currentValue += val;
                }
            }
            parse();
            return i;
        };
        Object.assign(this, RegisteredRule);
        const { id, definition } = RegisteredRule;
        const { analyze, transformValue, declare, transformValueComponents, create, layer, unit } = definition;
        this.layer = layer;
        if (!definition.unit) definition.unit = '';
        if (!definition.separators) definition.separators = [
            ','
        ];
        const { scope, important, modes } = css.config;
        const { selectors, at, stylesBy, animations } = css;
        const classNames = stylesBy[className];
        if (create) create.call(this, className);
        // 1. value / selectorToken
        this.declarations = definition.declarations;
        let stateToken;
        let prefixToken;
        if (layer === Layer.Utility) {
            // TODO: id 使用其他方式傳遞
            stateToken = className.slice(id.length - 1);
        } else {
            let valueToken;
            if (analyze) {
                [valueToken, prefixToken] = analyze.call(this, className);
            } else {
                const indexOfColon = className.indexOf(':');
                this.keyToken = className.slice(0, indexOfColon + 1);
                valueToken = className.slice(indexOfColon + 1);
            }
            this.valueComponents = [];
            const parsedValueIndex = this.parseValues(this.valueComponents, 0, valueToken, unit, '', undefined, false, definition.includeAnimations ? Object.keys(this.css.animations) : []);
            this.valueToken = valueToken.slice(0, parsedValueIndex);
            stateToken = valueToken.slice(parsedValueIndex);
        }
        // 2. !important
        if (stateToken[0] === '!') {
            this.important = true;
            stateToken = stateToken.slice(1);
        }
        this.stateToken = stateToken;
        // 3. prefix selector
        const generateVendorSelectors = (selectorText, vendorSelectors)=>{
            const transformSelector = (selectorText, selectorValues, selectors, matched)=>{
                for (const [regexp, newSelectorTexts] of selectorValues){
                    if (regexp.test(selectorText)) {
                        for (const eachNewSelectorText of newSelectorTexts){
                            transformSelector(selectorText.replace(regexp, eachNewSelectorText), selectorValues, selectors, true);
                        }
                        return;
                    }
                }
                if (matched) {
                    selectors.push(selectorText);
                }
            };
            const spacedSelectorToken = (selectorText)=>{
                // 1. \'123\'
                // 2. [href='http://localhost']
                const transformedSelectorText = selectorText.split(/(\\'(?:.*?)[^\\]\\')(?=[*_>~+,)])|(\[[^=]+='(?:.*?)[^\\]'\])/).map((eachToken, i)=>i % 3 ? eachToken : eachToken.replace(/(^|[^_])_(?!_)/g, '$1 ')).join('');
                const selectors = [];
                let currentSelector = '';
                let symbolCount = 0;
                for(let i = 0; i < transformedSelectorText.length; i++){
                    const char = transformedSelectorText[i];
                    if (char === '\\') {
                        currentSelector += char + transformedSelectorText[++i];
                        continue;
                    }
                    if (!symbolCount && char === ',') {
                        selectors.push(currentSelector);
                        currentSelector = '';
                    } else {
                        currentSelector += char;
                        if (symbolCount && char === ')') {
                            symbolCount--;
                        } else if (char === '(') {
                            symbolCount++;
                        }
                    }
                }
                if (currentSelector) {
                    selectors.push(currentSelector);
                }
                return selectors;
            };
            const transformedSelectors = [];
            if ('' in selectors) {
                transformSelector(selectorText, selectors[''], transformedSelectors, true);
            } else {
                transformedSelectors.push(selectorText);
            }
            const unspacedVendorSelectors = {};
            for (const [vendor, selectorValues] of Object.entries(selectors)){
                if (!vendor) continue;
                const newUnspacedVendorSelectors = [];
                for (const eachTransformedSelector of transformedSelectors){
                    transformSelector(eachTransformedSelector, selectorValues, newUnspacedVendorSelectors, false);
                }
                if (newUnspacedVendorSelectors.length) {
                    unspacedVendorSelectors[vendor] = newUnspacedVendorSelectors;
                }
            }
            const insertVendorSelectors = (vendor, selectorTexts)=>{
                const groupedSelectorTexts = selectorTexts.reduce((arr, eachSuffixSelector)=>{
                    arr.push(...spacedSelectorToken(eachSuffixSelector));
                    return arr;
                }, []);
                if (vendor in vendorSelectors) {
                    vendorSelectors[vendor].push(...groupedSelectorTexts);
                } else {
                    vendorSelectors[vendor] = groupedSelectorTexts;
                }
            };
            const vendors = Object.keys(unspacedVendorSelectors);
            if (vendors.length) {
                for (const eachVendor of vendors){
                    insertVendorSelectors(eachVendor, unspacedVendorSelectors[eachVendor]);
                }
            } else {
                insertVendorSelectors('', transformedSelectors);
            }
        };
        if (prefixToken) {
            this.vendorPrefixSelectors = {};
            generateVendorSelectors(prefixToken, this.vendorPrefixSelectors);
        } else {
            this.vendorPrefixSelectors = {
                '': [
                    ''
                ]
            };
        }
        // 4. suffix selector
        const stateTokens = stateToken.split('@');
        const suffixSelector = stateTokens[0];
        if (suffixSelector) {
            this.vendorSuffixSelectors = {};
            generateVendorSelectors(suffixSelector, this.vendorSuffixSelectors);
            for (const suffixSelectors of Object.values(this.vendorSuffixSelectors)){
                for (const eachSuffixSelector of suffixSelectors){
                    if (this.hasWhere !== false) {
                        this.hasWhere = eachSuffixSelector.includes(':where(');
                    }
                    const SORTED_SELECTORS = [
                        ':disabled',
                        ':active',
                        ':focus',
                        ':hover'
                    ];
                    for(let i = 0; i < SORTED_SELECTORS.length; i++){
                        if (eachSuffixSelector.includes(SORTED_SELECTORS[i])) {
                            if (this.priority === -1 || this.priority > i) {
                                this.priority = i;
                            }
                            break;
                        }
                    }
                }
            }
        } else {
            this.vendorSuffixSelectors = {
                '': [
                    ''
                ]
            };
        }
        // selector combinations
        const suffixSelectorVendorsByPrefixSelectorVendor = {};
        const isPrefixSelectorWithoutVendor = Object.prototype.hasOwnProperty.call(this.vendorPrefixSelectors, '');
        const isSuffixSelectorWithoutVendor = Object.prototype.hasOwnProperty.call(this.vendorSuffixSelectors, '');
        if (isPrefixSelectorWithoutVendor) {
            suffixSelectorVendorsByPrefixSelectorVendor[''] = isSuffixSelectorWithoutVendor ? [
                ''
            ] : Object.keys(this.vendorSuffixSelectors);
        } else {
            if (isSuffixSelectorWithoutVendor) {
                for(const vendor in this.vendorPrefixSelectors){
                    suffixSelectorVendorsByPrefixSelectorVendor[vendor] = [
                        ''
                    ];
                }
            } else {
                for(const prefixSelectorVendor in this.vendorPrefixSelectors){
                    const suffixSelectorVendors = suffixSelectorVendorsByPrefixSelectorVendor[prefixSelectorVendor] = [];
                    if (Object.prototype.hasOwnProperty.call(this.vendorSuffixSelectors, prefixSelectorVendor)) {
                        suffixSelectorVendors.push(prefixSelectorVendor);
                    } else {
                        for(const suffixVendor in this.vendorSuffixSelectors){
                            suffixSelectorVendors.push(suffixVendor);
                        }
                    }
                }
            }
        }
        // 5. atTokens
        for(let i = 1; i < stateTokens.length; i++){
            const atToken = stateTokens[i];
            this.atToken = (this.atToken || '') + '@' + atToken;
            if (atToken) {
                if (atToken === 'rtl' || atToken === 'ltr') {
                    this.direction = atToken;
                } else {
                    let queryType;
                    const atComponents = [];
                    // x font-face, counter-style, keyframes, font-feature-values, property, layer
                    const queryTypeRegExp = /^(media|supports|container|layer)/;
                    const atRuleResult = queryTypeRegExp.exec(atToken);
                    if (atRuleResult) {
                        queryType = atRuleResult[1];
                        atComponents.push({
                            type: 'arbitrary',
                            token: atToken,
                            value: atToken.slice(queryType.length).replace(/\|/g, ' ')
                        });
                    } else {
                        const analyzeToken = (atComponentToken)=>{
                            if (atComponentToken === '&') {
                                atComponents.push({
                                    type: 'operator',
                                    token: atComponentToken,
                                    value: 'and'
                                });
                            } else if (atComponentToken.startsWith('')) {
                                const targetAt = at[atComponentToken];
                                if (targetAt && typeof targetAt === 'string') {
                                    const match = targetAt.match(queryTypeRegExp);
                                    queryType = match ? match[1] : '';
                                    if (!queryType) throw new Error(`Invalid query '${atComponentToken}': '${targetAt}'`);
                                    atComponents.push({
                                        type: 'arbitrary',
                                        value: targetAt.slice(match ? match[1].length + 1 : 0)
                                    });
                                } else {
                                    // todo: container queries
                                    queryType = 'media';
                                    let featureName = '';
                                    let extremumOperator = '';
                                    let correction = 0;
                                    if (atComponentToken.startsWith('<=')) {
                                        extremumOperator = '<=';
                                        featureName = 'max-width';
                                    } else if (atComponentToken.startsWith('>') && !atComponentToken.startsWith('>=')) {
                                        extremumOperator = '>';
                                        featureName = 'min-width';
                                        correction = .02;
                                    } else if (atComponentToken.startsWith('<')) {
                                        extremumOperator = '<';
                                        featureName = 'max-width';
                                        correction = -.02;
                                    } else if (atComponentToken.startsWith('>=') || targetAt) {
                                        extremumOperator = '>=';
                                        featureName = 'min-width';
                                    }
                                    const token = extremumOperator ? atComponentToken.replace(extremumOperator, '') : atComponentToken;
                                    const viewport = at[token];
                                    switch(featureName){
                                        case 'max-width':
                                        case 'min-width':
                                            // eslint-disable-next-line no-case-declarations
                                            if (typeof viewport === 'number') {
                                                atComponents.push({
                                                    type: 'feature',
                                                    name: featureName,
                                                    valueType: 'number',
                                                    value: viewport + correction,
                                                    unit: 'px'
                                                });
                                            } else {
                                                const valueComponent = this.parseValue(token, 'px');
                                                if (valueComponent.type === 'number') {
                                                    atComponents.push({
                                                        type: 'feature',
                                                        name: featureName,
                                                        valueType: 'number',
                                                        value: valueComponent.value + correction,
                                                        unit: valueComponent.unit
                                                    });
                                                } else {
                                                    atComponents.push({
                                                        type: 'feature',
                                                        name: featureName,
                                                        valueType: 'string',
                                                        value: token
                                                    });
                                                }
                                            }
                                            break;
                                    }
                                }
                            }
                        };
                        const isAmpersandIncluded = atToken.includes('&');
                        if (isAmpersandIncluded) {
                            const typeOrFeatureTokens = atToken.split(/(&|,)/);
                            for (const atComponentToken of typeOrFeatureTokens){
                                analyzeToken(atComponentToken);
                            }
                        } else {
                            analyzeToken(atToken);
                        }
                        if (!atComponents.length) {
                            if (!isAmpersandIncluded && modes?.[atToken]) {
                                this.mode = atToken;
                                continue;
                            }
                        }
                    }
                    if (queryType) {
                        this.at[queryType] = atComponents;
                    }
                }
            }
        }
        // 7. value
        let newValue;
        if (this.valueComponents) {
            if (transformValueComponents) {
                this.valueComponents = transformValueComponents.call(this, this.valueComponents);
            }
            newValue = this.resolveValue(this.valueComponents, unit, [], false);
            if (transformValue) {
                newValue = transformValue.call(this, newValue);
            }
            if (declare) {
                this.declarations = declare.call(this, newValue, this.valueComponents);
            } else if (id) {
                this.declarations = {
                    [id]: newValue
                };
            }
        }
        const propertiesText = [];
        for(const propertyName in this.declarations){
            const push = (propertyText)=>{
                // animations
                if (animations && (propertyText.startsWith('animation') || propertyText.startsWith('animation-name'))) {
                    const animationNames = propertyText.split(':')[1].split('!important')[0].split(' ').filter((eachValue)=>eachValue in this.css.animations && (!this.animationNames || !this.animationNames.includes(eachValue)));
                    if (animationNames.length) {
                        if (!this.animationNames) {
                            this.animationNames = [];
                        }
                        this.animationNames = animationNames;
                    }
                }
                propertiesText.push(propertyText + ((this.important || important) && !propertyText.endsWith('!important') ? '!important' : ''));
            };
            const keyToken = propertyName + ':';
            const declarations = this.declarations[propertyName];
            if (Array.isArray(declarations)) {
                for (const value of declarations){
                    push(keyToken + String(value));
                }
            } else {
                push(keyToken + String(declarations));
            }
        }
        // 創建 Natives
        if (propertiesText.length) {
            for(const prefixSelectorVendor in suffixSelectorVendorsByPrefixSelectorVendor){
                for (const eachSuffixSelectorVendor of suffixSelectorVendorsByPrefixSelectorVendor[prefixSelectorVendor]){
                    let prefixText = '';
                    if (this.direction) {
                        prefixText += '[dir=' + this.direction + '] ';
                    }
                    const prefixSelectors = this.vendorPrefixSelectors[prefixSelectorVendor];
                    const suffixSelectors = this.vendorSuffixSelectors[eachSuffixSelectorVendor];
                    const prefixTexts = prefixSelectors.map((eachPrefixSelector)=>eachPrefixSelector + prefixText);
                    const getCssText = (name)=>prefixTexts.map((eachPrefixText)=>(this.mode && modes?.[this.mode] !== 'media' ? modes?.[this.mode] === 'host' ? `:host(.${this.mode}) ` : `.${this.mode} ` : '') + (scope ? scope + ' ' : '') + eachPrefixText).reduce((arr, eachPrefixText)=>{
                            arr.push(suffixSelectors.reduce((_arr, eachSuffixSelector)=>{
                                _arr.push(eachPrefixText + '.' + cssEscape(name) + eachSuffixSelector);
                                return _arr;
                            }, []).join(','));
                            return arr;
                        }, []).join(',');
                    let cssText = getCssText(className) + (classNames ? classNames.reduce((str, className)=>str + ',' + getCssText(className), '') : '') + '{' + propertiesText.join(';') + '}';
                    for (const key of Object.keys(this.at).sort((_a, b)=>b === 'supports' ? -1 : 1)){
                        cssText = '@' + key + (key.includes(' ') ? '' : ' ') + this.at[key].map((eachAtComponent)=>this.resolveAtComponent(eachAtComponent)).join(' ') + '{' + cssText + '}';
                    }
                    if (this.mode && modes?.[this.mode] === 'media') {
                        cssText = `@media(prefers-color-scheme:${this.mode}){` + cssText + '}';
                    }
                    this.natives.push({
                        text: cssText
                    });
                }
            }
        }
    }
    resolveAtComponent(atComponent) {
        switch(atComponent.type){
            case 'arbitrary':
                return atComponent.value;
            case 'feature':
                return '(' + atComponent.name + ':' + atComponent.value + (atComponent.unit || '') + ')';
            case 'operator':
                return atComponent.value;
        }
    }
    resolveValue;
    get text() {
        return this.natives.map((eachNative)=>eachNative.text).join('');
    }
    parseValues;
    parseValue(token, unit = this.definition.unit) {
        const defaultUnit = unit ?? this.definition.unit;
        let newUnit = '';
        let value;
        if (typeof token === 'number') {
            /**
             * 當無單位值且 defaultUnit === 'rem'，
             * 將 pxValue / 16 轉為 remValue
             */ if (defaultUnit && !newUnit) {
                if (defaultUnit === 'rem' || defaultUnit === 'em') {
                    value = token / (this.css.config.rootSize || 1);
                }
                newUnit = defaultUnit || '';
            } else {
                value = token;
            }
            return {
                value,
                unit: newUnit,
                type: 'number'
            };
        } else if (defaultUnit) {
            // w:1/2 -> width: 50%
            if (/^\d+\/\d+/.test(token)) {
                const [dividend, divisor] = token.split('/');
                return {
                    value: +dividend / +divisor * 100,
                    unit: '%',
                    type: 'number'
                };
            }
            const matches = token.match(UNIT_REGEX);
            // ['0.5deg', '0.5', 'deg', index: 0, input: '0.5deg', groups: undefined]
            if (matches) {
                value = +matches[1];
                newUnit = matches[3] || '';
                /**
                 * 當無單位值且 defaultUnit === 'rem'，
                 * 將 pxValue / 16 轉為 remValue
                 */ if (!newUnit) {
                    if (defaultUnit === 'rem' || defaultUnit === 'em') {
                        value = value / (this.css.config.rootSize || 1);
                    }
                    newUnit = defaultUnit || '';
                }
                return {
                    value,
                    unit: newUnit,
                    type: 'number'
                };
            }
        }
        return {
            value: token,
            type: 'string'
        };
    }
}

export { Rule };
