import { Rule } from './rule.mjs';
import { config } from './config/index.mjs';
import { Layer } from './layer.mjs';
import { hexToRgb } from './utils/hex-to-rgb.mjs';
import { flattenObject } from './utils/flatten-object.mjs';
import extendConfig from './functions/extend-config.mjs';

class MasterCSS {
    customConfig;
    static config = config;
    rules;
    ruleBy;
    classesUsage;
    config;
    RegisteredSemanticRules;
    RegisteredRules;
    constructor(customConfig = config){
        this.customConfig = customConfig;
        this.rules = [];
        this.ruleBy = {};
        this.classesUsage = {};
        this.RegisteredSemanticRules = [];
        this.RegisteredRules = [];
        if (!customConfig?.override) {
            this.config = extendConfig(config, customConfig);
        } else {
            this.config = extendConfig(customConfig);
        }
        this.resolve();
        if (this.constructor === MasterCSS) {
            masterCSSs.push(this);
        }
    }
    resolve() {
        this.styles = {};
        this.stylesBy = {};
        this.selectors = {};
        this.variables = {};
        this.queries = {};
        this.animations = {};
        this.RegisteredRules.length = 0;
        this.RegisteredSemanticRules.length = 0;
        this.variablesNativeRules = {};
        this.hasKeyframesRule = false;
        const colorVariableNames = {
            current: undefined,
            currentColor: undefined,
            transparent: undefined
        };
        const { styles, selectors, variables, semantics, queries, rules, animations } = this.config;
        function escapeString(str) {
            return str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
        }
        if (selectors) {
            const resolvedSelectors = flattenObject(selectors);
            for(const eachSelectorName in resolvedSelectors){
                const eachResolvedSelectorText = resolvedSelectors[eachSelectorName];
                const regexp = new RegExp(escapeString(eachSelectorName) + '(?![a-z-])');
                for (const eachNewSelectorText of Array.isArray(eachResolvedSelectorText) ? eachResolvedSelectorText : [
                    eachResolvedSelectorText
                ]){
                    const vendor = eachNewSelectorText.match(/^::-[a-z]+-/m)?.[0] ?? '';
                    let selectorValues = this.selectors[vendor];
                    if (!selectorValues) {
                        selectorValues = this.selectors[vendor] = [];
                    }
                    let currentSelectValue = selectorValues.find(([_valueRegexp])=>_valueRegexp === regexp);
                    if (!currentSelectValue) {
                        currentSelectValue = [
                            regexp,
                            []
                        ];
                        selectorValues.push(currentSelectValue);
                    }
                    currentSelectValue[1].push(eachNewSelectorText);
                }
            }
        }
        if (variables) {
            const unexecutedAliasVariable = {};
            const parseVariable = (variable, name, mode)=>{
                if (!variable) return;
                const addVariable = (name, variableValue, replacedMode, alpha)=>{
                    if (variableValue === undefined) return;
                    if (variableValue.type === 'color') {
                        if (alpha) {
                            const slashIndex = variableValue.value.indexOf('/');
                            variableValue = {
                                ...variableValue,
                                value: slashIndex === -1 ? variableValue.value + ' / ' + (alpha.startsWith('0.') ? alpha.slice(1) : alpha) : variableValue.value.slice(0, slashIndex + 2) + String(+variableValue.value.slice(slashIndex + 2) * +alpha).slice(1)
                            };
                        }
                        colorVariableNames[name] = undefined;
                    }
                    const currentMode = replacedMode ?? mode;
                    if (currentMode !== undefined) {
                        if (Object.prototype.hasOwnProperty.call(this.variables, name)) {
                            const variable = this.variables[name];
                            if (currentMode) {
                                if (!variable.modes) {
                                    variable.modes = {};
                                }
                                variable.modes[currentMode] = variableValue;
                            } else {
                                variable.value = variableValue.value;
                                if (variableValue.type === 'color') {
                                    variable.space = variableValue.space;
                                }
                            }
                        } else {
                            if (currentMode) {
                                const newVariable = {
                                    type: variableValue.type,
                                    modes: {
                                        [currentMode]: variableValue
                                    }
                                };
                                if (variableValue.type === 'color') {
                                    newVariable.space = variableValue.space;
                                }
                                this.variables[name] = newVariable;
                            } else {
                                this.variables[name] = variableValue;
                            }
                        }
                    } else {
                        this.variables[name] = variableValue;
                    }
                };
                const type = typeof variable;
                if (type === 'object') {
                    if (Array.isArray(variable)) {
                        addVariable(name, {
                            type: 'string',
                            value: variable.join(',')
                        });
                    } else {
                        const keys = Object.keys(variable);
                        for (const eachKey of keys){
                            if (eachKey === '' || eachKey.startsWith('@')) {
                                parseVariable(variable[eachKey], name, eachKey || keys.some((eachKey)=>eachKey.startsWith('@')) ? eachKey.slice(1) : undefined);
                            } else {
                                parseVariable(variable[eachKey], name + '-' + eachKey, undefined);
                            }
                        }
                    }
                } else if (type === 'number') {
                    addVariable(name, {
                        type: 'number',
                        value: variable
                    });
                    addVariable('-' + name, {
                        type: 'number',
                        value: variable * -1
                    });
                } else if (type === 'string') {
                    const aliasResult = /^\$\((.*?)\)(?: ?\/ ?(.+?))?$/.exec(variable);
                    if (aliasResult) {
                        if (!Object.prototype.hasOwnProperty.call(unexecutedAliasVariable, name)) {
                            unexecutedAliasVariable[name] = {};
                        }
                        unexecutedAliasVariable[name][mode] = ()=>{
                            delete unexecutedAliasVariable[name][mode];
                            const [alias, aliasMode] = aliasResult[1].split('@');
                            if (alias) {
                                if (Object.prototype.hasOwnProperty.call(unexecutedAliasVariable, alias)) {
                                    for (const mode of Object.keys(unexecutedAliasVariable[alias])){
                                        unexecutedAliasVariable[alias][mode]?.();
                                    }
                                }
                                const aliasVariable = this.variables[alias];
                                if (aliasVariable) {
                                    if (aliasMode === undefined && aliasVariable.modes) {
                                        addVariable(name, {
                                            type: aliasVariable.type,
                                            value: aliasVariable.value,
                                            space: aliasVariable.space
                                        }, '', aliasResult[2]);
                                        for(const mode in aliasVariable.modes){
                                            addVariable(name, aliasVariable.modes[mode], mode, aliasResult[2]);
                                        }
                                    } else {
                                        const variable = aliasMode !== undefined ? aliasVariable.modes?.[aliasMode] : aliasVariable;
                                        if (variable) {
                                            const newVariable = {
                                                type: variable.type,
                                                value: variable.value
                                            };
                                            if (variable.type === 'color') {
                                                newVariable.space = variable.space;
                                            }
                                            addVariable(name, newVariable, undefined, aliasResult[2]);
                                        }
                                    }
                                }
                            }
                        };
                    } else {
                        const hexColorResult = /^#([A-Fa-f0-9]{3,4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/.exec(variable);
                        if (hexColorResult) {
                            const [r, g, b, a] = hexToRgb(hexColorResult[1]);
                            addVariable(name, {
                                type: 'color',
                                value: `${r} ${g} ${b}${a === 1 ? '' : ' / ' + a}`,
                                space: 'rgb'
                            });
                        } else {
                            const rgbFunctionResult = /^rgb\( *([0-9]{1,3})(?: *, *| +)([0-9]{1,3})(?: *, *| +)([0-9]{1,3}) *(?:(?:,|\/) *(.*?) *)?\)$/.exec(variable);
                            if (rgbFunctionResult) {
                                addVariable(name, {
                                    type: 'color',
                                    value: rgbFunctionResult[1] + ' ' + rgbFunctionResult[2] + ' ' + rgbFunctionResult[3] + (rgbFunctionResult[4] ? ' / ' + (rgbFunctionResult[4].startsWith('0.') ? rgbFunctionResult[4].slice(1) : rgbFunctionResult[4]) : ''),
                                    space: 'rgb'
                                });
                            } else {
                                const hslFunctionResult = /^hsl\((.*?)\)$/.exec(variable);
                                if (hslFunctionResult) {
                                    addVariable(name, {
                                        type: 'color',
                                        value: hslFunctionResult[1],
                                        space: 'hsl'
                                    });
                                } else {
                                    addVariable(name, {
                                        type: 'string',
                                        value: variable
                                    });
                                }
                            }
                        }
                    }
                }
            };
            for(const parnetKey in variables){
                parseVariable(variables[parnetKey], parnetKey);
            }
            for (const name of Object.keys(unexecutedAliasVariable)){
                for (const mode of Object.keys(unexecutedAliasVariable[name])){
                    unexecutedAliasVariable[name][mode]?.();
                }
            }
        }
        if (queries) {
            this.queries = flattenObject(queries);
        }
        if (animations) {
            for(const animationName in animations){
                const eachAnimation = this.animations[animationName] = {};
                const eachKeyframes = animations[animationName];
                for(const eachKeyframeValue in eachKeyframes){
                    const newValueByPropertyName = eachAnimation[eachKeyframeValue] = {};
                    const eachKeyframeDeclarations = eachKeyframes[eachKeyframeValue];
                    for(const propertyName in eachKeyframeDeclarations){
                        newValueByPropertyName[propertyName] = eachKeyframeDeclarations[propertyName];
                    }
                }
            }
        }
        const flattedStyles = styles ? flattenObject(styles) : {};
        const semanticNames = Object.keys(flattedStyles);
        const handleSemanticName = (semanticName)=>{
            if (Object.prototype.hasOwnProperty.call(this.styles, semanticName)) return;
            const currentClass = this.styles[semanticName] = [];
            const className = flattedStyles[semanticName];
            if (!className) return;
            const classNames = className.replace(/(?:\n(?:\s*))+/g, ' ').trim().split(' ');
            for (const eachClassName of classNames){
                const handle = (className)=>{
                    if (Object.prototype.hasOwnProperty.call(this.stylesBy, className)) {
                        const currentRelation = this.stylesBy[className];
                        if (!currentRelation.includes(semanticName)) {
                            currentRelation.push(semanticName);
                        }
                    } else {
                        this.stylesBy[className] = [
                            semanticName
                        ];
                    }
                    if (!currentClass.includes(className)) {
                        currentClass.push(className);
                    }
                };
                if (semanticNames.includes(eachClassName)) {
                    handleSemanticName(eachClassName);
                    for (const parentClassName of this.styles[eachClassName]){
                        handle(parentClassName);
                    }
                } else {
                    handle(eachClassName);
                }
            }
        };
        for (const eachSemanticName of semanticNames){
            handleSemanticName(eachSemanticName);
        }
        if (semantics) {
            Object.entries(semantics).sort((a, b)=>a[0].localeCompare(b[0])).forEach(([id, declarations], index)=>{
                this.RegisteredSemanticRules.push({
                    id: '.' + id,
                    match: new RegExp('^' + escapeString(id) + '(?=!|\\*|>|\\+|~|:|\\[|@|_|\\.|$)', 'm'),
                    order: index,
                    definition: {
                        declarations,
                        layer: Layer.Semantic
                    }
                });
            });
        }
        if (rules) {
            const rulesEntries = Object.entries(rules).sort((a, b)=>{
                if (a[1].layer !== b[1].layer) {
                    return (b[1].layer || 0) - (a[1].layer || 0);
                }
                return b[0].localeCompare(a[0]);
            });
            const rulesEntriesLength = rulesEntries.length;
            const colorNames = Object.keys(colorVariableNames);
            rulesEntries.forEach(([id, eachRuleDefinition], index)=>{
                const eachRegisteredRule = {
                    id,
                    variables: {},
                    order: this.RegisteredSemanticRules.length + rulesEntriesLength - 1 - index,
                    definition: eachRuleDefinition
                };
                this.RegisteredRules.push(eachRegisteredRule);
                const match = eachRuleDefinition.match;
                // todo: 不可使用 startsWith 判斷，應改為更精準的從 config.variables 取得目標變數群組，但 config.variables 中的值還沒被 resolve 像是 Array
                const addResolvedVariables = (prefix)=>{
                    for(const eachVariableName in this.variables){
                        if (eachVariableName.startsWith(prefix + '-') || eachVariableName.startsWith('-' + prefix + '-')) {
                            const simplifiedName = eachVariableName.slice(prefix.length + (prefix.startsWith('-') ? 0 : 1));
                            eachRegisteredRule.variables[simplifiedName] = {
                                ...this.variables[eachVariableName],
                                name: eachVariableName
                            };
                        }
                    }
                };
                // 1. custom `config.rules[id].variables`
                if (eachRuleDefinition.variables) {
                    for (const eachVariableGroup of eachRuleDefinition.variables){
                        addResolvedVariables(eachVariableGroup);
                    }
                }
                // 2. custom `config.variables`
                addResolvedVariables(id);
                if (match) {
                    if (Array.isArray(match)) {
                        const [key, values = []] = match;
                        const valueMatches = [];
                        if (values.length) {
                            valueMatches.push(`(?:${values.join('|')})(?![a-zA-Z0-9-])`);
                        }
                        if (Object.keys(eachRegisteredRule.variables).length) {
                            valueMatches.push(`(?:${Object.keys(eachRegisteredRule.variables).join('|')})(?![a-zA-Z0-9-])`);
                        }
                        if (eachRuleDefinition.colored) {
                            valueMatches.push('#', '(?:color|color-contrast|color-mix|hwb|lab|lch|oklab|oklch|rgb|rgba|hsl|hsla)\\(.*\\)', `(?:${colorNames.join('|')})(?![a-zA-Z0-9-])`);
                        }
                        if (eachRuleDefinition.numeric) {
                            valueMatches.push('[\\d\\.]', '(?:max|min|calc|clamp)\\(.*\\)');
                        }
                        if (valueMatches.length) eachRegisteredRule.match = new RegExp(`^${key}:(?:${valueMatches.join('|')})[^|]*?(?:@|$)`);
                    } else {
                        eachRegisteredRule.match = match;
                    }
                }
            });
        }
    }
    /**
     * Match check if Master CSS syntax
     * @param className
     * @returns css text
     */ match(className) {
        // 1. rules
        for (const eachRegisteredRule of this.RegisteredRules){
            if (eachRegisteredRule.match?.test(className) || (eachRegisteredRule.definition.layer === Layer.Native || eachRegisteredRule.definition.layer === Layer.NativeShorthand || eachRegisteredRule.definition.layer === Layer.CoreNative || eachRegisteredRule.definition.layer === Layer.CoreNativeShorthand) && className.startsWith(eachRegisteredRule.id + ':')) {
                return eachRegisteredRule;
            }
        }
        // 2. semantic rules
        for (const EachRegisteredSemanticRule of this.RegisteredSemanticRules){
            if (EachRegisteredSemanticRule.match?.test(className)) {
                return EachRegisteredSemanticRule;
            }
        }
    }
    /**
     * Generate rules from class name
     * @param className
     * @returns Rule[]
     */ generate(className) {
        return (Object.prototype.hasOwnProperty.call(this.styles, className) ? this.styles[className].map((eachSyntax)=>this.create(eachSyntax)) : [
            this.create(className)
        ]).filter((eachRule)=>eachRule && eachRule.text);
    }
    /**
     * Create rule from given syntax
     * @param syntax
     * @returns Rule
     */ create(syntax) {
        if (Object.prototype.hasOwnProperty.call(this.ruleBy, syntax)) return this.ruleBy[syntax];
        const RegistedRule = this.match(syntax);
        if (RegistedRule) {
            return new Rule(syntax, RegistedRule, this);
        }
    }
    /**
     * 根據蒐集到的所有 DOM class 重新 create
     */ refresh(customConfig = this.customConfig) {
        if (!customConfig?.override) {
            // @ts-ignore
            this.config = extendConfig(config, customConfig);
        } else {
            // @ts-ignore
            this.config = extendConfig(customConfig);
        }
        this.resolve();
        this.rules.length = 0;
        // @ts-ignore
        this.ruleBy = {};
        /**
         * 拿當前所有的 classNames 按照最新的 colors, config.rules 匹配並生成新的 style
         * 所以 refresh 過後 rules 可能會變多也可能會變少
         */ for(const name in this.classesUsage){
            this.add(name);
        }
        return this;
    }
    reset() {
        // @ts-ignore
        this.ruleBy = {};
        // @ts-ignore
        this.classesUsage = {};
        this.rules.length = 0;
        this.hasKeyframesRule = false;
        this.variablesNativeRules = {};
        for(const keyframeName in this.animations){
            const animation = this.animations[keyframeName];
            animation.usage = 0;
            animation.native = undefined;
        }
        for(const variableName in this.variables){
            const variable = this.variables[variableName];
            variable.usage = 0;
        }
        return this;
    }
    destroy() {
        this.reset();
        masterCSSs.splice(masterCSSs.indexOf(this), 1);
        return this;
    }
    add(...classNames) {
        for (const className of classNames){
            const rules = this.generate(className);
            if (rules.length) {
                for (const rule of rules){
                    this.insert(rule);
                }
            }
        }
        return this;
    }
    delete(...classNames) {
        /**
         * class name 從 DOM tree 中被移除，
         * 匹配並刪除對應的 rule
         */ const sheet = this.style?.sheet;
        const deleteRule = (name)=>{
            const rule = this.ruleBy[name];
            if (!rule || Object.prototype.hasOwnProperty.call(this.stylesBy, name) && this.stylesBy[name].some((eachClassName)=>Object.prototype.hasOwnProperty.call(this.classesUsage, eachClassName))) return;
            if (sheet && rule.natives.length) {
                const firstNative = rule.natives[0];
                for(let index = 0; index < sheet.cssRules.length; index++){
                    const eachCSSRule = sheet.cssRules[index];
                    if (eachCSSRule === firstNative.cssRule) {
                        for(let i = 0; i < rule.natives.length; i++){
                            sheet.deleteRule(index);
                        }
                        break;
                    }
                }
            }
            this.rules.splice(this.rules.indexOf(rule), 1);
            delete this.ruleBy[name];
            // variables
            if (rule.variableNames) {
                for (const eachVariableName of rule.variableNames){
                    const variable = this.variables[eachVariableName];
                    if (!variable.usage) variable.usage = 0;
                    if (!--variable.usage) {
                        const removeProperty = (mode)=>{
                            const nativeRule = this.variablesNativeRules[mode];
                            nativeRule.cssRule.style.removeProperty('--' + eachVariableName);
                            if (!nativeRule.cssRule.style.length) {
                                const variablesRule = this.rules[0];
                                const index = variablesRule.natives.indexOf(nativeRule);
                                sheet?.deleteRule(index);
                                variablesRule.natives.splice(index, 1);
                                delete this.variablesNativeRules[mode];
                                if (!variablesRule.natives.length) {
                                    this.rules.splice(0, 1);
                                    this.variablesNativeRules = {};
                                }
                            }
                        };
                        if (variable.value) {
                            removeProperty('');
                        }
                        if (variable.modes) {
                            for(const mode in variable.modes){
                                removeProperty(mode);
                            }
                        }
                    }
                }
            }
            // animations
            if (rule.animationNames) {
                const keyframeRulesIndex = Object.keys(this.variablesNativeRules).length ? 1 : 0;
                const keyframeRule = this.rules[keyframeRulesIndex];
                for (const eachKeyframeName of rule.animationNames){
                    const keyframe = this.animations[eachKeyframeName];
                    if (!keyframe.usage) keyframe.usage = 0;
                    if (!--keyframe.usage && keyframe.native) {
                        const nativeIndex = keyframeRule.natives.indexOf(keyframe.native);
                        this.style.sheet?.deleteRule(Object.keys(this.variablesNativeRules).length + nativeIndex);
                        keyframeRule.natives.splice(nativeIndex, 1);
                        keyframe.native = undefined;
                    }
                }
                if (!keyframeRule.natives.length) {
                    this.rules.splice(keyframeRulesIndex, 1);
                    this.hasKeyframesRule = false;
                }
            }
            rule.definition.delete?.call(rule, name);
        };
        for (const className of classNames){
            if (Object.prototype.hasOwnProperty.call(this.styles, className)) {
                for (const eachClassName of this.styles[className]){
                    if (!Object.prototype.hasOwnProperty.call(this.classesUsage, eachClassName)) {
                        deleteRule(eachClassName);
                    }
                }
                delete this.ruleBy[className];
            } else {
                deleteRule(className);
            }
        }
    }
    /**
    * 加工插入規則
    * 1. where
    * 2. normal
    * 3. where selectors
    * 4. normal selectors
    * 5. media where
    * 6. media normal
    * 7. media where selectors
    * 8. media selectors
    * 9. media width where
    * 10. media width
    * 11. media width where selectors
    * 12. media width selectors
    */ insert(rule) {
        if (this.ruleBy[rule.className]) return;
        let index;
        /**
         * 必須按斷點值遞增，並透過索引插入，
         * 以實現響應式先後套用的規則
         * @example <1  <2  <3  ALL  >=1 >=2 >=3
         * @description
         */ const endIndex = this.rules.length - 1;
        const { at, atToken, order, priority, hasWhere, className } = rule;
        const findIndex = (startIndex, stopCheck, matchCheck)=>{
            let i = startIndex;
            for(; i <= endIndex; i++){
                const eachRule = this.rules[i];
                if (stopCheck?.(eachRule)) return matchCheck ? -1 : i - 1;
                if (matchCheck?.(eachRule)) return i;
            }
            return matchCheck ? -1 : i - 1;
        };
        let matchStartIndex;
        let matchEndIndex;
        if (atToken) {
            const mediaStartIndex = this.rules.findIndex((eachRule)=>eachRule.at?.media);
            if (mediaStartIndex === -1) {
                index = endIndex + 1;
            } else {
                const maxWidthFeature = at.media?.find(({ name })=>name === 'max-width');
                const minWidthFeature = at.media?.find(({ name })=>name === 'min-width');
                if (maxWidthFeature || minWidthFeature) {
                    const mediaWidthStartIndex = this.rules.findIndex((eachRule)=>eachRule.at?.media?.find(({ name })=>name === 'max-width' || name === 'min-width'));
                    if (mediaWidthStartIndex === -1) {
                        index = endIndex + 1;
                    } else {
                        if (maxWidthFeature && minWidthFeature) {
                            /**
                             * 範圍越小 ( 越限定 越侷限 ) 越優先，
                             * 按照範圍 max-width - min-width 遞減排序
                             * find 第一個所遇到同樣 feature 且範圍值比自己大的 rule，
                             * 並插入在該 rule 之後，讓自己優先被套用
                             */ if (priority === -1) {
                                matchStartIndex = findIndex(mediaWidthStartIndex, (eachRule)=>eachRule.priority !== -1, (eachRule)=>eachRule.at.media?.find(({ name })=>name === 'max-width') && eachRule.at.media?.find(({ name })=>name === 'min-width'));
                                matchEndIndex = findIndex(mediaWidthStartIndex, (eachRule)=>eachRule.priority !== -1);
                            } else {
                                matchStartIndex = findIndex(mediaWidthStartIndex, undefined, (eachRule)=>eachRule.at.media?.find(({ name })=>name === 'max-width') && eachRule.at.media?.find(({ name })=>name === 'min-width') && eachRule.priority !== -1);
                                matchEndIndex = endIndex;
                            }
                            if (matchStartIndex !== -1) {
                                const range = maxWidthFeature.value - minWidthFeature.value;
                                let i = matchEndIndex;
                                const endI = matchStartIndex;
                                matchStartIndex = undefined;
                                for(; i >= endI; i--){
                                    const eachRule = this.rules[i];
                                    const eachMaxWidthFeature = eachRule.at.media?.find(({ name })=>name === 'max-width');
                                    const eachMinWidthFeature = eachRule.at.media?.find(({ name })=>name === 'min-width');
                                    const eachRange = eachMaxWidthFeature.value - eachMinWidthFeature.value;
                                    if (eachRange < range) {
                                        matchEndIndex = i - 1;
                                    } else if (eachRange === range) {
                                        matchStartIndex = i;
                                    } else {
                                        break;
                                    }
                                }
                            }
                            if (matchStartIndex !== -1 && matchStartIndex !== undefined) {
                                const range = maxWidthFeature.value - minWidthFeature.value;
                                for(let i = matchEndIndex; i >= matchStartIndex; i--){
                                    const eachRule = this.rules[i];
                                    const eachMaxWidthFeature = eachRule.at.media?.find(({ name })=>name === 'max-width');
                                    const eachMinWidthFeature = eachRule.at.media?.find(({ name })=>name === 'min-width');
                                    const eachRange = eachMaxWidthFeature.value - eachMinWidthFeature.value;
                                    if (eachRange < range) {
                                        matchEndIndex = i - 1;
                                    } else if (eachRange > range) {
                                        matchStartIndex = i + 1;
                                        break;
                                    }
                                }
                            }
                        } else if (minWidthFeature) {
                            /**
                             * find 第一個所遇到同樣 feature 且值比自己大的 rule，
                             * 並插入在該 rule 之後，讓自己優先被套用
                             */ if (priority === -1) {
                                matchStartIndex = findIndex(mediaWidthStartIndex, (eachRule)=>eachRule.at.media?.find(({ name })=>name === 'max-width') && eachRule.at.media?.find(({ name })=>name === 'min-width') || eachRule.priority !== -1, (eachRule)=>!eachRule.at.media?.find(({ name })=>name === 'max-width') && eachRule.at.media?.find(({ name })=>name === 'min-width'));
                                matchEndIndex = findIndex(mediaWidthStartIndex, (eachRule)=>eachRule.at.media?.find(({ name })=>name === 'max-width') && eachRule.at.media?.find(({ name })=>name === 'min-width') || eachRule.priority !== -1);
                            } else {
                                matchStartIndex = findIndex(mediaWidthStartIndex, (eachRule)=>eachRule.at.media?.find(({ name })=>name === 'max-width') && eachRule.at.media?.find(({ name })=>name === 'min-width') && eachRule.priority !== -1, (eachRule)=>!eachRule.at.media?.find(({ name })=>name === 'max-width') && eachRule.at.media?.find(({ name })=>name === 'min-width') && eachRule.priority !== -1);
                                matchEndIndex = findIndex(mediaWidthStartIndex, (eachRule)=>eachRule.at.media?.find(({ name })=>name === 'max-width') && eachRule.at.media?.find(({ name })=>name === 'min-width') && eachRule.priority !== -1);
                            }
                            if (matchStartIndex !== -1) {
                                for(let i = matchEndIndex; i >= matchStartIndex; i--){
                                    const value = (this.rules[i].at.media?.find(({ name })=>name === 'min-width')).value;
                                    if (value > minWidthFeature.value) {
                                        matchEndIndex = i - 1;
                                    } else if (value < minWidthFeature.value) {
                                        matchStartIndex = i + 1;
                                        break;
                                    }
                                }
                            }
                        } else {
                            /**
                             * find 第一個所遇到同樣 feature 且值比自己大的 rule，
                             * 並插入在該 rule 之後，讓自己優先被套用
                             */ if (priority === -1) {
                                matchStartIndex = findIndex(mediaWidthStartIndex, (eachRule)=>eachRule.at.media?.find(({ name })=>name === 'min-width') || eachRule.priority !== -1, (eachRule)=>eachRule.at.media?.find(({ name })=>name === 'max-width'));
                                matchEndIndex = findIndex(mediaWidthStartIndex, (eachRule)=>eachRule.at.media?.find(({ name })=>name === 'min-width') || eachRule.priority !== -1);
                            } else {
                                matchStartIndex = findIndex(mediaWidthStartIndex, (eachRule)=>eachRule.at.media?.find(({ name })=>name === 'min-width') && eachRule.priority !== -1, (eachRule)=>eachRule.at.media?.find(({ name })=>name === 'max-width') && eachRule.priority !== -1);
                                matchEndIndex = findIndex(mediaWidthStartIndex, (eachRule)=>eachRule.at.media?.find(({ name })=>name === 'min-width') && eachRule.priority !== -1);
                            }
                            if (matchStartIndex !== -1) {
                                for(let i = matchEndIndex; i >= matchStartIndex; i--){
                                    const value = (this.rules[i].at.media?.find(({ name })=>name === 'max-width')).value;
                                    if (value < maxWidthFeature.value) {
                                        matchEndIndex = i - 1;
                                    } else if (value > maxWidthFeature.value) {
                                        matchStartIndex = i + 1;
                                        break;
                                    }
                                }
                            }
                        }
                    }
                } else {
                    if (priority === -1) {
                        matchStartIndex = mediaStartIndex;
                        matchEndIndex = findIndex(mediaStartIndex, (eachRule)=>eachRule.at.media?.find(({ name })=>name === 'max-width' || name === 'min-width') || eachRule.priority !== -1);
                    } else {
                        matchStartIndex = findIndex(mediaStartIndex, (eachRule)=>eachRule.at.media?.find(({ name })=>name === 'max-width' || name === 'min-width'), (eachRule)=>eachRule.priority !== -1);
                        matchEndIndex = findIndex(mediaStartIndex, (eachRule)=>eachRule.at.media?.find(({ name })=>name === 'max-width' || name === 'min-width'));
                    }
                }
            }
        } else {
            const findStartIndex = Object.keys(this.variablesNativeRules).length ? this.hasKeyframesRule ? 2 : 1 : this.hasKeyframesRule ? 1 : 0;
            if (priority === -1) {
                matchStartIndex = findStartIndex;
                matchEndIndex = findIndex(findStartIndex, (eachRule)=>eachRule.atToken || eachRule.priority !== -1);
            } else {
                matchStartIndex = findIndex(findStartIndex, (eachRule)=>eachRule.atToken, (eachRule)=>eachRule.priority !== -1);
                matchEndIndex = findIndex(findStartIndex, (eachRule)=>eachRule.atToken);
            }
        }
        if (index === undefined && matchEndIndex !== undefined && matchStartIndex !== undefined) {
            if (matchStartIndex === -1) {
                index = matchEndIndex + 1;
            } else {
                if (priority === -1) {
                    for(let i = matchStartIndex; i <= matchEndIndex; i++){
                        const currentRule = this.rules[i];
                        if (!hasWhere && currentRule.hasWhere) continue;
                        if (hasWhere && !currentRule.hasWhere || currentRule.order >= order) {
                            index = i;
                            break;
                        }
                    }
                } else {
                    for(let i = matchStartIndex; i <= matchEndIndex; i++){
                        const currentRule = this.rules[i];
                        if (!hasWhere && currentRule.hasWhere) continue;
                        if (hasWhere && !currentRule.hasWhere) {
                            index = i;
                            break;
                        }
                        if (currentRule.priority < priority) {
                            index = i;
                            break;
                        } else if (currentRule.priority === priority) {
                            if (currentRule.order >= order) {
                                index = i;
                                break;
                            }
                        } else {
                            index = i + 1;
                        }
                    }
                }
                if (index === undefined) {
                    index = matchEndIndex + 1;
                }
            }
        }
        this.rules.splice(index, 0, rule);
        this.ruleBy[className] = rule;
        // 只在瀏覽器端運行
        if (this.style) {
            const sheet = this.style.sheet;
            let cssRuleIndex = 0;
            const getCssRuleIndex = (index)=>{
                const previousRule = this.rules[index];
                if (previousRule) {
                    if (!previousRule.natives.length) return getCssRuleIndex(index - 1);
                    const lastNative = previousRule.natives[previousRule.natives.length - 1];
                    const lastNativeCssRule = lastNative.cssRule?.parentRule ?? lastNative.cssRule;
                    if (sheet) for(let i = 0; i < sheet.cssRules.length; i++){
                        if (sheet.cssRules[i] === lastNativeCssRule) {
                            cssRuleIndex = i + 1;
                            break;
                        }
                    }
                }
            };
            getCssRuleIndex(index - 1);
            for(let i = 0; i < rule.natives.length;){
                try {
                    const native = rule.natives[i];
                    sheet?.insertRule(native.text, cssRuleIndex);
                    native.cssRule = sheet?.cssRules[cssRuleIndex++];
                    i++;
                } catch (error) {
                    console.error(error);
                    rule.natives.splice(i, 1);
                }
            }
        }
        // variables
        this.handleRuleWithVariableNames(rule);
        // animations
        this.handleRuleWithAnimationNames(rule);
        rule.definition.insert?.call(rule);
    }
    get text() {
        return this.rules.map((eachRule)=>eachRule.text).join('');
    }
    handleRuleWithAnimationNames(rule, initializing = false) {
        if (rule.animationNames) {
            const sheet = this.style?.sheet;
            for (const eachKeyframeName of rule.animationNames){
                const animation = this.animations[eachKeyframeName];
                if (animation.usage) {
                    animation.usage++;
                } else {
                    const nativeRule = {
                        text: `@keyframes ${eachKeyframeName}{` + Object.entries(animation).filter(([key])=>key !== 'usage' && key !== 'native').map(([key, variables])=>`${key}{${Object.entries(variables).map(([name, value])=>name + ':' + value).join(';')}}`).join('') + '}'
                    };
                    const keyframeRulesIndex = Object.keys(this.variablesNativeRules).length ? 1 : 0;
                    let keyframeRule;
                    if (this.hasKeyframesRule) {
                        (keyframeRule = this.rules[keyframeRulesIndex]).natives.push(nativeRule);
                    } else {
                        this.rules.splice(keyframeRulesIndex, 0, keyframeRule = {
                            natives: [
                                nativeRule
                            ],
                            get text () {
                                return this.natives.map((eachNative)=>eachNative.text).join('');
                            }
                        });
                        this.hasKeyframesRule = true;
                    }
                    if (sheet) {
                        let cssRule;
                        if (initializing) {
                            for(let i = 0; i < sheet.cssRules.length; i++){
                                const eachCSSRule = sheet.cssRules[i];
                                if (eachCSSRule.constructor.name === 'CSSKeyframesRule' && eachCSSRule.name === eachKeyframeName) {
                                    cssRule = eachCSSRule;
                                    break;
                                }
                            }
                        }
                        if (cssRule) {
                            nativeRule.cssRule = cssRule;
                        } else {
                            const cssRuleIndex = Object.keys(this.variablesNativeRules).length + keyframeRule.natives.length - 1;
                            sheet.insertRule(nativeRule.text, cssRuleIndex);
                            nativeRule.cssRule = sheet.cssRules[cssRuleIndex];
                        }
                    }
                    animation.usage = 1;
                    animation.native = nativeRule;
                }
            }
        }
    }
    handleRuleWithVariableNames(rule, initializing = false) {
        if (rule.variableNames) {
            const sheet = this.style?.sheet;
            for (const eachVariableName of rule.variableNames){
                const variable = this.variables[eachVariableName];
                if (variable.usage) {
                    variable.usage++;
                } else {
                    const addProperty = (mode, variableValue)=>{
                        let nativeRule = this.variablesNativeRules[mode];
                        if (!nativeRule) {
                            let cssRule;
                            let mediaConditionText;
                            let selectorText;
                            if (mode) {
                                switch(this.config.modeDriver){
                                    case 'media':
                                        mediaConditionText = `@media(prefers-color-scheme:${mode})`;
                                        selectorText = ':root';
                                        break;
                                    case 'host':
                                        selectorText = `:host(.${mode})`;
                                        break;
                                    default:
                                        selectorText = `.${mode}`;
                                        break;
                                }
                            } else {
                                selectorText = ':root';
                            }
                            if (sheet) {
                                const newCSSRuleIndex = Object.keys(this.variablesNativeRules).length ? this.rules[0].natives.length : 0;
                                sheet.insertRule((mediaConditionText ? mediaConditionText + '{' : '') + selectorText + '{}' + (mediaConditionText ? mediaConditionText + '}' : ''), newCSSRuleIndex);
                                cssRule = mediaConditionText ? sheet.cssRules[newCSSRuleIndex].cssRules[0] : sheet.cssRules[newCSSRuleIndex];
                            } else {
                                const styleMap = new Map();
                                const style = Object.defineProperties({}, {
                                    getPropertyValue: {
                                        value: (property)=>styleMap.get(property)
                                    },
                                    removeProperty: {
                                        value: (property)=>{
                                            styleMap.delete(property);
                                            for(let i = 0; i < style.length; i++){
                                                if (style[i] === property) {
                                                    delete style[i];
                                                }
                                            }
                                        }
                                    },
                                    setProperty: {
                                        value: (property, value)=>{
                                            style[style.length] = property;
                                            styleMap.set(property, value);
                                        }
                                    },
                                    length: {
                                        get () {
                                            return Object.keys(style).length;
                                        }
                                    }
                                });
                                cssRule = {
                                    selectorText,
                                    style,
                                    styleMap
                                };
                                if (mediaConditionText) {
                                    // @ts-ignore
                                    cssRule.parentRule = {
                                        conditionText: mediaConditionText
                                    };
                                }
                            }
                            nativeRule = this.pushVariableNativeRule(mode, cssRule);
                        }
                        const propertyName = '--' + eachVariableName;
                        if (!initializing || !nativeRule.cssRule.style.getPropertyValue(propertyName)) {
                            nativeRule.cssRule.style.setProperty(propertyName, String(variableValue.value));
                        }
                    };
                    if (variable.value) {
                        addProperty('', variable);
                    }
                    if (variable.modes) {
                        for(const mode in variable.modes){
                            addProperty(mode, variable.modes[mode]);
                        }
                    }
                    variable.usage = 1;
                }
            }
        }
    }
    pushVariableNativeRule(mode, variableCSSRule) {
        if (!Object.keys(this.variablesNativeRules).length) {
            this.variablesNativeRules = {};
            const newRule = {
                natives: [],
                get text () {
                    return this.natives.map((eachNative)=>eachNative.text).join('');
                }
            };
            this.rules.splice(0, 0, newRule);
        }
        let prefix = '';
        let suffix = '}';
        if (variableCSSRule.parentRule) {
            prefix += variableCSSRule.parentRule.conditionText.replace(/ /g, '') + '{';
            suffix += '}';
        }
        prefix += variableCSSRule.selectorText + '{';
        const nativeRule = {
            cssRule: variableCSSRule,
            get text () {
                const properties = [];
                for(let i = 0; i < variableCSSRule.style.length; i++){
                    const property = variableCSSRule.style[i];
                    properties.push(property + ':' + variableCSSRule.style.getPropertyValue(property));
                }
                return prefix + properties.join(';') + suffix;
            }
        };
        this.rules[0].natives.push(this.variablesNativeRules[mode] = nativeRule);
        return nativeRule;
    }
}
const masterCSSs = [];
(()=>{
    globalThis.MasterCSS = MasterCSS;
    globalThis.masterCSSs = masterCSSs;
})();

export { MasterCSS as default, masterCSSs };
