import { __assign, __awaiter, __generator, __values } from "tslib";
import { getGlobalScope } from '@amplitude/analytics-core';
import { DEFAULT_MASK_LEVEL } from './config/types';
import { KB_SIZE, MASK_TEXT_CLASS, UNMASK_TEXT_CLASS } from './constants';
import { getInputType } from './utils/get-input-type';
export { getServerUrl } from './utils/server-url';
/**
 * Light: Subset of inputs (sensitive types only — password, hidden, email, tel, cc-*); all texts
 * Medium: All inputs and all texts
 * Conservative: All inputs and all texts
 */
var isMaskedForLevel = function (elementType, level, element) {
    switch (level) {
        case 'light': {
            if (elementType !== 'input') {
                return true;
            }
            var inputType = element ? getInputType(element) : '';
            /* istanbul ignore if */ // TODO(lew): For some reason it's impossible to test this.
            if (!inputType) {
                return false;
            }
            if (['password', 'hidden', 'email', 'tel'].includes(inputType)) {
                return true;
            }
            if (element.autocomplete.startsWith('cc-')) {
                return true;
            }
            return false;
        }
        case 'medium':
        case 'conservative':
            return true;
        default:
            return isMaskedForLevel(elementType, DEFAULT_MASK_LEVEL, element);
    }
};
/**
 * Returns the effective mask level for a given URL by checking `urlMaskLevels`
 * (first match wins) and falling back to `defaultMaskLevel`.
 */
export var getEffectiveMaskLevel = function (url, config) {
    var e_1, _a;
    var _b;
    if (url && config.urlMaskLevels) {
        try {
            for (var _c = __values(config.urlMaskLevels), _d = _c.next(); !_d.done; _d = _c.next()) {
                var rule = _d.value;
                if (globToRegex(rule.match).test(url)) {
                    return rule.maskLevel;
                }
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
            }
            finally { if (e_1) throw e_1.error; }
        }
    }
    return (_b = config.defaultMaskLevel) !== null && _b !== void 0 ? _b : DEFAULT_MASK_LEVEL;
};
/**
 * Checks if the given element set to be masked by rrweb
 *
 * Priority is:
 *  1. [In code] Element/class based masking/unmasking <> [Config based] Selector based masking/unmasking
 *  2. Use app defaults
 */
