<commit_before>class SearchManager {

    get status() {
        return this._status;
    }

    constructor() {
        this._db = {};
        this._status = {active: false, available: false, query: '', total: 0, passwords: 0, folders: 0, tags: 0, time: 0};
        this._index = null;
        this._indexFields = {
            passwords: ['website', 'username', 'url', 'type', 'password', 'notes', 'label', 'id'],
            folders  : ['label', 'type', 'id'],
            tags     : ['label', 'type', 'id']
        };
        this._domIdentifiers = {
            passwords: '<PASSWORD>',
            folders  : 'data-folder-id',
            tags     : 'data-tag-id'
        };
    }

    init() {
        if(OC.Plugins) {
            OC.Plugins.register('OCA.Search', this);
            document.querySelector('form.searchbox').style.opacity = '0';
            this._status.available = false;
        }
    }

    // noinspection JSUnusedGlobalSymbols
    attach(search) {
        search.setFilter('passwords', (q) => { this.search(q); });
    }

    search(query) {
        if(query === undefined || query.trim().length === 0) {
            this._resetSearch();
            return;
        }

        let stats        = {passwords: 0, folders: 0, tags: 0, start: new Date().getTime()},
            searchParams = SearchManager._processQuery(query),
            index        = this._getSearchIndex();
        for(let key in index) {
            if(!index.hasOwnProperty(key)) continue;
            let section    = index[key],
                identifier = this._domIdentifiers[key];

            for(let i = 0; i < section.length; i++) {
                let object = section[i],
                    el     = document.querySelector(`[${identifier}="${object.id}"]`);
                if(!el) continue;

                if(SearchManager._checkIfObjectMatchesQuery(object, searchParams)) {
                    if(el.classList.contains('search-hidden')) el.classList.remove('search-hidden');
                    el.classList.add('search-visible');
                    stats[key]++;
                } else {
                    if(el.classList.contains('search-visible')) el.classList.remove('search-visible');
                    el.classList.add('search-hidden');
                }
            }
        }
        this._updateStatus(query, stats);
    }

    /**
     *
     * @param query
     * @param stats
     * @private
     */
    _updateStatus(query, stats) {
        this._status.active = true;
        this._status.query = query;
        this._status.total = stats.passwords + stats.folders + stats.tags;
        this._status.passwords = stats.passwords;
        this._status.folders = stats.folders;
        this._status.tags = stats.tags;
        this._status.time = new Date().getTime() - stats.start;
    }

    /**
     *
     * @param entry
     * @param query
     * @returns {boolean}
     * @private
     */
    static _checkIfObjectMatchesQuery(entry, query) {
        queryLoop: for(let j = 0; j < query.length; j++) {
            let fields = query[j].field,
                search = query[j].value;

            if(fields === 'all') {
                fields = ['website', 'username', 'url', 'notes', 'label'];
            } else if(fields === 'name' || fields === 'title') {
                fields = ['label'];
            } else {
                fields = [fields];
            }

            for(let k = 0; k < fields.length; k++) {
                let field = fields[k];
                if(!entry.hasOwnProperty(field)) continue;

                if(entry[field].indexOf(search) !== -1) {
                    continue queryLoop;
                }
            }
            return false;
        }
        return true;
    }

    /**
     * Clears the search database
     */
    clearDatabase() {
        this._db = {};
        this._index = null;
        this._status.available = false;
        this._resetSearch();

        document.querySelector('form.searchbox').style.opacity = '0';
        document.getElementById('searchbox').value = '';
    }

    /**
     * Update the search database
     *
     * @param database
     */
    setDatabase(database) {
        this._db = database;
        this._index = null;
        this._resetSearch();

        if(database.passwords.length || database.folders.length || database.tags.length) {
            document.querySelector('form.searchbox').style.opacity = '1';
            document.getElementById('searchbox').value = '';
            this._status.available = true;
        }
    }

    /**
     *
     * @private
     */
    _resetSearch() {
        this._status.active = false;
        let elements = document.querySelectorAll('.search-hidden, .search-visible');

        elements.forEach((el) => {
            el.classList.remove('search-hidden');
            el.classList.remove('search-visible');
        });
    }

    /**
     *
     * @param query
     * @returns {string}
     * @private<commit_msg>Automatically repeat search if data changed<commit_after>class SearchManager {

    get status() {
        return this._status;
    }

    constructor() {
        this._db = {};
        this._status = {active: false, available: false, query: '', total: 0, passwords: 0, folders: 0, tags: 0, time: 0};
        this._index = null;
        this._indexFields = {
            passwords: ['website', 'username', 'url', 'type', 'password', 'notes', 'label', 'id'],
            folders  : ['label', 'type', 'id'],
            tags     : ['label', 'type', 'id']
        };
        this._domIdentifiers = {
            passwords: '<PASSWORD>',
            folders  : 'data-folder-id',
            tags     : 'data-tag-id'
        };
    }

    init() {
        if(OC.Plugins) {
            OC.Plugins.register('OCA.Search', this);
            document.querySelector('form.searchbox').style.opacity = '0';
            this._status.available = false;
        }
    }

    // noinspection JSUnusedGlobalSymbols
    attach(search) {
        search.setFilter('passwords', (q) => { this.search(q); });
    }

    search(query) {
        if(query === undefined || query.trim().length === 0) {
            this._resetSearch();
            return;
        }

        let stats        = {passwords: 0, folders: 0, tags: 0, start: new Date().getTime()},
            searchParams = SearchManager._processQuery(query),
            index        = this._getSearchIndex();
        for(let key in index) {
            if(!index.hasOwnProperty(key)) continue;
            let section    = index[key],
                identifier = this._domIdentifiers[key];

            for(let i = 0; i < section.length; i++) {
                let object = section[i],
                    el     = document.querySelector(`[${identifier}="${object.id}"]`);
                if(!el) continue;

                if(SearchManager._checkIfObjectMatchesQuery(object, searchParams)) {
                    if(el.classList.contains('search-hidden')) el.classList.remove('search-hidden');
                    el.classList.add('search-visible');
                    stats[key]++;
                } else {
                    if(el.classList.contains('search-visible')) el.classList.remove('search-visible');
                    el.classList.add('search-hidden');
                }
            }
        }
        this._updateStatus(query, stats);
    }

    /**
     *
     * @param query
     * @param stats
     * @private
     */
    _updateStatus(query, stats) {
        this._status.active = true;
        this._status.query = query;
        this._status.total = stats.passwords + stats.folders + stats.tags;
        this._status.passwords = stats.passwords;
        this._status.folders = stats.folders;
        this._status.tags = stats.tags;
        this._status.time = new Date().getTime() - stats.start;
    }

    /**
     *
     * @param entry
     * @param query
     * @returns {boolean}
     * @private
     */
    static _checkIfObjectMatchesQuery(entry, query) {
        queryLoop: for(let j = 0; j < query.length; j++) {
            let fields = query[j].field,
                search = query[j].value;

            if(fields === 'all') {
                fields = ['website', 'username', 'url', 'notes', 'label'];
            } else if(fields === 'name' || fields === 'title') {
                fields = ['label'];
            } else {
                fields = [fields];
            }

            for(let k = 0; k < fields.length; k++) {
                let field = fields[k];
                if(!entry.hasOwnProperty(field)) continue;

                if(entry[field].indexOf(search) !== -1) {
                    continue queryLoop;
                }
            }
            return false;
        }
        return true;
    }

    /**
     * Clears the search database
     */
    clearDatabase() {
        this._db = {};
        this._index = null;
        this._status.available = false;
        this._resetSearch();

        document.querySelector('form.searchbox').style.opacity = '0';
        document.getElementById('searchbox').value = '';
    }

    /**
     * Update the search database
     *
     * @param database
     */
    setDatabase(database) {
        this._db = database;
        this._index = null;
        this._resetSearch();

        if(database.passwords.length || database.folders.length || database.tags.length) {
            document.querySelector('form.searchbox').style.opacity = '1';
            document.getElementById('searchbox').value = '';
            this._status.available = true;
            this.search(this._status.query);
        }
    }

    /**
     *
     * @private
     */
    _resetSearch() {
        this._status.active = false;
        let elements = document.querySelectorAll('.search-hidden, .search-visible');

        elements.forEach((el) => {
            el.classList.remove('search-hidden');
            el.classList.remove('search-visible');
        });
    }

    /**
     *
     * @param query
     * @returns {string}
     * @private
{'fraction_non_alphanumeric': 0.14597231902817884, 'fraction_numerical': 0.003186298914666932, 'mean_word_length': 1.6445497630331753, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2991329', 'n_tokens_mistral': 2821, 'n_tokens_neox': 2736, 'n_words': 519}