let {{JS_PREFIX}}_doc_ready = false;
const {{JS_PREFIX}}_request_id = '{{BUBBLE_REQUEST_ID}}';
const {{JS_PREFIX}}_interval = 100;
const {{JS_PREFIX}}_idle_interval = 15000;

const {{JS_PREFIX}}_blacklist = {{{BUBBLE_BLACKLIST_JSON}}};
const {{JS_PREFIX}}_whitelist = {{{BUBBLE_WHITELIST_JSON}}};

const {{JS_PREFIX}}_filters = {{{BUBBLE_SELECTORS_JSON}}};

function {{JS_PREFIX}}_check_entry_exclusions_and_types(entry, url, type) {
    if (typeof entry.domainExclusions !== 'undefined' && entry.domainExclusions !== null) {
        for (let i=0; i<entry.domainExclusions.length; i++) {
            if (entry.domainExclusions[i] === url.hostname) return false;
        }
    }
    if (typeof type !== 'undefined' && type !== null) {
        if (typeof entry.typeExclusions !== 'undefined' && entry.typeExclusions !== null) {
            for (let i=0; i<entry.typeExclusions.length; i++) {
                if (entry.typeExclusions[i] === type) return false;
            }
        }
        if (typeof entry.typeMatches !== 'undefined' && entry.typeMatches !== null) {
            for (let i=0; i<entry.typeExclusions.length; i++) {
                if (entry.typeMatches[i] === type) return true;
            }
            return false;
        }
    }
    return true;
}

function {{JS_PREFIX}}_entry_matches(entry, url, type) {
    const target = entry.target;
    if (typeof target.domainRegex !== 'undefined' && target.domainRegex !== null && url.hostname.match(target.domainRegex) !== null) {
        return {{JS_PREFIX}}_check_entry_exclusions_and_types(entry, url, type);
    }
    if (typeof target.regex !== 'undefined' && target.regex !== null && (url.hostname.match(target.regex) !== null || url.pathname.match(target.regex) !== null)) {
        return {{JS_PREFIX}}_check_entry_exclusions_and_types(entry, url, type);
    }
}

function {{JS_PREFIX}}_block_target(url, type) {
    if (typeof url === 'undefined' || url == null) return false;
    let u;
    try {
        u = new URL(url);
    } catch (e) {
        return false;
    }
    if (typeof type === 'undefined' || type === null) {
        if (u.pathname.endsWith('.png')
            || u.pathname.endsWith('.gif')
            || u.pathname.endsWith('.jpeg')
            || u.pathname.endsWith('.jpg')) {
            type = 'image';
        } else if (u.pathname.endsWith('.css')) {
            type = 'stylesheet';
        } else if (u.pathname.endsWith('.js')) {
            type = 'script';
        } else {
            type = null;
        }
    }
    for (let i=0; i<{{JS_PREFIX}}_whitelist.length; i++) {
        const entry = {{JS_PREFIX}}_whitelist[i];
        if ({{JS_PREFIX}}_entry_matches(entry, u, type)) return false;
    }
    for (let i=0; i<{{JS_PREFIX}}_blacklist.length; i++) {
        const entry = {{JS_PREFIX}}_blacklist[i];
        if ({{JS_PREFIX}}_entry_matches(entry, u, type)) return true;
    }
    return false;
}

// // this seems to slow down pages quite a bit. disabling until we can find a better/faster way
// window.f = Element.prototype.appendChild;
// Element.prototype.appendChild = function() {
//     let type = null;
//     if (arguments[0] instanceof HTMLImageElement) {
//         type = 'image';
//     }
//     if (typeof arguments[0].src !== 'undefined') {
//         if ({{JS_PREFIX}}_block_target(arguments[0].src, type)) {
//             console.log('BLOCKING appendChild for '+arguments[0]+', src='+arguments[0].src);
//             return;
//         }
//     }
//     if (typeof arguments[0].href !== 'undefined') {
//         if ({{JS_PREFIX}}_block_target(arguments[0].href, type)) {
//             console.log('BLOCKING appendChild for '+arguments[0]+', src='+arguments[0].href);
//             return;
//         }
//     }
//     return window.f.apply(this, arguments);
// };