export var isMasked = function (elementType, config, element, currentUrl) {
    var _a, _b;
    if (config === void 0) { config = { defaultMaskLevel: DEFAULT_MASK_LEVEL }; }
    if (element) {
        // Element or parent is explicitly instrumented in code to mask
        if (element.closest('.' + MASK_TEXT_CLASS)) {
            return true;
        }
        // Config has override for mask
        var shouldMask = ((_a = config.maskSelector) !== null && _a !== void 0 ? _a : []).some(function (selector) { return element.closest(selector); });
        if (shouldMask) {
            return true;
        }
        // Code or config has override to unmask
        if (element.closest('.' + UNMASK_TEXT_CLASS)) {
            return false;
        }
        // Here we are probably sent an element, but we want to match if they have a
        // parent with an unmask selector.
        var shouldUnmask = ((_b = config.unmaskSelector) !== null && _b !== void 0 ? _b : []).some(function (selector) { return element.closest(selector); });
        if (shouldUnmask) {
            return false;
        }
    }
    return isMaskedForLevel(elementType, getEffectiveMaskLevel(currentUrl, config), element);
};
export var maskFn = function (elementType, config, getCurrentUrl) {
    return function (text, element) {
        return isMasked(elementType, config, element, getCurrentUrl === null || getCurrentUrl === void 0 ? void 0 : getCurrentUrl()) ? text.replace(/[^\s]/g, '*') : text;
    };
};
export var maskAttributeFn = function (config, getCurrentUrl) {
    return function (key, value, element) {
        var _a;
        // Never mask style — rrweb has a separate styleDiff path for attribute mutations
        // that reads directly from the DOM, bypassing maskAttributeFn.
        if (key === 'style')
            return value;
        // Short-circuit: only proceed if this attribute is in the allowlist.
        if (!((_a = config === null || config === void 0 ? void 0 : config.maskAttributes) !== null && _a !== void 0 ? _a : []).includes(key))
            return value;
        // Recompute masking every call so class/ancestor mutations do not stale-cache
        // the decision for later attribute mutations on the same element.
        return isMasked('text', config, element, getCurrentUrl === null || getCurrentUrl === void 0 ? void 0 : getCurrentUrl()) ? value.replace(/[^\s]/g, '*') : value;
    };
};
export var getCurrentUrl = function () {
    var globalScope = getGlobalScope();
    return (globalScope === null || globalScope === void 0 ? void 0 : globalScope.location) ? globalScope.location.href : '';
};
export var generateSessionReplayId = function (sessionId, deviceId) {
    return "".concat(deviceId, "/").concat(sessionId);
};
var isValidGlobUrl = function (globUrl) {
    if (typeof globUrl !== 'string' || globUrl.trim() === '')
        return false;
    var urlPattern = /^\/|^https?:\/\/[^\s]+$/;
    if (!urlPattern.test(globUrl))
        return false;
    return true;
};
var globRegexCache = new Map();
var globToRegex = function (glob) {
    var cached = globRegexCache.get(glob);
    if (cached)
        return cached;
    // Glob → regex conversion. Glob substitution must happen BEFORE regex escaping so that
    // the escaper does not corrupt the glob characters (e.g. turning `/**` into `/\*\*`).
    // Placeholder tokens use null bytes, which are illegal in HTTP URLs and therefore can
    // never collide with real pattern content.
    //
    // Substitution order (most-specific first):
    //   trailing /**   → (/.*)?          — path with or without a trailing slash/subpath
    //   middle  /**/   → /(.*\/)?        — zero-or-more path segments (including zero)
    //   bare    **     → .*              — graceful fallback for ** not adjacent to /
    //   single  *      → .*              — any characters (preserves existing behaviour)
    //   ?              → .               — single character wildcard
    var T_TRAILING = '\x00TRAIL\x00';
    var T_MIDDLE = '\x00MID\x00';
    var T_DSTAR = '\x00DS\x00';
    var T_STAR = '\x00ST\x00';
    var T_QUEST = '\x00QU\x00';
    var s = glob;
    s = s.replace(/\/\*\*$/, T_TRAILING); // trailing /**
    s = s.replace(/\/\*\*\//g, T_MIDDLE); // /**/ in the middle
    s = s.replace(/\*\*/g, T_DSTAR); // bare ** (e.g. **.example.com)
    s = s.replace(/\*/g, T_STAR); // single *
    s = s.replace(/\?/g, T_QUEST); // ?
    // Escape all remaining regex special characters.
    s = s.replace(/[.+^${}()|[\]\\]/g, '\\$&');
    // Expand tokens into their regex equivalents.
    // Use split/join (not regex) to avoid the no-control-regex lint rule on the token strings.
    s = s.split(T_TRAILING).join('(/.*)?'); // /** → optional /anything
    s = s.split(T_MIDDLE).join('/(.*\\/)?'); // /**/ → /zero-or-more-segments/
    s = s.split(T_DSTAR).join('.*'); // bare ** → .*
    s = s.split(T_STAR).join('.*'); // * → .*
    s = s.split(T_QUEST).join('.'); // ? → .
    var regex = new RegExp("^".concat(s, "$"));
    globRegexCache.set(glob, regex);
    return regex;
};
export var validateUGCFilterRules = function (ugcFilterRules) {
    // validate ugcFilterRules
    if (!ugcFilterRules.every(function (rule) { return typeof rule.selector === 'string' && typeof rule.replacement === 'string'; })) {
        throw new Error('ugcFilterRules must be an array of objects with selector and replacement properties');
    }
    // validate ugcFilterRules are valid globs
    if (!ugcFilterRules.every(function (rule) { return isValidGlobUrl(rule.selector); })) {
        throw new Error('ugcFilterRules must be an array of objects with valid globs');
    }
};
export var getPageUrl = function (pageUrl, ugcFilterRules) {
    var e_2, _a;
    try {
        // apply ugcFilterRules, order is important, first rule wins
        for (var ugcFilterRules_1 = __values(ugcFilterRules), ugcFilterRules_1_1 = ugcFilterRules_1.next(); !ugcFilterRules_1_1.done; ugcFilterRules_1_1 = ugcFilterRules_1.next()) {
            var rule = ugcFilterRules_1_1.value;
            var regex = globToRegex(rule.selector);
            if (regex.test(pageUrl)) {
                return pageUrl.replace(regex, rule.replacement);
            }
        }
    }
    catch (e_2_1) { e_2 = { error: e_2_1 }; }
    finally {
        try {
            if (ugcFilterRules_1_1 && !ugcFilterRules_1_1.done && (_a = ugcFilterRules_1.return)) _a.call(ugcFilterRules_1);
        }
        finally { if (e_2) throw e_2.error; }
    }
    return pageUrl;
};
export var getStorageSize = function () { return __awaiter(void 0, void 0, void 0, function () {
    var globalScope, _a, usage, quota, usageDetails, totalStorageSize, percentOfQuota, e_3;
    return __generator(this, function (_b) {
        switch (_b.label) {
            case 0:
                _b.trys.push([0, 3, , 4]);
                globalScope = getGlobalScope();
                if (!globalScope) return [3 /*break*/, 2];
                return [4 /*yield*/, globalScope.navigator.storage.estimate()];
            case 1:
                _a = _b.sent(), usage = _a.usage, quota = _a.quota, usageDetails = _a.usageDetails;
                totalStorageSize = usage ? Math.round(usage / KB_SIZE) : 0;
                percentOfQuota = usage && quota ? Math.round((usage / quota + Number.EPSILON) * 1000) / 1000 : 0;
                return [2 /*return*/, { totalStorageSize: totalStorageSize, percentOfQuota: percentOfQuota, usageDetails: JSON.stringify(usageDetails) }];
            case 2: return [3 /*break*/, 4];
            case 3:
                e_3 = _b.sent();
                return [3 /*break*/, 4];
            case 4: return [2 /*return*/, { totalStorageSize: 0, percentOfQuota: 0, usageDetails: '' }];
        }
    });
}); };
export var getDebugConfig = function (config) {
    var debugConfig = __assign({}, config);
    var apiKey = debugConfig.apiKey;
    debugConfig.apiKey = "****".concat(apiKey.substring(apiKey.length - 4));
    return debugConfig;
};
//# sourceMappingURL=helpers.js.map