import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import Clipboard from "@ckeditor/ckeditor5-clipboard/src/clipboard";
import DOMPurify from 'dompurify';
import word_excerpt from "../data/word_excerpt";

// use in local dev to quickly inject a word-like html paste snippet
const testKeybind = false;

const ALLOWED_TAGS = [
    'p', 'br',
    'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
    'strong', 'b', 'em', 'i', 'u', 's', 'del', 'strike', 'sub', 'sup',
    'blockquote', 'pre', 'code',
    'ul', 'ol', 'li',
    'a',
    'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td',
];

const ALLOWED_ATTRS = {
    'a': ['href', 'target', 'rel'],
};

// Flat union of all per-tag attrs — DOMPurify pre-filters on this before the hook runs
const ALL_ALLOWED_ATTRS = [...new Set(Object.values(ALLOWED_ATTRS).flat())];

DOMPurify.addHook('uponSanitizeAttribute', (node, data) => {
    const allowed = ALLOWED_ATTRS[node.tagName.toLowerCase()] || [];
    if (!allowed.includes(data.attrName)) {
        data.keepAttr = false;
    }
});

const PURIFY_CONFIG = {
    ALLOWED_TAGS,
    ALLOWED_ATTR: ALL_ALLOWED_ATTRS,
};

export default class CustomPasteSanitize extends Plugin {
    static get requires() {
        return [Clipboard];
    }

    static get pluginName() {
        return 'CustomPasteSanitize';
    }

    init() {
        const editor = this.editor;
        const view = editor.editing.view;
        const viewDocument = view.document;
        const clipboardPlugin = editor.plugins.get('ClipboardPipeline');

        if (!editor.config.get('aaCustomPasteSanitize')) return;

        this.listenTo(viewDocument, 'clipboardInput', (evt, data) => {
            if (editor.isReadOnly) return;

            const htmlContent = data.dataTransfer.getData('text/html');
            console.log('Pasting HTML:');
            console.log(htmlContent);

            if (!htmlContent) return;

            const cleanedHtml = this._cleanHtml(htmlContent);
            const content = editor.data.htmlProcessor.toView(cleanedHtml);
            clipboardPlugin.fire('inputTransformation', {content, dataTransfer: data.dataTransfer});
            view.scrollToTheSelection();
            evt.stop();
        }, {priority: 'highest'});

        if (testKeybind) {
            editor.keystrokes.set('Ctrl+Shift+Alt+H', (keyEvtData, cancel) => {
                cancel();
                const cleanedHtml = this._cleanHtml(word_excerpt);
                const content = editor.data.htmlProcessor.toView(cleanedHtml);
                clipboardPlugin.fire('inputTransformation', {content, dataTransfer: {getData: () => ''}});
                view.scrollToTheSelection();
            });
        }
    }

    _cleanHtml(html) {
        if (html.includes('MsoListParagraph')) {
            const pre = new DOMParser().parseFromString(html, 'text/html');
            this._convertWordLists(pre);
            html = pre.body.innerHTML;
        }

        const clean = DOMPurify.sanitize(html, PURIFY_CONFIG);
        const doc = new DOMParser().parseFromString(clean, 'text/html');

        // OpenOffice wraps li content inside p tags, so strip it
        for (const p of doc.querySelectorAll('li > p')) {
            p.replaceWith(...p.childNodes);
        }

        // convert h1 to h2
        for (const h1 of doc.querySelectorAll('h1')) {
            const h2 = doc.createElement('h2');
            h2.append(...h1.childNodes);
            h1.replaceWith(h2);
        }

        return doc.body.innerHTML;
    }

    _convertWordLists(doc) {
        const processed = new Set();
        for (const para of doc.querySelectorAll('p[class*="MsoListParagraph"]')) {
            if (processed.has(para)) continue;
            processed.add(para);

            // Read the marker text before stripping — it lives inside the
            // <![if !supportLists]> block which DOMParser keeps as real DOM nodes
            const markerText = para.querySelector('span[style*="mso-list:Ignore"]')?.textContent ?? '';
            // check the starting marker to know if it is `*.` or `*)`
            const isOl = /^\s*\w+[.)]\s*$/.test(markerText);

            this._stripSupportListsBlock(para);

            const group = [para];
            for (let sib = para.nextElementSibling; sib; sib = sib.nextElementSibling) {
                if (!/MsoListParagraph/.test(sib.className || '')) break;
                this._stripSupportListsBlock(sib);
                group.push(sib);
                processed.add(sib);
            }

            const listEl = doc.createElement(isOl ? 'ol' : 'ul');
            for (const p of group) {
                const li = doc.createElement('li');
                li.append(...p.childNodes);
                listEl.appendChild(li);
            }
            group[0].replaceWith(listEl);
            for (let k = 1; k < group.length; k++) group[k].remove();
        }
    }

    _stripSupportListsBlock(el) {
        let removing = false;
        for (const node of [...el.childNodes]) {
            if (node.nodeType === Node.COMMENT_NODE && /\[if\b/.test(node.data)) {
                removing = true;
                node.remove();
            } else if (node.nodeType === Node.COMMENT_NODE && /\[endif\]/.test(node.data)) {
                removing = false;
                node.remove();
            } else if (removing) {
                node.remove();
            }
        }
    }
}
