/**
 * Copied from:
 * https://github.com/TryGhost/Ghost-Admin/blob/1f3d77d7230dd47a7eb5f38b90dfa510b2a16801/lib/koenig-editor/addon/options/parser-plugins.js
 * Which makes use of:
 * https://github.com/TryGhost/Ghost-Admin/blob/1f3d77d7230dd47a7eb5f38b90dfa510b2a16801/lib/koenig-editor/addon/helpers/clean-basic-html.js
 *
 * These functions are used to proces nodes during parsing from DOM -> mobiledoc
 */

import cleanBasicHtml from '@tryghost/kg-clean-basic-html';

import * as audioCard from './cards/audio';
import * as buttonCard from './cards/button';
import * as embedCard from './cards/embed';
import * as fileCard from './cards/file';
import * as headerCard from './cards/header';
import * as htmlCard from './cards/html';
import * as imageCard from './cards/image';
import * as productCard from './cards/product';
import * as softReturn from './cards/soft-return';
import * as videoCard from './cards/video';
import * as galleryCard from './cards/gallery';

export function createParserPlugins(_options = {}) {
    const defaults = {};
    const options = Object.assign({}, defaults, _options);

    if (!options.createDocument) {
        const Parser = (typeof DOMParser !== 'undefined' && DOMParser) || (typeof window !== 'undefined' && window.DOMParser);

        if (!Parser) {
            // eslint-disable-next-line ghost/ghost-custom/no-native-error
            throw new Error('createParserPlugins() must be passed a `createDocument` function as an option when used in a non-browser environment');
        }

        options.createDocument = function (html) {
            const parser = new Parser();
            return parser.parseFromString(html, 'text/html');
        };
    }

    options.cleanBasicHtml = function (html) {
        return cleanBasicHtml(html, options);
    };

    // HELPERS -----------------------------------------------------------------

    function _readFigCaptionFromNode(node, payload, selector = 'figcaption') {
        let figcaptions = Array.from(node.querySelectorAll(selector));

        if (figcaptions.length) {
            figcaptions.forEach((caption) => {
                let cleanHtml = options.cleanBasicHtml(caption.innerHTML);
                payload.caption = payload.caption ? `${payload.caption} / ${cleanHtml}` : cleanHtml;
                caption.remove(); // cleanup this processed element
            });
        }
    }

    // PLUGINS -----------------------------------------------------------------

    function kgCalloutCardToCard(node, builder, {addSection, nodeFinished}) {
        if (node.nodeType !== 1 || !node.classList.contains('kg-callout-card')) {
            return;
        }

        const emojiNode = node.querySelector('.kg-callout-emoji');
        const htmlNode = node.querySelector('.kg-callout-text');

        const backgroundColor = node.style.backgroundColor || '#F1F3F4';

        let calloutEmoji = '';
        if (emojiNode) {
            calloutEmoji = emojiNode?.textContent;
            if (calloutEmoji) {
                calloutEmoji = calloutEmoji.trim();
            }
        }

        let calloutText = htmlNode?.innerHTML || '';

        const payload = {
            calloutEmoji,
            calloutText,
            backgroundColor
        };

        const cardSection = builder.createCardSection('callout', payload);
        addSection(cardSection);
        nodeFinished();
    }

    function kgToggleCardToCard(node, builder, {addSection, nodeFinished}) {
        if (node.nodeType !== 1 || !node.classList.contains('kg-toggle-card')) {
            return;
        }

        const headingNode = node.querySelector('.kg-toggle-heading-text');
        const contentNode = node.querySelector('.kg-toggle-content');

        let toggleHeading = headingNode.innerHTML;
        let toggleContent = contentNode.innerText;

        const payload = {
            heading: toggleHeading,
            content: toggleContent
        };

        const cardSection = builder.createCardSection('toggle', payload);
        addSection(cardSection);
        nodeFinished();
    }

    // leading newlines in text nodes will add a space to the beginning of the text
    // which doesn't render correctly if we're replacing <br> with SoftReturn atoms
    // after parsing text as markdown to html
    function removeLeadingNewline(node) {
        if (node.nodeType !== 3 || node.nodeName !== '#text') {
            return;
        }

        node.nodeValue = node.nodeValue.replace(/^\n/, '');
    }

    function hrToCard(node, builder, {addSection, nodeFinished}) {
        if (node.nodeType !== 1 || node.tagName !== 'HR') {
            return;
        }

        let cardSection = builder.createCardSection('hr');
        addSection(cardSection);
        nodeFinished();
    }

    function figureToCodeCard(node, builder, {addSection, nodeFinished}) {
        if (node.nodeType !== 1 || node.tagName !== 'FIGURE') {
            return;
        }

        let pre = node.querySelector('pre');

        // If this figure doesn't have a pre tag in it
        if (!pre) {
            return;
        }

        let code = pre.querySelector('code');
        let figcaption = node.querySelector('figcaption');

        // if there's no caption the preCodeToCard plugin will pick it up instead
        if (!code || !figcaption) {
            return;
        }

        let payload = {
            code: code.textContent
        };

        _readFigCaptionFromNode(node, payload);

        let preClass = pre.getAttribute('class') || '';
        let codeClass = code.getAttribute('class') || '';
        let langRegex = /lang(?:uage)?-(.*?)(?:\s|$)/i;
        let languageMatches = preClass.match(langRegex) || codeClass.match(langRegex);
        if (languageMatches) {
            payload.language = languageMatches[1].toLowerCase();
        }

        let cardSection = builder.createCardSection('code', payload);
        addSection(cardSection);
        nodeFinished();
    }

    function preCodeToCard(node, builder, {addSection, nodeFinished}) {
        if (node.nodeType !== 1 || node.tagName !== 'PRE') {
            return;
        }

        let [codeElement] = node.children;

        if (codeElement && codeElement.tagName === 'CODE') {
            let payload = {code: codeElement.textContent};

            let preClass = node.getAttribute('class') || '';
            let codeClass = codeElement.getAttribute('class') || '';
            let langRegex = /lang(?:uage)?-(.*?)(?:\s|$)/i;
            let languageMatches = preClass.match(langRegex) || codeClass.match(langRegex);
            if (languageMatches) {
                payload.language = languageMatches[1].toLowerCase();
            }

            let cardSection = builder.createCardSection('code', payload);
            addSection(cardSection);
            nodeFinished();
        }
    }

    function figureScriptToHtmlCard(node, builder, {addSection, nodeFinished}) {
        if (node.nodeType !== 1 || node.tagName !== 'FIGURE') {
            return;
        }

        let script = node.querySelector('script');

        if (!script || !script.src.match(/^https:\/\/gist\.github\.com/)) {
            return;
        }

        let payload = {html: script.outerHTML};
        let cardSection = builder.createCardSection('html', payload);
        addSection(cardSection);
        nodeFinished();
    }

    // Nested paragraphs in blockquote are currently treated as separate blockquotes,
    // see [here](https://github.com/bustle/mobiledoc-kit/issues/715). When running migrations,
    // this is not the desired behaviour and will cause the content to lose the previous semantic.
    function blockquoteWithChildren(node) {
        if (node.nodeType !== 1 || node.tagName !== 'BLOCKQUOTE' || node.children.length < 1) {
            return;
        }

        const html = [];
        const children = Array.from(node.children);

        children.forEach((child) => {
            let nextSibling = child.nextSibling;
            let previousSibling = child.previousSibling;

            // Only add a soft-break for two sequential paragraphs.
            // Use the innerHTML only in that case, so Mobiledoc's default behaviour
            // of creating separate blockquotes doesn't apply.
            if (child.tagName === 'P' && (nextSibling && nextSibling.tagName === 'P')) {
                html.push(`${child.innerHTML}<br><br>`);
            } else if (child.tagName === 'P' && (previousSibling && previousSibling.tagName === 'P')) {
                html.push(child.innerHTML);
            } else {
                html.push(child.outerHTML);
            }
        });

        node.innerHTML = html.join('').trim();

        return;
    }

    // we store alt-style blockquotes as `aside` sections as a workaround
    // for mobiledoc not allowing arbitrary attributes on markup sections
    function altBlockquoteToAside(node) {
        if (node.nodeType !== 1 || node.tagName !== 'BLOCKQUOTE') {
            return;
        }

        if (!node.classList.contains('kg-blockquote-alt')) {
            return;
        }

        const replacementDoc = options.createDocument(`<aside>${node.innerHTML}</aside>`);
        const aside = replacementDoc.querySelector('aside');

        // bit of an ugly hack because
        // 1. node.tagName is readonly so we can't directly change it's type
        // 2. the node list of the current tree branch is not re-evaluated so removing
        //    this node, replacing it, or adding a new _sibling_ will not be picked up
        //
        // relies on mobiledoc-kit's handling of nested elements picking the
        // inner-most understandable section element when creating sections
        node.textContent = '';
        node.appendChild(aside);

        // let the default parser handle the nested aside node, keeping any formatting
        return;
    }

    function tableToHtmlCard(node, builder, {addSection, nodeFinished}) {
        if (node.nodeType !== 1 || node.tagName !== 'TABLE') {
            return;
        }

        if (node.parentNode.tagName === 'TABLE') {
            return;
        }

        let payload = {html: node.outerHTML};
        let cardSection = builder.createCardSection('html', payload);
        addSection(cardSection);
        nodeFinished();
    }

    return [
        embedCard.fromNFTEmbed(),
        embedCard.fromMixtape(options),
        htmlCard.fromKoenigCard(options),
        buttonCard.fromKoenigCard(options),
        buttonCard.fromWordpressButton(options),
        buttonCard.fromSubstackButton(options),
        kgCalloutCardToCard,
        kgToggleCardToCard,
        productCard.fromKoenigCard(options),
        audioCard.fromKoenigCard(options),
        videoCard.fromKoenigCard(options),
        fileCard.fromKoenigCard(options),
        headerCard.fromKoenigCard(options),
        blockquoteWithChildren,
        softReturn.fromBr(options),
        removeLeadingNewline,
        galleryCard.fromKoenigCard(options),
        embedCard.fromFigureBlockquote(options), // I think these can contain images
        galleryCard.fromGrafGallery(options),
        galleryCard.fromSqsGallery(options),
        imageCard.fromFigure(options),
        imageCard.fromImg(options),
        hrToCard,
        figureToCodeCard,
        preCodeToCard,
        embedCard.fromFigureIframe(options),
        embedCard.fromIframe(options), // Process iFrames without figures after ones with
        figureScriptToHtmlCard,
        altBlockquoteToAside,
        tableToHtmlCard
    ];
}
