/*!
 * KoliBri - The accessible HTML-Standard
 */
import { getCssStyle, patchTheme, patchThemeTag } from "adopted-style-sheets";
import rgba from "rgba-convert";
import { hex, score } from "wcag-contrast";
import { getDocument, getExperimentalMode, Log } from "./dev.utils";
import { devHint } from "./a11y.tipps";
const OBJECT_OBJECT = /\[object Object\]/;
export const objectObjectHandler = (value, cb) => {
    if (typeof value === 'string' && OBJECT_OBJECT.test(value)) {
        return;
    }
    cb();
};
export const emptyStringByArrayHandler = (value, cb) => {
    if (typeof value === 'string' && value === '') {
        return;
    }
    cb();
};
export const setEventTarget = (event, target) => {
    if (getExperimentalMode()) {
        Log.debug([event, target]);
        Log.debug(`↑ We propagate the (submit) event to this target.`);
    }
    Object.defineProperty(event, 'target', {
        value: target,
        writable: false,
    });
};
const patchState = (component) => {
    var _a, _b, _c;
    (_a = component.nextHooks) === null || _a === void 0 ? void 0 : _a.forEach((hooks, key) => {
        var _a;
        const beforePatch = hooks.get('beforePatch');
        if (typeof beforePatch === 'function') {
            beforePatch((_a = component.nextState) === null || _a === void 0 ? void 0 : _a.get(key), component.nextState, component, key);
        }
    });
    if (((_b = component.nextState) === null || _b === void 0 ? void 0 : _b.size) > 0) {
        component.state = Object.assign(Object.assign({}, component.state), Object.fromEntries(component.nextState));
        delete component.nextState;
        (_c = component.nextHooks) === null || _c === void 0 ? void 0 : _c.forEach((hooks, key) => {
            const afterPatch = hooks.get('afterPatch');
            if (typeof afterPatch === 'function') {
                afterPatch(component.state[key], component.state, component, key);
            }
        });
    }
    delete component.nextHooks;
};
export const setState = (component, propName, value, hooks = {}) => {
    var _a, _b;
    if (component.nextHooks === undefined) {
        component.nextHooks = new Map();
    }
    if (component.nextState === undefined) {
        component.nextState = new Map();
    }
    const nextHooks = component.nextHooks.get(propName);
    if (nextHooks instanceof Map === false) {
        component.nextHooks.set(propName, new Map());
    }
    if (typeof hooks.afterPatch === 'function') {
        (_a = component.nextHooks.get(propName)) === null || _a === void 0 ? void 0 : _a.set('afterPatch', hooks.afterPatch);
    }
    if (typeof hooks.beforePatch === 'function') {
        (_b = component.nextHooks.get(propName)) === null || _b === void 0 ? void 0 : _b.set('beforePatch', hooks.beforePatch);
    }
    component.nextState.set(propName, value);
    patchState(component);
};
const logWarn = (component, propName, value, requiredGeneric) => {
    devHint(`[${component.constructor.name}] The property value: (${value}) for '${propName}' is not valid. Allowed values are: ${Array.from(requiredGeneric).join(', ')}`);
};
export function watchValidator(component, propName, validationFunction, requiredGeneric, value, options = {}) {
    if (validationFunction(value)) {
        setState(component, propName, value, options.hooks);
    }
    else if (value === undefined && options.required !== true && validationFunction(options.defaultValue)) {
        setState(component, propName, options.defaultValue, options.hooks);
    }
    else {
        if (!options.required) {
            requiredGeneric.add(null);
        }
        logWarn(component, propName, value, requiredGeneric);
    }
}
export const watchBoolean = (component, propName, value, options) => {
    watchValidator(component, propName, (value) => typeof value === 'boolean', new Set(['Boolean {true, false}']), value, options);
};
export const watchString = (component, propName, value, options = {}) => {
    const minLength = typeof options.minLength === 'number' ? options === null || options === void 0 ? void 0 : options.minLength : 0;
    watchValidator(component, propName, (value) => typeof value === 'string' && value.length >= minLength && (typeof (options === null || options === void 0 ? void 0 : options.maxLength) === 'undefined' || value.length <= options.maxLength), new Set([`String`]), value, options);
};
export const watchNumber = (component, propName, value, options) => {
    watchValidator(component, propName, (value) => typeof value === 'number' &&
        (typeof (options === null || options === void 0 ? void 0 : options.min) === 'undefined' || (typeof (options === null || options === void 0 ? void 0 : options.min) === 'number' && value >= options.min)) &&
        (typeof (options === null || options === void 0 ? void 0 : options.max) === 'undefined' || (typeof (options === null || options === void 0 ? void 0 : options.max) === 'number' && value <= options.max)), new Set(['Number']), value, options);
};
export const watchJsonArrayString = (component, propName, itemValidation, value, arrayValidation = (items) => items === items, options = {}) => {
    emptyStringByArrayHandler(value, () => {
        objectObjectHandler(value, () => {
            if (typeof value === 'undefined') {
                value = [];
            }
            try {
                try {
                    value = parseJson(value);
                }
                catch (_a) {
                }
                if (Array.isArray(value)) {
                    const invalid = value.find((item) => !itemValidation(item));
                    if (invalid === undefined && arrayValidation(value)) {
                        setState(component, propName, value, options.hooks);
                    }
                    else {
                        objectObjectHandler(invalid, () => {
                            Log.debug(invalid);
                            throw new Error(`↑ The schema for the property (_options) is not valid. The value will not be changed.`);
                        });
                    }
                }
                else {
                    objectObjectHandler(value, () => {
                        Log.debug(value);
                        throw new Error(`↑ The schema for the property (_options) is not valid. The value will not be changed.`);
                    });
                }
            }
            catch (error) {
                Log.debug(error);
            }
        });
    });
};
export const stringifyJson = (value) => {
    try {
        return JSON.stringify(value).replace(/"/g, "'");
    }
    catch (_a) {
        Log.warn(['stringifyJson', value]);
        Log.error(`↑ The JSON could not be converted to a string. A stringifiable JSON is expected.`);
        throw new Error();
    }
};
const JSON_CHARS = /^[{[]/;
export const parseJson = (value) => {
    if (typeof value === 'string') {
        try {
            return JSON.parse(value);
        }
        catch (_a) {
            if (JSON_CHARS.test(value)) {
                try {
                    return JSON.parse(value.replace(/'/g, '"'));
                }
                catch (_b) {
                    Log.warn(['parseJson', value]);
                    Log.error(`↑ The JSON string could not be parsed. Make sure that single quotes in the text are escaped (&#8216;).`);
                }
            }
        }
    }
    throw new Error();
};
export const mapBoolean2String = (value) => {
    return typeof value === 'boolean' ? (value === true ? 'true' : 'false') : undefined;
};
export const mapStringOrBoolean2String = (value) => {
    return typeof value === 'string' ? value : mapBoolean2String(value);
};
const traverseShadowDom = (rootNode, onNode) => {
    if (!rootNode) {
        return;
    }
    const visited = new Set();
    const queue = [rootNode];
    let index = 0;
    while (index < queue.length) {
        const current = queue[index++];
        if (visited.has(current)) {
            continue;
        }
        visited.add(current);
        if (onNode(current)) {
            return;
        }
        const children = Array.from(current.children || []);
        for (let i = 0; i < children.length; i++) {
            queue.push(children[i]);
        }
        if (current instanceof HTMLElement && current.shadowRoot) {
            queue.push(current.shadowRoot);
        }
        if (current instanceof HTMLSlotElement) {
            const assigned = current.assignedNodes({ flatten: true });
            for (let i = 0; i < assigned.length; i++) {
                if (assigned[i] instanceof Element) {
                    queue.push(assigned[i]);
                }
            }
        }
    }
};
const querySelectorShadow = (selector, rootNode) => {
    let result = null;
    traverseShadowDom(rootNode, (current) => {
        if (current instanceof Element && current.matches(selector)) {
            result = current;
            return true;
        }
        return false;
    });
    return result;
};
const querySelectorAllShadow = (selector, rootNode) => {
    const results = [];
    traverseShadowDom(rootNode, (current) => {
        if (current instanceof Element && current.matches(selector)) {
            results.push(current);
        }
        return false;
    });
    return results;
};
export const koliBriQuerySelector = (selector, node) => querySelectorShadow(selector, node || getDocument());
export const koliBriQuerySelectorAll = (selector, node) => querySelectorAllShadow(selector, node || getDocument());
let DEFAULT_COLOR_CONTRAST = null;
const getDefaultColorContrast = () => {
    DEFAULT_COLOR_CONTRAST = DEFAULT_COLOR_CONTRAST || {
        backgroundColor: '#00000000',
        color: '#00000000',
        domNode: getDocument().body,
        level: 'Fail',
        score: 1,
    };
    return DEFAULT_COLOR_CONTRAST;
};
const TRANSPARENT_REGEXP = /(\d+, ){3}0\)/;
export const koliBriA11yColorContrast = (domNode, a11yColorContrast = getDefaultColorContrast()) => {
    const computedStyle = getComputedStyle(domNode);
    const hexBG = TRANSPARENT_REGEXP.test(computedStyle.backgroundColor) ? a11yColorContrast.backgroundColor : rgba.hex(computedStyle.backgroundColor);
    const hexC = TRANSPARENT_REGEXP.test(computedStyle.color) ? a11yColorContrast.color : rgba.hex(computedStyle.color);
    const diff = hex(hexBG, hexC);
    const contrast = {
        backgroundColor: hexBG,
        color: hexC,
        domNode: domNode,
        level: score(diff),
        score: diff,
    };
    if (diff < 4.5) {
        Log.error([
            'Color-Contrast-Error',
            {
                backgroundColor: contrast.backgroundColor,
                color: contrast.color,
                level: contrast.level,
                score: contrast.score,
            },
            contrast.domNode,
        ]);
    }
    return contrast;
};
export const koliBriQuerySelectorColors = (selector, a11yColorContrast = getDefaultColorContrast()) => {
    if (a11yColorContrast.domNode instanceof HTMLElement) {
        a11yColorContrast = koliBriA11yColorContrast(a11yColorContrast.domNode, a11yColorContrast);
    }
    const selectedNode = a11yColorContrast.domNode.querySelector(selector);
    if (selectedNode === null) {
        const nodeList = a11yColorContrast.domNode.querySelectorAll('[class="hydrated"]');
        for (let i = 0; i < nodeList.length; i++) {
            a11yColorContrast.domNode = nodeList[i];
            a11yColorContrast = koliBriQuerySelectorColors(selector, a11yColorContrast);
            if (a11yColorContrast.domNode !== null) {
                break;
            }
        }
        return a11yColorContrast;
    }
    else {
        return koliBriA11yColorContrast(selectedNode, a11yColorContrast);
    }
};
export class KoliBriUtils {
    static queryHtmlElementColors(targetNode, a11yColorContrast, recursion = false, log = true) {
        let returnValue = null;
        if (recursion === true || KoliBriUtils.executionLock === false) {
            if (recursion === false) {
                KoliBriUtils.cache.clear();
                KoliBriUtils.cache.set(a11yColorContrast.domNode, a11yColorContrast);
                KoliBriUtils.executionLock = true;
                if (log === true) {
                    Log.debug(`[KoliBriUtils] Color contrast analysis started...`);
                }
            }
            if (targetNode === a11yColorContrast.domNode) {
                returnValue = a11yColorContrast;
            }
            else {
                const children = new Set();
                if (a11yColorContrast.domNode.shadowRoot) {
                    const shadowChildren = a11yColorContrast.domNode.shadowRoot.children;
                    for (let i = 0; i < shadowChildren.length; i++) {
                        children.add(shadowChildren[i]);
                    }
                }
                const slotElement = a11yColorContrast.domNode;
                if (typeof slotElement.assignedNodes === 'function') {
                    const slotChildren = slotElement.assignedNodes();
                    for (let i = 0; i < slotChildren.length; i++) {
                        if (slotChildren[i] instanceof HTMLElement) {
                            children.add(slotChildren[i]);
                        }
                    }
                }
                const domChildren = a11yColorContrast.domNode.children;
                for (let i = 0; i < domChildren.length; i++) {
                    children.add(domChildren[i]);
                }
                const arrayChildren = Array.from(children);
                for (let i = 0; i < arrayChildren.length; i++) {
                    let colorContrast = KoliBriUtils.cache.get(arrayChildren[i]);
                    if (colorContrast === undefined) {
                        colorContrast = koliBriA11yColorContrast(arrayChildren[i], a11yColorContrast);
                    }
                    KoliBriUtils.cache.set(arrayChildren[i], colorContrast);
                    const colors = KoliBriUtils.queryHtmlElementColors(targetNode, colorContrast, true, false);
                    if (colors !== null) {
                        returnValue = colors;
                        break;
                    }
                }
            }
        }
        else {
            Log.debug(`[KoliBriUtils] Call aborted because a color contrast analysis is currently being executed.`);
        }
        if (recursion === false) {
            if (log === true) {
                Log.debug(`[KoliBriUtils] Color contrast analysis finished (${KoliBriUtils.cache.size} DOM elements are analysed).`);
            }
            KoliBriUtils.executionLock = false;
            KoliBriUtils.cache.clear();
        }
        return returnValue;
    }
}
KoliBriUtils.executionLock = false;
KoliBriUtils.cache = new Map();
export class KoliBriDevHelper {
}
KoliBriDevHelper.getCssStyle = getCssStyle;
KoliBriDevHelper.patchTheme = patchTheme;
KoliBriDevHelper.patchThemeTag = patchThemeTag;
KoliBriDevHelper.querySelector = koliBriQuerySelector;
KoliBriDevHelper.querySelectorAll = koliBriQuerySelectorAll;
KoliBriDevHelper.stringifyJson = stringifyJson;
//# sourceMappingURL=prop.validators.js.map
