/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import lunr from "@generated/lunr.client";
lunr.tokenizer.separator = /[\s\-/]+/;

class LunrSearchAdapter {
    constructor(searchDocs, searchIndex) {
        this.searchDocs = searchDocs;
        this.lunrIndex = lunr.Index.load(searchIndex);
    }

    getLunrResult(input) {
        return this.lunrIndex.query(function (query) {
            const tokens = lunr.tokenizer(input);
            query.term(tokens, {
                boost: 10
            });
            query.term(tokens, {
                wildcard: lunr.Query.wildcard.TRAILING
            });
        });
    }

    getHit(doc, formattedTitle, formattedContent) {
        return {
            hierarchy: {
                lvl0: doc.pageTitle || doc.title,
                lvl1: doc.type === 0 ? null : doc.title
            },
            url: doc.url,
            _snippetResult: formattedContent ? {
                content: {
                    value: formattedContent,
                    matchLevel: "full"
                }
            } : null,
            _highlightResult: {
                hierarchy: {
                    lvl0: {
                        value: doc.type === 0 ? formattedTitle || doc.title : doc.pageTitle,
                    },
                    lvl1:
                        doc.type === 0
                            ? null
                            : {
                                value: formattedTitle || doc.title
                            }
                }
            }
        };
    }
    getTitleHit(doc, position, length) {
        const start = position[0];
        const end = position[0] + length;
        let formattedTitle = doc.title.substring(0, start) + '<span class="algolia-docsearch-suggestion--highlight">' + doc.title.substring(start, end) + '</span>' + doc.title.substring(end, doc.title.length);
        return this.getHit(doc, formattedTitle)
    }

    getKeywordHit(doc, position, length) {
        const start = position[0];
        const end = position[0] + length;
        let formattedTitle = doc.title + '<br /><i>Keywords: ' + doc.keywords.substring(0, start) + '<span class="algolia-docsearch-suggestion--highlight">' + doc.keywords.substring(start, end) + '</span>' + doc.keywords.substring(end, doc.keywords.length) + '</i>'
        return this.getHit(doc, formattedTitle)
    }

    getContentHit(doc, position) {
        const start = position[0];
        const end = position[0] + position[1];
        let previewStart = start;
        let previewEnd = end;
        let ellipsesBefore = true;
        let ellipsesAfter = true;
        for (let k = 0; k < 3; k++) {
            const nextSpace = doc.content.lastIndexOf(' ', previewStart - 2);
            const nextDot = doc.content.lastIndexOf('.', previewStart - 2);
            if ((nextDot > 0) && (nextDot > nextSpace)) {
                previewStart = nextDot + 1;
                ellipsesBefore = false;
                break;
            }
            if (nextSpace < 0) {
                previewStart = 0;
                ellipsesBefore = false;
                break;
            }
            previewStart = nextSpace + 1;
        }
        for (let k = 0; k < 10; k++) {
            const nextSpace = doc.content.indexOf(' ', previewEnd + 1);
            const nextDot = doc.content.indexOf('.', previewEnd + 1);
            if ((nextDot > 0) && (nextDot < nextSpace)) {
                previewEnd = nextDot;
                ellipsesAfter = false;
                break;
            }
            if (nextSpace < 0) {
                previewEnd = doc.content.length;
                ellipsesAfter = false;
                break;
            }
            previewEnd = nextSpace;
        }
        let preview = doc.content.substring(previewStart, start);
        if (ellipsesBefore) {
            preview = '... ' + preview;
        }
        preview += '<span class="algolia-docsearch-suggestion--highlight">' + doc.content.substring(start, end) + '</span>';
        preview += doc.content.substring(end, previewEnd);
        if (ellipsesAfter) {
            preview += ' ...';
        }
        return this.getHit(doc, null, preview);

    }
    search(input) {
        return new Promise((resolve, rej) => {
            const results = this.getLunrResult(input);
            const hits = [];
            results.length > 5 && (results.length = 5);
            this.titleHitsRes = []
            this.contentHitsRes = []
            results.forEach(result => {
                const doc = this.searchDocs[result.ref];
                const { metadata } = result.matchData;
                for (let i in metadata) {
                    if (metadata[i].title) {
                        if (!this.titleHitsRes.includes(result.ref)) {
                            const position = metadata[i].title.position[0]
                            hits.push(this.getTitleHit(doc, position, input.length));
                            this.titleHitsRes.push(result.ref);
                        }
                    } else if (metadata[i].content) {
                        const position = metadata[i].content.position[0]
                        hits.push(this.getContentHit(doc, position))
                    } else if (metadata[i].keywords) {
                        const position = metadata[i].keywords.position[0]
                        hits.push(this.getKeywordHit(doc, position, input.length));
                        this.titleHitsRes.push(result.ref);
                    }
                }
            });
            hits.length > 5 && (hits.length = 5);
            resolve(hits);
        });
    }
}

export default LunrSearchAdapter;
