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 './utils/extend-config.mjs';

class MasterCSS {
    customConfig;
    static config = config;
    syntaxes;
    ruleBy;
    classesUsage;
    config;
    Rules;
    constructor(customConfig){
        this.customConfig = customConfig;
        this.syntaxes = [];
        this.ruleBy = {};
        this.classesUsage = {};
        this.Rules = [];
        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.at = {};
        this.animations = {};
        this.Rules.length = 0;
        this.variablesNativeRules = {};
        this.hasKeyframesRule = false;
        const colorVariableNames = {
            current: undefined,
            currentColor: undefined,
            transparent: undefined
        };
        const { styles, selectors, variables, utilities, at, syntaxes, 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 resolveVariable = (variableDefinition, name, mode)=>{
                if (!variableDefinition === undefined || variableDefinition === null) return;
                const addVariable = (name, variable, replacedMode, alpha)=>{
                    if (variable === undefined) return;
                    const flatName = name.join('-');
                    const groups = name.slice(0, -1).filter(Boolean);
                    const key = (name[0] === '' ? '-' : '') + name[name.length - 1];
                    variable.key = key;
                    variable.name = flatName;
                    if (groups.length) variable.group = groups.join('.');
                    if (variable.type === 'color') {
                        if (alpha) {
                            const slashIndex = variable.value.indexOf('/');
                            variable = {
                                ...variable,
                                value: slashIndex === -1 ? variable.value + ' / ' + (alpha.startsWith('0.') ? alpha.slice(1) : alpha) : variable.value.slice(0, slashIndex + 2) + String(+variable.value.slice(slashIndex + 2) * +alpha).slice(1)
                            };
                        }
                        colorVariableNames[flatName] = undefined;
                    }
                    /**
                     * resolve `variables.screen-*` to `at.*`
                     */ if (variable.name.startsWith('screen-') && variable.type === 'number') {
                        this.at[variable.name.slice(7)] = variable.value;
                    }
                    const currentMode = replacedMode ?? mode;
                    if (currentMode !== undefined) {
                        if (Object.prototype.hasOwnProperty.call(this.variables, flatName)) {
                            const foundVariable = this.variables[flatName];
                            if (currentMode) {
                                if (!foundVariable.modes) {
                                    foundVariable.modes = {};
                                }
                                foundVariable.modes[currentMode] = variable;
                            } else {
                                foundVariable.value = variable.value;
                                if (variable.type === 'color') {
                                    foundVariable.space = variable.space;
                                }
                            }
                        } else {
                            if (currentMode) {
                                const newVariable = {
                                    key: variable.key,
                                    name: variable.name,
                                    group: variable.group,
                                    type: variable.type,
                                    modes: {
                                        [currentMode]: variable
                                    }
                                };
                                if (variable.type === 'color') {
                                    newVariable.space = variable.space;
                                }
                                this.variables[flatName] = newVariable;
                            } else {
                                this.variables[flatName] = variable;
                            }
                        }
                    } else {
                        this.variables[flatName] = variable;
                    }
                };
                if (typeof variableDefinition === 'object') {
                    if (Array.isArray(variableDefinition)) {
                        addVariable(name, {
                            type: 'string',
                            value: variableDefinition.join(',')
                        });
                    } else {
                        const keys = Object.keys(variableDefinition);
                        for (const eachKey of keys){
                            if (eachKey === '' || eachKey.startsWith('@')) {
                                resolveVariable(variableDefinition[eachKey], name, eachKey || keys.some((eachKey)=>eachKey.startsWith('@')) ? eachKey.slice(1) : undefined);
                            } else {
                                resolveVariable(variableDefinition[eachKey], [
                                    ...name,
                                    eachKey
                                ]);
                            }
                        }
                    }
                } else if (typeof variableDefinition === 'number') {
                    addVariable(name, {
                        type: 'number',
                        value: variableDefinition
                    });
                    addVariable([
                        '',
                        ...name
                    ], {
                        type: 'number',
                        value: variableDefinition * -1
                    });
                } else if (typeof variableDefinition === 'string') {
                    const aliasResult = /^\$\((.*?)\)(?: ?\/ ?(.+?))?$/.exec(variableDefinition);
                    const flatName = name.join('-');
                    if (aliasResult) {
                        if (!Object.prototype.hasOwnProperty.call(unexecutedAliasVariable, flatName)) {
                            unexecutedAliasVariable[flatName] = {};
                        }
                        unexecutedAliasVariable[flatName][mode] = ()=>{
                            delete unexecutedAliasVariable[flatName][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(variableDefinition);
                        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(variableDefinition);
                            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(variableDefinition);
                                if (hslFunctionResult) {
                                    addVariable(name, {
                                        type: 'color',
                                        value: hslFunctionResult[1],
                                        space: 'hsl'
                                    });
                                } else {
                                    addVariable(name, {
                                        type: 'string',
                                        value: variableDefinition
                                    });
                                }
                            }
                        }
                    }
                }
            };
            for(const parnetKey in variables){
                resolveVariable(variables[parnetKey], [
                    parnetKey
                ]);
            }
            // todo: address to the target variable
            for (const name of Object.keys(unexecutedAliasVariable)){
                for (const mode of Object.keys(unexecutedAliasVariable[name])){
                    unexecutedAliasVariable[name][mode]?.();
                }
            }
        }
        if (at) {
            Object.assign(this.at, flattenObject(at));
        }
        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 utilityNames = Object.keys(flattedStyles);
        const handleUtilityName = (utilityName)=>{
            if (Object.prototype.hasOwnProperty.call(this.styles, utilityName)) return;
            const currentClass = this.styles[utilityName] = [];
            const className = flattedStyles[utilityName];
            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(utilityName)) {
                            currentRelation.push(utilityName);
                        }
                    } else {
                        this.stylesBy[className] = [
                            utilityName
                        ];
                    }
                    if (!currentClass.includes(className)) {
                        currentClass.push(className);
                    }
                };
                if (utilityNames.includes(eachClassName)) {
                    handleUtilityName(eachClassName);
                    for (const parentClassName of this.styles[eachClassName]){
                        handle(parentClassName);
                    }
                } else {
                    handle(eachClassName);
                }
            }
        };
        for (const eachUtilityName of utilityNames){
            handleUtilityName(eachUtilityName);
        }
        if (syntaxes || utilities) {
            const rulesEntries = [];
            if (utilities) {
                for(const utilityName in utilities){
                    const declarations = utilities[utilityName];
                    rulesEntries.push([
                        utilityName,
                        {
                            declarations,
                            layer: Layer.Utility
                        }
                    ]);
                }
            }
            if (syntaxes) {
                rulesEntries.push(...Object.entries(syntaxes));
            }
            const rulesEntriesLength = rulesEntries.length;
            const colorNames = Object.keys(colorVariableNames);
            rulesEntries.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]);
            }).forEach(([id, eachSyntaxDefinition], index)=>{
                const EachRule = {
                    id,
                    keys: [],
                    variables: {},
                    matchers: {},
                    order: rulesEntriesLength - 1 - index,
                    definition: eachSyntaxDefinition
                };
                this.Rules.push(EachRule);
                const { matcher, layer, subkey, ambiguousKeys, ambiguousValues, sign } = eachSyntaxDefinition;
                if (layer === Layer.Utility) {
                    EachRule.id = '.' + id;
                    EachRule.matchers.arbitrary = new RegExp('^' + escapeString(id) + '(?=!|\\*|>|\\+|~|:|\\[|@|_|\\.|$)', 'm');
                }
                // todo: 不可使用 startsWith 判斷，應改為更精準的從 config.variables 取得目標變數群組，但 config.variables 中的值還沒被 resolve 像是 Array
                const addResolvedVariables = (groupName)=>{
                    for(const eachVariableName in this.variables){
                        const eachVariable = this.variables[eachVariableName];
                        if (eachVariable.group === groupName) {
                            EachRule.variables[eachVariable.key] = eachVariable;
                        }
                    }
                };
                // 1. custom `config.syntaxes[id].variables`
                if (eachSyntaxDefinition.variables) {
                    for (const eachVariableGroup of eachSyntaxDefinition.variables){
                        addResolvedVariables(eachVariableGroup);
                    }
                }
                // 2. custom `config.variables`
                addResolvedVariables(id);
                const keys = [];
                let { key } = eachSyntaxDefinition;
                if (layer === Layer.NativeShorthand || layer === Layer.Native) {
                    if (!key) eachSyntaxDefinition.key = key = id;
                    keys.push(id);
                }
                if (sign) {
                    EachRule.matchers.arbitrary = new RegExp(`^${sign}[^!*>+~:[@_]+\\|`);
                } else if (!matcher) {
                    const colorsPatten = colorNames.join('|');
                    if (!key && !subkey) {
                        keys.push(id);
                    } else {
                        if (key && !keys.includes(key)) keys.push(key);
                        if (subkey) keys.push(subkey);
                        if (layer === Layer.Shorthand) {
                            keys.push(id);
                        }
                    }
                    if (ambiguousKeys?.length) {
                        const ambiguousKeyPattern = ambiguousKeys.length > 1 ? `(?:${ambiguousKeys.join('|')})` : ambiguousKeys[0];
                        const variableKeys = Object.keys(EachRule.variables);
                        if (ambiguousValues?.length) {
                            const ambiguousValuePatterns = [];
                            for (const eachAmbiguousValue of ambiguousValues){
                                if (eachAmbiguousValue instanceof RegExp) {
                                    ambiguousValuePatterns.push(eachAmbiguousValue.source.replace('\\$colors', colorsPatten));
                                } else {
                                    ambiguousValuePatterns.unshift(`${eachAmbiguousValue}(?:\\b|_)`);
                                }
                            }
                            EachRule.matchers.value = new RegExp(`^${ambiguousKeyPattern}:(?:${ambiguousValuePatterns.join('|')})[^|]*?(?:@|$)`);
                        }
                        if (variableKeys.length) {
                            EachRule.matchers.variable = new RegExp(`^${ambiguousKeyPattern}:(?:${variableKeys.join('|')}(?![a-zA-Z0-9-]))[^|]*?(?:@|$)`);
                        }
                    }
                } else {
                    EachRule.matchers.arbitrary = matcher;
                }
                if (keys.length) {
                    EachRule.keys = keys;
                    EachRule.matchers.key = new RegExp(`^${keys.length > 1 ? `(${keys.join('|')})` : keys[0]}:.`);
                }
            });
        }
    }
    /**
     * Match check if Master CSS syntax
     * @param className
     * @returns css text
     */ match(className) {
        /**
         * 1. variable
         * @example fg:primary bg:blue
         */ for (const EachRule of this.Rules){
            if (EachRule.matchers.variable?.test(className)) return EachRule;
        }
        /**
         * 2. value (ambiguous.key * ambiguous.values)
         * @example bg:current box:content font:12
         */ for (const EachRule of this.Rules){
            if (EachRule.matchers.value?.test(className)) return EachRule;
        }
        /**
         * 3. full key
         * @example text-align:center color:blue-40
         */ for (const EachRule of this.Rules){
            if (EachRule.matchers.key?.test(className)) return EachRule;
        }
        /**
         * 4. arbitrary
         * @example custom RegExp, utility
         */ for (const EachRule of this.Rules){
            if (EachRule.matchers.arbitrary?.test(className)) return EachRule;
        }
    }
    /**
     * Generate syntaxes 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((eachSyntax)=>eachSyntax && eachSyntax?.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 RegisteredRule = this.match(syntax);
        if (RegisteredRule) {
            return new Rule(syntax, RegisteredRule, this);
        }
    }
    /**
     * 根據蒐集到的所有 DOM class 重新 create
     */ refresh(customConfig) {
        if (customConfig) {
            this.customConfig = customConfig;
        } else {
            customConfig = this.customConfig;
        }
        if (!customConfig?.override) {
            // @ts-ignore
            this.config = extendConfig(config, customConfig);
        } else {
            // @ts-ignore
            this.config = extendConfig(customConfig);
        }
        this.resolve();
        this.syntaxes.length = 0;
        // @ts-ignore
        this.ruleBy = {};
        /**
         * 拿當前所有的 classNames 按照最新的 colors, config.syntaxes 匹配並生成新的 style
         * 所以 refresh 過後 syntaxes 可能會變多也可能會變少
         */ for(const name in this.classesUsage){
            this.add(name);
        }
        return this;
    }
    reset() {
        // @ts-ignore
        this.ruleBy = {};
        // @ts-ignore
        this.classesUsage = {};
        this.syntaxes.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 syntaxes = this.generate(className);
            if (syntaxes.length) {
                for (const rule of syntaxes){
                    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.syntaxes.splice(this.syntaxes.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.syntaxes[0];
                                const index = variablesRule.natives.indexOf(nativeRule);
                                sheet?.deleteRule(index);
                                variablesRule.natives.splice(index, 1);
                                delete this.variablesNativeRules[mode];
                                if (!variablesRule.natives.length) {
                                    this.syntaxes.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.syntaxes[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);
                        sheet?.deleteRule(Object.keys(this.variablesNativeRules).length + nativeIndex);
                        keyframeRule.natives.splice(nativeIndex, 1);
                        keyframe.native = undefined;
                    }
                }
                if (!keyframeRule.natives.length) {
                    this.syntaxes.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.syntaxes.length - 1;
        const { at, atToken, order, priority, hasWhere, className } = rule;
        const findIndex = (startIndex, stopCheck, matchCheck)=>{
            let i = startIndex;
            for(; i <= endIndex; i++){
                const eachSyntax = this.syntaxes[i];
                if (stopCheck?.(eachSyntax)) return matchCheck ? -1 : i - 1;
                if (matchCheck?.(eachSyntax)) return i;
            }
            return matchCheck ? -1 : i - 1;
        };
        let matchStartIndex;
        let matchEndIndex;
        if (atToken) {
            const mediaStartIndex = this.syntaxes.findIndex((eachSyntax)=>eachSyntax.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.syntaxes.findIndex((eachSyntax)=>eachSyntax.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, (eachSyntax)=>eachSyntax.priority !== -1, (eachSyntax)=>eachSyntax.at.media?.find(({ name })=>name === 'max-width') && eachSyntax.at.media?.find(({ name })=>name === 'min-width'));
                                matchEndIndex = findIndex(mediaWidthStartIndex, (eachSyntax)=>eachSyntax.priority !== -1);
                            } else {
                                matchStartIndex = findIndex(mediaWidthStartIndex, undefined, (eachSyntax)=>eachSyntax.at.media?.find(({ name })=>name === 'max-width') && eachSyntax.at.media?.find(({ name })=>name === 'min-width') && eachSyntax.priority !== -1);
                                matchEndIndex = endIndex;
                            }
                            if (matchStartIndex !== -1) {
                                const range = maxWidthFeature.value - minWidthFeature.value;
                                let i = matchEndIndex;
                                const endI = matchStartIndex;
                                matchStartIndex = matchEndIndex + 1;
                                for(; i >= endI; i--){
                                    const eachSyntax = this.syntaxes[i];
                                    const eachMaxWidthFeature = eachSyntax.at.media?.find(({ name })=>name === 'max-width');
                                    const eachMinWidthFeature = eachSyntax.at.media?.find(({ name })=>name === 'min-width');
                                    if (!eachMaxWidthFeature || !eachMinWidthFeature) {
                                        break;
                                    } else {
                                        const eachRange = eachMaxWidthFeature.value - eachMinWidthFeature.value;
                                        if (eachRange < range) {
                                            matchEndIndex = i - 1;
                                        } else if (eachRange === range) {
                                            matchStartIndex = i;
                                        } else {
                                            break;
                                        }
                                    }
                                }
                            }
                        } else if (minWidthFeature) {
                            /**
                             * find 第一個所遇到同樣 feature 且值比自己大的 rule，
                             * 並插入在該 rule 之後，讓自己優先被套用
                             */ if (priority === -1) {
                                matchStartIndex = findIndex(mediaWidthStartIndex, (eachSyntax)=>eachSyntax.at.media?.find(({ name })=>name === 'max-width') && eachSyntax.at.media?.find(({ name })=>name === 'min-width') || eachSyntax.priority !== -1, (eachSyntax)=>!eachSyntax.at.media?.find(({ name })=>name === 'max-width') && eachSyntax.at.media?.find(({ name })=>name === 'min-width'));
                                matchEndIndex = findIndex(mediaWidthStartIndex, (eachSyntax)=>eachSyntax.at.media?.find(({ name })=>name === 'max-width') && eachSyntax.at.media?.find(({ name })=>name === 'min-width') || eachSyntax.priority !== -1);
                            } else {
                                matchStartIndex = findIndex(mediaWidthStartIndex, (eachSyntax)=>eachSyntax.at.media?.find(({ name })=>name === 'max-width') && eachSyntax.at.media?.find(({ name })=>name === 'min-width') && eachSyntax.priority !== -1, (eachSyntax)=>!eachSyntax.at.media?.find(({ name })=>name === 'max-width') && eachSyntax.at.media?.find(({ name })=>name === 'min-width') && eachSyntax.priority !== -1);
                                matchEndIndex = findIndex(mediaWidthStartIndex, (eachSyntax)=>eachSyntax.at.media?.find(({ name })=>name === 'max-width') && eachSyntax.at.media?.find(({ name })=>name === 'min-width') && eachSyntax.priority !== -1);
                            }
                            if (matchStartIndex !== -1) {
                                for(let i = matchEndIndex; i >= matchStartIndex; i--){
                                    const value = (this.syntaxes[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, (eachSyntax)=>eachSyntax.at.media?.find(({ name })=>name === 'min-width') || eachSyntax.priority !== -1, (eachSyntax)=>eachSyntax.at.media?.find(({ name })=>name === 'max-width'));
                                matchEndIndex = findIndex(mediaWidthStartIndex, (eachSyntax)=>eachSyntax.at.media?.find(({ name })=>name === 'min-width') || eachSyntax.priority !== -1);
                            } else {
                                matchStartIndex = findIndex(mediaWidthStartIndex, (eachSyntax)=>eachSyntax.at.media?.find(({ name })=>name === 'min-width') && eachSyntax.priority !== -1, (eachSyntax)=>eachSyntax.at.media?.find(({ name })=>name === 'max-width') && eachSyntax.priority !== -1);
                                matchEndIndex = findIndex(mediaWidthStartIndex, (eachSyntax)=>eachSyntax.at.media?.find(({ name })=>name === 'min-width') && eachSyntax.priority !== -1);
                            }
                            if (matchStartIndex !== -1) {
                                for(let i = matchEndIndex; i >= matchStartIndex; i--){
                                    const value = (this.syntaxes[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, (eachSyntax)=>eachSyntax.at.media?.find(({ name })=>name === 'max-width' || name === 'min-width') || eachSyntax.priority !== -1);
                    } else {
                        matchStartIndex = findIndex(mediaStartIndex, (eachSyntax)=>eachSyntax.at.media?.find(({ name })=>name === 'max-width' || name === 'min-width'), (eachSyntax)=>eachSyntax.priority !== -1);
                        matchEndIndex = findIndex(mediaStartIndex, (eachSyntax)=>eachSyntax.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, (eachSyntax)=>eachSyntax.atToken || eachSyntax.priority !== -1);
            } else {
                matchStartIndex = findIndex(findStartIndex, (eachSyntax)=>eachSyntax.atToken, (eachSyntax)=>eachSyntax.priority !== -1);
                matchEndIndex = findIndex(findStartIndex, (eachSyntax)=>eachSyntax.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.syntaxes[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.syntaxes[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.syntaxes.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.syntaxes[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);
        return index;
    }
    get text() {
        return this.syntaxes.map((eachSyntax)=>eachSyntax.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.syntaxes[keyframeRulesIndex]).natives.push(nativeRule);
                    } else {
                        this.syntaxes.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, variable)=>{
                        let nativeRule = this.variablesNativeRules[mode];
                        if (!nativeRule) {
                            let cssRule;
                            let mediaConditionText;
                            let selectorText;
                            if (mode) {
                                switch(this.config.modes?.[mode]){
                                    case 'media':
                                        mediaConditionText = `@media(prefers-color-scheme:${mode})`;
                                        selectorText = ':root';
                                        break;
                                    case 'host':
                                        selectorText = `:host(.${mode})`;
                                        if (this.config.defaultMode === mode) {
                                            selectorText += ',:host';
                                        }
                                        break;
                                    case 'class':
                                        selectorText = '.' + mode;
                                        if (this.config.defaultMode === mode) {
                                            selectorText += ',:root';
                                        }
                                        break;
                                    default:
                                        return;
                                }
                            } else {
                                selectorText = ':root';
                            }
                            if (sheet) {
                                const newCSSRuleIndex = Object.keys(this.variablesNativeRules).length ? this.syntaxes[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(variable.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.syntaxes.splice(0, 0, newRule);
        }
        let prefix = '';
        let suffix = '}';
        if (variableCSSRule.parentRule) {
            prefix += '@media' + 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.syntaxes[0].natives.push(this.variablesNativeRules[mode] = nativeRule);
        return nativeRule;
    }
}
const masterCSSs = [];
(()=>{
    globalThis.MasterCSS = MasterCSS;
    globalThis.masterCSSs = masterCSSs;
})();

export { MasterCSS as default, masterCSSs };
