const gunzip = async data => {
    const readableStream = new Blob([data]).stream();
    const decompressedStream = readableStream.pipeThrough(
        new DecompressionStream('gzip'),
    );
    const arrayBuffer = await new Response(decompressedStream).arrayBuffer();
    return arrayBuffer;
};
const base64ToBytes = base64 => {
    const binString = atob(base64);
    return Uint8Array.from(binString, (m) => m.charCodeAt(0));
}
const isSafeHref = (v) => {
    return typeof v === "string" &&
        !/^[a-z][a-z0-9+.-]*:/i.test(v) &&
        !v.startsWith("//") &&
        /^[A-Za-z0-9._/@%()-]+\.html$/.test(v);
};
const sanitizeHTML = (html) => {
    const template = document.createElement("template");
    template.innerHTML = html;

    const container = document.createElement("div");

    template.content.querySelectorAll("div.itempage").forEach(srcPage => {
        const dstPage = document.createElement("div");
        dstPage.className = "itempage";

        const style = srcPage.getAttribute("style");
        if(style){
            const m = style.match(/display\s*:\s*(none|block|inline|inline-block)\s*;?/i);
            if(m){
                dstPage.style.display = m[1].toLowerCase();
            }
        }

        const srcTitle = srcPage.querySelector("div.itemtitle");
        if(srcTitle){
            const dstTitle = document.createElement("div");
            dstTitle.className = "itemtitle";

            const srcA = srcTitle.querySelector("a");
            if(srcA){
                const a = document.createElement("a");

                const href = srcA.getAttribute("href");
                const dataHref = srcA.getAttribute("data-href");

                if(isSafeHref(href)) a.setAttribute("href", href);
                if(isSafeHref(dataHref)) a.setAttribute("data-href", dataHref);

                if(srcA.getAttribute("target") === "content"){
                    a.setAttribute("target", "content");
                }
                a.textContent = srcA.textContent;
                dstTitle.appendChild(a);
            }
            dstPage.appendChild(dstTitle);
        }

        const dstMatched = document.createElement("div");
        dstMatched.className = "itemmatched";
        dstPage.appendChild(dstMatched);

        const srcBody = srcPage.querySelector("div.itembody");
        if(srcBody){
            const dstBody = document.createElement("div");
            dstBody.className = "itembody";
            dstBody.textContent = srcBody.textContent;
            dstPage.appendChild(dstBody);
        }
        container.appendChild(dstPage);
    });

    return container.innerHTML;
};
const normalizeQuery = (q) => {
    if(typeof q !== "string") return null;
    q = q.trim();
    if(q.length === 0) return null;
    if(q.length > 100) return null;
    return q;
};
const initSearch = async () => {
    try{
        const gzipb64 = $("#gbi").text().trim();
        if(gzipb64.length > 0){
            const gzip = base64ToBytes(gzipb64);
            const arrayBuffer = await gunzip(gzip);
            const html = new TextDecoder().decode(arrayBuffer);
            const safeHTML = sanitizeHTML(html);
            $("#pagelist").html(safeHTML);
            $("#gbi").remove();
        }
    } catch (e) {
        console.error(e);
        $("#msg").text("初期化に失敗しました。");
        return;
    }

    document.addEventListener("submit", function(e){
        e.preventDefault();
    }, false);

    const pages = $(".itempage");
    const matcheds = $(".itemmatched");

    const get_query = () => normalizeQuery($("#query").val());
    const pp = (query, case_sensitive) => case_sensitive ? query : query.toLowerCase();
    const is_matched = (str, query, case_sensitive) => pp(str, case_sensitive).includes(pp(query, case_sensitive));
    const do_mark = (jq, query, case_sensitive) => {
        try{
            jq.mark(query, {separateWordSearch: false, caseSensitive: case_sensitive});
        }catch(e){
            console.error(e);
        }
    };
    const show_matched_lines = function(str, query, case_sensitive, jq){
        const lines = str.split("\n");
        const results = [];
        for(let i = 0; i < lines.length; i++){
            if(is_matched(lines[i], query, case_sensitive)){
                results.push(lines[i]);
                break;
            }
        }
        jq.text(results.join("\n"));
        jq.show();
    }
    let lock = false;
    const do_search = function(){
        if (lock) return;
        lock = true;
        setTimeout(() => lock = false, 200);
        
        const case_sensitive = $("#casesensitive").is(":checked")
        pages.hide();
        matcheds.text("");
        $("#msg").text("");
        let count = 0;
        const query = get_query();
        if(!query){
            sessionStorage.removeItem("query");
            pages.filter(":visible").hide();
            $("#query").val("").focus();
            return;
        }else{
            sessionStorage.setItem("query", query);
            sessionStorage.setItem("casesensitive", case_sensitive);
            pages.each(function(){
                const itembody = $(this).children(".itembody").text();
                if(is_matched(itembody, query, case_sensitive)){
                    $(this).show().children(".itemtitle").show();
                    show_matched_lines(itembody, query, case_sensitive, $(this).children(".itemmatched"));
                    count++;
                }
            });
            $("div.itemtitle a").each(function(){
                const href = $(this).attr("data-href");
                if(isSafeHref(href)){
                    $(this).attr("href", `${href}?search=true`);
                }else{
                    $(this).removeAttr("href");
                }
            });
        }
        $("#msg").text(`${count}ページ見つかりました`);
        do_mark($(".itemmatched"), query, case_sensitive);
        $("#query").val(query).focus();
    }
    $("#search").click(do_search).attr("disabled", false).val("検索");
    $("#query").attr("disabled", false).focus();
    $("form").submit(do_search);
    $("#casesensitive").prop("checked", sessionStorage.getItem("casesensitive") === "true" ? true : false).click(function(){
        sessionStorage.setItem("casesensitive", $(this).is(":checked"));
        if(get_query() === sessionStorage.getItem("query")){
            do_search();
        }
    });
}
$(function(){
    initSearch();
});
