/*******************************************************************************

    uBlock Origin - a comprehensive, efficient content blocker
    Copyright (C) 2024-present Raymond Hill

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see {http://www.gnu.org/licenses/}.

    Home: https://github.com/gorhill/uBlock
*/

import { dom, qs$ } from './dom.js';
import { fetchJSON } from './fetch.js';
import { getEnabledRulesetsDetails } from './ruleset-manager.js';
import { i18n$ } from './i18n.js';
import { sendMessage } from './ext.js';
import { urlSkip } from './urlskip.js';

/******************************************************************************/

const rulesetDetailsPromise = getEnabledRulesetsDetails();

/******************************************************************************/

function urlToFragment(raw) {
    try {
        const fragment = new DocumentFragment();
        const url = new URL(raw);
        const hn = url.hostname;
        const i = raw.indexOf(hn);
        const b = document.createElement('b');
        b.append(hn);
        fragment.append(raw.slice(0,i), b, raw.slice(i+hn.length));
        return fragment;
    } catch {
    }
    return raw;
}

/******************************************************************************/

const toURL = new URL('about:blank');
const toFinalURL = new URL('about:blank');

try {
    toURL.href = self.location.hash.slice(1);
    toFinalURL.href = toURL.href;
} catch {
}

dom.clear('#theURL > p > span:first-of-type');
qs$('#theURL > p > span:first-of-type').append(urlToFragment(toURL.href));

/******************************************************************************/

async function proceed() {
    const permanent = qs$('#disableWarning').checked;
    // Do not exclude current hostname from strict-block ruleset if a urlskip
    // directive to another site is in effect.
    // TODO: what if the urlskip directive leads to a different subdomain on
    //       same site?
    if ( toFinalURL.hostname !== toURL.hostname && permanent !== true ) {
        return window.location.replace(toFinalURL.href);
    }
    await sendMessage({
        what: 'excludeFromStrictBlock',
        hostname: toURL.hostname,
        permanent,
    });
    window.location.replace(toURL.href);
}

/******************************************************************************/

function fragmentFromTemplate(template, placeholder, text, details) {
    const fragment = new DocumentFragment();
    const pos = template.indexOf(placeholder);
    if ( pos === -1 ) {
        fragment.append(template);
        return fragment;
    }
    const elem = document.createElement(details.tag);
    const { attributes } = details;
    if ( attributes ) {
        for ( let i = 0; i < attributes.length; i+= 2 ) {
            elem.setAttribute(attributes[i+0], attributes[i+1]);
        }
    }
    elem.append(text);
    fragment.append(
        template.slice(0, pos),
        elem,
        template.slice(pos + placeholder.length)
    );
    return fragment;
}

/******************************************************************************/

// https://github.com/gorhill/uBlock/issues/691
//   Parse URL to extract as much useful information as possible. This is
//   useful to assist the user in deciding whether to navigate to the web page.
(( ) => {
    const reURL = /^https?:\/\//;

    const liFromParam = function(name, value) {
        if ( value === '' ) {
            value = name;
            name = '';
        }
        const li = dom.create('li');
        let span = dom.create('span');
        dom.text(span, name);
        li.appendChild(span);
        if ( name !== '' && value !== '' ) {
            li.appendChild(document.createTextNode(' = '));
        }
        span = dom.create('span');
        if ( reURL.test(value) ) {
            const a = dom.create('a');
            dom.attr(a, 'href', value);
            dom.text(a, value);
            span.appendChild(a);
        } else {
            dom.text(span, value);
        }
        li.appendChild(span);
        return li;
    };

    // https://github.com/uBlockOrigin/uBlock-issues/issues/1649
    //   Limit recursion.
    const renderParams = function(parentNode, rawURL, depth = 0) {
        let url;
        try {
            url = new URL(rawURL);
        } catch {
            return false;
        }

        const search = url.search.slice(1);
        if ( search === '' ) { return false; }

        url.search = '';
        const li = liFromParam(i18n$('strictblockNoParamsPrompt'), url.href);
        parentNode.appendChild(li);

        const params = new self.URLSearchParams(search);
        for ( const [ name, value ] of params ) {
            const li = liFromParam(name, value);
            if ( depth < 2 && reURL.test(value) ) {
                const ul = dom.create('ul');
                renderParams(ul, value, depth + 1);
                li.appendChild(ul);
            }
            parentNode.appendChild(li);
        }

        return true;
    };

    if ( renderParams(qs$('#parsed'), toURL.href) === false ) { return; }

    dom.cl.remove('#toggleParse', 'hidden');

    dom.on('#toggleParse', 'click', ( ) => {
        dom.cl.toggle('#theURL', 'collapsed');
    });
})();

