"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const PropertySymbol = __importStar(require("../../../PropertySymbol.cjs"));
const CSSStyleDeclarationPropertyManager_js_1 = __importDefault(require("../property-manager/CSSStyleDeclarationPropertyManager.cjs"));
const NodeTypeEnum_js_1 = __importDefault(require("../../../nodes/node/NodeTypeEnum.cjs"));
const CSSRuleTypeEnum_js_1 = __importDefault(require("../../CSSRuleTypeEnum.cjs"));
const CSSStyleDeclarationElementDefaultCSS_js_1 = __importDefault(require("./config/CSSStyleDeclarationElementDefaultCSS.cjs"));
const CSSStyleDeclarationElementInheritedProperties_js_1 = __importDefault(require("./config/CSSStyleDeclarationElementInheritedProperties.cjs"));
const CSSStyleDeclarationElementMeasurementProperties_js_1 = __importDefault(require("./config/CSSStyleDeclarationElementMeasurementProperties.cjs"));
const CSSStyleDeclarationCSSParser_js_1 = __importDefault(require("../css-parser/CSSStyleDeclarationCSSParser.cjs"));
const QuerySelector_js_1 = __importDefault(require("../../../query-selector/QuerySelector.cjs"));
const CSSMeasurementConverter_js_1 = __importDefault(require("../measurement-converter/CSSMeasurementConverter.cjs"));
const MediaQueryList_js_1 = __importDefault(require("../../../match-media/MediaQueryList.cjs"));
const WindowBrowserContext_js_1 = __importDefault(require("../../../window/WindowBrowserContext.cjs"));
const CSS_MEASUREMENT_REGEXP = /[0-9.]+(px|rem|em|vw|vh|%|vmin|vmax|cm|mm|in|pt|pc|Q)/g;
const CSS_VARIABLE_REGEXP = /var\( *(--[^), ]+)\)|var\( *(--[^), ]+), *(.+)\)/;
/**
 * CSS Style Declaration utility
 */