function {{JS_PREFIX}}_onReady(callback) {
    const intervalId = window.setInterval(function() {
        if (document.getElementsByTagName('body')[0] !== undefined) {
            {{JS_PREFIX}}_doc_ready = true;
            window.clearInterval(intervalId);
            callback.call(this);
        }
    }, {{JS_PREFIX}}_interval);
}

function {{JS_PREFIX}}_style_prop_match(e, styleVal, comparison, value) {
    if (styleVal === null || styleVal === '') return false;
    switch (comparison) {
        case 'equals': case 'exact':
            return styleVal.toLowerCase() === value.toLowerCase();
        case 'startsWith':
            return styleVal.toLowerCase().startsWith(value.toLowerCase());
        case 'endsWith':
            return styleVal.toLowerCase().endsWith(value.toLowerCase());
        case 'contains':
            return styleVal.toLowerCase().includes(value.toLowerCase());
        case 'wildcard':
            return styleVal.toLowerCase().match(value.toLowerCase()) !== null;
    }
    return false;
}

function {{JS_PREFIX}}_literal_styles_string(e) {
    const matches = new RegExp(/.+?\s+style\s*=\s*"([^"]+)"/g).exec(e.outerHTML);
    if (matches !== null && matches.length >= 1) {
        return matches[1];
    }
    return '';
}

function {{JS_PREFIX}}_literal_styles(e) {
    const styles = [];
    const literal = {{JS_PREFIX}}_literal_styles_string(e).split(';');
    for (let i=0; i<literal.length; i++) {
        const prop = literal[i].trim();
        if (prop.length > 0) {
            const colon = prop.indexOf(':');
            if (colon !== -1 && colon !== 0 && colon !== prop.length-1) {
                styles.push({name: prop.substring(0, colon).trim(), value: prop.substring(colon+1).trim()});
            }
        }
    }
    return styles;
}

function {{JS_PREFIX}}_style_match(e, comparison, propName, propValue) {
    if (comparison === 'regex') {
        if (e.style.cssText.toLowerCase().match(propValue.toLowerCase()) !== null) return true;
        return {{JS_PREFIX}}_literal_styles_string(e).toLowerCase().match(propValue.toLowerCase()) !== null;
    }
    const style = window.getComputedStyle(e);
    for (let i=0; i<style.length; i++) {
        if (style[i].toLowerCase() === propName.toLowerCase()) {
            let styleVal = style[style[i]];
            if ({{JS_PREFIX}}_style_prop_match(e, styleVal, comparison, propValue)) return true;
        }
    }
    const literalStyles = {{JS_PREFIX}}_literal_styles(e);
    for (let i=0; i<literalStyles.length; i++) {
        if (literalStyles[i].name.toLowerCase() === propName.toLowerCase()) {
            let styleVal = literalStyles[i].value;
            if ({{JS_PREFIX}}_style_prop_match(e, styleVal, comparison, propValue)) return true;
        }
    }
    return false;
}

function {{JS_PREFIX}}_attribute_match(e, attr) {
    if (attr.style) {
        for (let i=0; i<attr.style.length; i++) {
            const prop = attr.style[i];
            if (!{{JS_PREFIX}}_style_match(e, attr.comparison, prop.name, prop.value)) return false;
        }
        return true;

    } else {
        if (!e.hasAttribute(attr.name)) return false;
        const attrVal = e.getAttribute(attr.name);
        switch (attr.comparison) {
            case 'equals':
                return '' + attrVal === attr.value;
            case 'startsWith':
                return (''+attrVal).toLowerCase().startsWith(attr.value.toLowerCase());
            case 'endsWith':
                return (''+attrVal).toLowerCase().endsWith(attr.value.toLowerCase());
            case 'contains':
                return (''+attrVal).toLowerCase().includes(attr.value.toLowerCase());
        }
    }
    return false;
}

function {{JS_PREFIX}}_abp_match_properties(e, props) {
    for (let i=0; i<props.length; i++) {
        const prop = props[i];
        if (!{{JS_PREFIX}}_style_match(e, prop.type, prop.name, prop.value)) return false;
    }
    return true;
}

function {{JS_PREFIX}}_abp_match_contains(e, contains) {
    switch (contains.type) {
        case 'literal': return e.innerText.includes(contains.value);
        case 'regex': return e.innerText.toLowerCase().match(contains.value.toLowerCase()) !== null;
        case 'selector': return {{JS_PREFIX}}_process_filter(contains.selector, e).length > 0;
        default: return false;
    }
}