/******************************************************************************/

// Find which list caused the blocking.
(async ( ) => {
    const rulesetDetails = await rulesetDetailsPromise;
    let iList = -1;
    const searchInList = async i => {
        if ( iList !== -1 ) { return; }
        const rules = await fetchJSON(`/rulesets/strictblock/${rulesetDetails[i].id}`);
        if ( iList !== -1 ) { return; }
        const toHref = toURL.href;
        for ( const rule of rules ) {
            const { regexFilter, requestDomains } = rule.condition;
            let matchesDomain = requestDomains === undefined;
            if ( requestDomains ) {
                let hn = toURL.hostname;
                for (;;) {
                    if ( requestDomains.includes(hn) ) {
                        matchesDomain = true;
                        break;
                    }
                    const pos = hn.indexOf('.');
                    if ( pos === -1 ) { break; }
                    hn = hn.slice(pos+1);
                }
                if ( matchesDomain === false ) { continue; }
            }
            const re = new RegExp(regexFilter);
            const matchesRegex = re.test(toHref);
            if ( matchesDomain && matchesRegex ) {
                iList = i;
            }
        }
    };
    const toFetch = [];
    for ( let i = 0; i < rulesetDetails.length; i++ ) {
        if ( rulesetDetails[i].rules.strictblock === 0 ) { continue; }
        toFetch.push(searchInList(i));
    }
    if ( toFetch.length === 0 ) { return; }
    await Promise.all(toFetch);
    if ( iList === -1 ) { return; }
    const fragment = fragmentFromTemplate(
        i18n$('strictblockReasonSentence1'),
        '{{listname}}', rulesetDetails[iList].name,
        { tag: 'q' }
    );
    qs$('#reason').append(fragment);
    dom.attr('#reason', 'hidden', null);
})();

/******************************************************************************/

// Offer to skip redirection whenever possible
(async ( ) => {
    const rulesetDetails = await rulesetDetailsPromise;
    const toFetch = [];
    for ( const details of rulesetDetails ) {
        if ( details.rules.urlskip === 0 ) { continue; }
        toFetch.push(fetchJSON(`/rulesets/urlskip/${details.id}`));
    }
    if ( toFetch.length === 0 ) { return; }
    const urlskipLists = await Promise.all(toFetch);
    const toHn = toURL.hostname;
    const matchesHn = hn => {
        if ( toHn.endsWith(hn) === false ) { return false; }
        if ( hn.length === toHn.length ) { return true; }
        return toHn.charAt(toHn.length - hn.length - 1) === '.';
    };
    for ( const urlskips of urlskipLists ) {
        for ( const urlskip of urlskips ) {
            const re = new RegExp(urlskip.re, urlskip.c ? undefined : 'i');
            if ( re.test(toURL.href) === false ) { continue; }
            if ( urlskip.hostnames ) {
                if ( urlskip.hostnames.some(hn => matchesHn(hn)) === false ) {
                    continue;
                }
            }
            const finalURL = urlSkip(toURL.href, false, urlskip.steps);
            if ( finalURL === undefined ) { continue; }
            toFinalURL.href = finalURL;
            const fragment = fragmentFromTemplate(
                i18n$('strictblockRedirectSentence1'),
                '{{url}}', urlToFragment(finalURL),
                { tag: 'a', attributes: [ 'href', finalURL, 'class', 'code' ] }
            );
            qs$('#urlskip').append(fragment);
            dom.attr('#urlskip', 'hidden', null);
            return;
        }
    }
})();

/******************************************************************************/

// https://www.reddit.com/r/uBlockOrigin/comments/breeux/
if ( window.history.length > 1 ) {
    dom.on('#back', 'click', ( ) => { window.history.back(); });
    qs$('#bye').style.display = 'none';
} else {
    dom.on('#bye', 'click', ( ) => { window.close(); });
    qs$('#back').style.display = 'none';
}

dom.on('#disableWarning', 'change', ev => {
    const checked = ev.target.checked;
    dom.cl.toggle('[data-i18n="strictblockBack"]', 'disabled', checked);
    dom.cl.toggle('[data-i18n="strictblockClose"]', 'disabled', checked);
});

dom.on('#proceed', 'click', ( ) => { proceed(); });

dom.cl.remove(dom.body, 'loading');

/******************************************************************************/