class CSSStyleDeclarationComputedStyle {
    element;
    /**
     * Constructor.
     *
     * @param element Element.
     * @param [computed] Computed.
     */
    constructor(element) {
        this.element = element;
    }
    /**
     * Returns style sheets.
     *
     * @param element Element.
     * @returns Style sheets.
     */
    getComputedStyle() {
        const documentElements = [];
        const parentElements = [];
        let styleAndElement = {
            element: this.element,
            cssTexts: []
        };
        let shadowRootElements = [];
        if (!this.element[PropertySymbol.isConnected]) {
            return new CSSStyleDeclarationPropertyManager_js_1.default();
        }
        const cacheResult = this.element[PropertySymbol.cache].computedStyle;
        if (cacheResult?.result) {
            const result = cacheResult.result.deref();
            if (result) {
                return result;
            }
        }
        // Walks through all parent elements and stores them in an array with element and matching CSS text.
        while (styleAndElement.element) {
            if (styleAndElement.element[PropertySymbol.nodeType] === NodeTypeEnum_js_1.default.elementNode) {
                const rootNode = styleAndElement.element.getRootNode();
                if (rootNode[PropertySymbol.nodeType] === NodeTypeEnum_js_1.default.documentNode) {
                    documentElements.unshift(styleAndElement);
                }
                else {
                    shadowRootElements.unshift(styleAndElement);
                }
                parentElements.unshift(styleAndElement);
            }
            if (styleAndElement.element === this.element[PropertySymbol.ownerDocument]) {
                const styleSheets = (this.element[PropertySymbol.ownerDocument].querySelectorAll('style,link[rel="stylesheet"]'));
                for (const styleSheet of styleSheets) {
                    const sheet = styleSheet.sheet;
                    if (sheet) {
                        this.parseCSSRules({
                            elements: documentElements,
                            rootElement: documentElements[0].element[PropertySymbol.tagName] === 'HTML'
                                ? documentElements[0]
                                : null,
                            cssRules: sheet.cssRules
                        });
                    }
                }
                for (const sheet of this.element[PropertySymbol.ownerDocument].adoptedStyleSheets) {
                    this.parseCSSRules({
                        elements: documentElements,
                        rootElement: documentElements[0].element[PropertySymbol.tagName] === 'HTML'
                            ? documentElements[0]
                            : null,
                        cssRules: sheet.cssRules
                    });
                }
                styleAndElement = { element: null, cssTexts: [] };
            }
            else if (styleAndElement.element[PropertySymbol.nodeType] === NodeTypeEnum_js_1.default.documentFragmentNode &&
                styleAndElement.element.host) {
                const shadowRoot = styleAndElement.element;
                const styleSheets = (shadowRoot.querySelectorAll('style,link[rel="stylesheet"]'));
                styleAndElement = {
                    element: shadowRoot.host,
                    cssTexts: []
                };
                for (const styleSheet of styleSheets) {
                    const sheet = styleSheet.sheet;
                    if (sheet) {
                        this.parseCSSRules({
                            elements: shadowRootElements,
                            cssRules: sheet.cssRules,
                            hostElement: styleAndElement
                        });
                    }
                }
                for (const sheet of shadowRoot.adoptedStyleSheets) {
                    this.parseCSSRules({
                        elements: shadowRootElements,
                        cssRules: sheet.cssRules,
                        hostElement: styleAndElement
                    });
                }
                shadowRootElements = [];
            }
            else {
                styleAndElement = {
                    element: styleAndElement.element[PropertySymbol.parentNode],
                    cssTexts: []
                };
            }
        }
        // Concatenates all parent element CSS to one string.
        const targetElement = parentElements[parentElements.length - 1];
        const propertyManager = new CSSStyleDeclarationPropertyManager_js_1.default();
        const cssProperties = {};
        let rootFontSize = 16;
        let parentFontSize = 16;
        for (const parentElement of parentElements) {
            parentElement.cssTexts.sort((a, b) => a.priorityWeight - b.priorityWeight);
            let elementCSSText = '';
            if (CSSStyleDeclarationElementDefaultCSS_js_1.default[parentElement.element[PropertySymbol.tagName]]) {
                if (typeof CSSStyleDeclarationElementDefaultCSS_js_1.default[parentElement.element[PropertySymbol.tagName]] === 'string') {
                    elementCSSText +=
                        CSSStyleDeclarationElementDefaultCSS_js_1.default[parentElement.element[PropertySymbol.tagName]];
                }
                else {
                    for (const key of Object.keys(CSSStyleDeclarationElementDefaultCSS_js_1.default[parentElement.element[PropertySymbol.tagName]])) {
                        if (key === 'default' || !!parentElement.element[key]) {
                            elementCSSText +=
                                CSSStyleDeclarationElementDefaultCSS_js_1.default[parentElement.element[PropertySymbol.tagName]][key];
                        }
                    }
                }
                elementCSSText +=
                    CSSStyleDeclarationElementDefaultCSS_js_1.default[parentElement.element[PropertySymbol.tagName]];
            }
            for (const cssText of parentElement.cssTexts) {
                elementCSSText += cssText.cssText;
            }
            const elementStyleAttribute = parentElement.element[PropertySymbol.attributes][PropertySymbol.namedItems].get('style');
            if (elementStyleAttribute) {
                elementCSSText += elementStyleAttribute[PropertySymbol.value];
            }
            const rulesAndProperties = CSSStyleDeclarationCSSParser_js_1.default.parse(elementCSSText);
            const rules = rulesAndProperties.rules;
            Object.assign(cssProperties, rulesAndProperties.properties);
            for (const { name, value, important } of rules) {
                if (CSSStyleDeclarationElementInheritedProperties_js_1.default[name] ||
                    parentElement === targetElement) {
                    const parsedValue = this.parseCSSVariablesInValue(value.trim(), cssProperties);
                    if (parsedValue && (!propertyManager.get(name)?.important || important)) {
                        propertyManager.set(name, parsedValue, important);
                        if (name === 'font' || name === 'font-size') {
                            const fontSize = propertyManager.properties['font-size'];
                            if (fontSize !== null) {
                                const parsedValue = this.parseMeasurementsInValue({
                                    value: fontSize.value,
                                    rootFontSize,
                                    parentFontSize,
                                    parentSize: parentFontSize
                                });
                                if (parentElement.element[PropertySymbol.tagName] === 'HTML') {
                                    rootFontSize = parsedValue;
                                }
                                else if (parentElement !== targetElement) {
                                    parentFontSize = parsedValue;
                                }
                            }
                        }
                    }
                }
            }
        }
        for (const name of CSSStyleDeclarationElementMeasurementProperties_js_1.default) {
            const property = propertyManager.properties[name];
            if (property) {
                property.value = this.parseMeasurementsInValue({
                    value: property.value,
                    rootFontSize,
                    parentFontSize,
                    // TODO: Only "font-size" is supported when using percentage values. Add support for other properties.
                    parentSize: name === 'font-size' ? parentFontSize : null
                });
            }
        }
        const cachedResult = {
            result: new WeakRef(propertyManager)
        };
        this.element[PropertySymbol.cache].computedStyle = cachedResult;
        this.element[PropertySymbol.ownerDocument][PropertySymbol.affectsComputedStyleCache].push(cachedResult);
        return propertyManager;
    }
    /**
     * Applies CSS text to elements.
     *
     * @param options Options.
     * @param options.elements Elements.
     * @param options.cssRules CSS rules.
     * @param options.rootElement Root element.
     * @param [options.hostElement] Host element.
     */
    parseCSSRules(options) {
        if (!options.elements.length) {
            return;
        }
        const window = this.element[PropertySymbol.window];
        for (const rule of options.cssRules) {
            if (rule.type === CSSRuleTypeEnum_js_1.default.styleRule) {
                const selectorText = rule.selectorText;
                if (selectorText) {
                    if (selectorText.startsWith(':host')) {
                        if (options.hostElement) {
                            options.hostElement.cssTexts.push({
                                cssText: rule[PropertySymbol.cssText],
                                priorityWeight: 0
                            });
                        }
                    }
                    else if (selectorText.startsWith(':root')) {
                        if (options.rootElement) {
                            options.rootElement.cssTexts.push({
                                cssText: rule[PropertySymbol.cssText],
                                priorityWeight: 0
                            });
                        }
                    }
                    else {
                        for (const element of options.elements) {
                            const match = QuerySelector_js_1.default.matches(element.element, selectorText, {
                                ignoreErrors: true
                            });
                            if (match) {
                                element.cssTexts.push({
                                    cssText: rule[PropertySymbol.cssText],
                                    priorityWeight: match.priorityWeight
                                });
                            }
                        }
                    }
                }
            }
            else if (rule.type === CSSRuleTypeEnum_js_1.default.mediaRule &&
                // TODO: We need to send in a predfined root font size as it will otherwise be calculated using Window.getComputedStyle(), which will cause a never ending loop. Is there another solution?
                new MediaQueryList_js_1.default({
                    window,
                    media: rule.conditionText,
                    rootFontSize: this.element[PropertySymbol.tagName] === 'HTML' ? 16 : null
                }).matches) {
                this.parseCSSRules({
                    elements: options.elements,
                    cssRules: rule.cssRules,
                    hostElement: options.hostElement
                });
            }
        }
    }
    /**
     * Parses CSS variables in a value.
     *
     * @param value Value.
     * @param cssVariables CSS variables.
     * @returns CSS value.
     */
    parseCSSVariablesInValue(value, cssVariables) {
        let newValue = value;
        let match;
        while ((match = newValue.match(CSS_VARIABLE_REGEXP)) !== null) {
            // Fallback value - E.g. var(--my-var, #FFFFFF)
            if (match[2] !== undefined) {
                newValue = newValue.replace(match[0], cssVariables[match[2]] || match[3]);
            }
            else {
                newValue = newValue.replace(match[0], cssVariables[match[1]] || '');
            }
        }
        return newValue;
    }
    /**
     * Parses measurements in a value.
     *
     * @param options Options.
     * @param options.value Value.
     * @param options.rootFontSize Root font size.
     * @param options.parentFontSize Parent font size.
     * @param [options.parentSize] Parent width.
     * @returns CSS value.
     */
    parseMeasurementsInValue(options) {
        if (new WindowBrowserContext_js_1.default(this.element[PropertySymbol.window]).getSettings()
            ?.disableComputedStyleRendering) {
            return options.value;
        }
        const regexp = new RegExp(CSS_MEASUREMENT_REGEXP);
        let newValue = options.value;
        let match;
        while ((match = regexp.exec(options.value)) !== null) {
            if (match[1] !== 'px') {
                const valueInPixels = CSSMeasurementConverter_js_1.default.toPixels({
                    window: this.element[PropertySymbol.window],
                    value: match[0],
                    rootFontSize: options.rootFontSize,
                    parentFontSize: options.parentFontSize,
                    parentSize: options.parentSize
                });
                if (valueInPixels !== null) {
                    newValue = newValue.replace(match[0], valueInPixels + 'px');
                }
            }
        }
        return newValue;
    }
}
exports.default = CSSStyleDeclarationComputedStyle;
//# sourceMappingURL=CSSStyleDeclarationComputedStyle.cjs.map