function {{JS_PREFIX}}_has_class(e, cls) {
    if (e === null || typeof e.classList === 'undefined' || e.classList === null || e.classList.length === 0) return false;
    for (let i=0; i<e.classList.length; i++) {
        if (e.classList[i].toLowerCase() === cls.toLowerCase()) {
            return true;
        }
    }
    return false;
}

function {{JS_PREFIX}}_abp_match(e, abp) {
    switch (abp.type) {
        case 'properties': return {{JS_PREFIX}}_abp_match_properties(e, abp.properties);
        case 'contains': return {{JS_PREFIX}}_abp_match_contains(e, abp.contains);
        case 'has': return {{JS_PREFIX}}_process_filter(abp.selector, e).length > 0;
        default: return false;
    }
}

function {{JS_PREFIX}}_process_filter(f, startElement) {
    let elements = null;
    let candidates = [];
    switch (f.type) {
        case 'cls':
            elements = startElement.getElementsByClassName(f.name);
            for (let i=0; i<elements.length; i++) {
                candidates.push(elements[i]);
            }
            break;
        case 'tag':
            elements = startElement.getElementsByTagName(f.name);
            for (let i=0; i<elements.length; i++) {
                candidates.push(elements[i]);
            }
            break;
        case 'id':
            const e = document.getElementById(f.name);
            if (e !== null) candidates = [ e ];
            break;
        case 'id_and_cls':
            let elt = document.getElementById(f.name);
            if (elt !== null) {
                if ({{JS_PREFIX}}_has_class(elt, f.cls)) candidates.push(elt);
            }
            break;
        case 'tag_and_cls':
            elements = startElement.getElementsByTagName(f.name);
            for (let i=0; i<elements.length; i++) {
                const e = elements[i];
                if ({{JS_PREFIX}}_has_class(e, f.cls)) candidates.push(e);
            }
            break;
        default: return [];
    }

    if (candidates.length === 0) return candidates;
    if (typeof f.attributes !== 'undefined') {
        for (let i=0; i<f.attributes.length; i++) {
            candidates = candidates.filter(e => {{JS_PREFIX}}_attribute_match(e, f.attributes[i]));
        }
    }

    if (candidates.length === 0) return candidates;
    if (typeof f.abp !== 'undefined') {
        candidates = candidates.filter(e => {{JS_PREFIX}}_abp_match(e, f.abp));
    }

    if (candidates.length === 0) return candidates;
    if (typeof f.operator !== 'undefined') {
        switch (f.operator) {
            case 'next':
                candidates = candidates.filter(e => {{JS_PREFIX}}_process_filter(f.next, e.nextSibling).length > 0);
                break;
            case 'encloses':
                candidates = candidates.filter(e => {{JS_PREFIX}}_process_filter(f.next, e).length > 0);
                break;
        }
    }

    return candidates;
}

function {{JS_PREFIX}}_process_filters() {
    for (let i=0; i<{{JS_PREFIX}}_filters.length; i++) {
        let filter = {{JS_PREFIX}}_filters[i];
        const elementsToRemove = {{JS_PREFIX}}_process_filter(filter, document);
        for (let i=0; i<elementsToRemove.length; i++) {
            const e = elementsToRemove[i];
            console.log('removing element: '+e.outerHTML);
            e.parentNode.removeChild(e);
        }
    }
    console.log('{{JS_PREFIX}}_process_filters: finished processing '+{{JS_PREFIX}}_filters.length+' filters');

    const images = document.getElementsByTagName('img');
    for (let i=0; i<images.length; i++) {
        const image = images[i];
        if ({{JS_PREFIX}}_block_target(image.src, 'image')) {
            console.log('removing image: '+image.outerHTML);
            image.parentNode.removeChild(image);
        }
    }
    const anchors = document.getElementsByTagName('a');
    for (let i=0; i<anchors.length; i++) {
        const anchor = anchors[i];
        if ({{JS_PREFIX}}_block_target(anchor.href)) {
            console.log('removing anchor: '+anchor.outerHTML);
            anchor.parentNode.removeChild(anchor);
        }
    }

}

{{JS_PREFIX}}_onReady(function() {
    {{JS_PREFIX}}_process_filters();
    window.setInterval({{JS_PREFIX}}_process_filters, {{JS_PREFIX}}_idle_interval);
});

{{{BLOCK_STATS_JS}}}