/* Essentials YOOtheme Pro 2.2.16 build 0813.1608; ZOOlanders https://www.zoolanders.com; Copyright (C) Joolanders, SL; http://www.gnu.org/licenses/gpl.html GNU/GPL */

import Vue from 'vue';
import { uuid } from '../util';
import { deepCopy } from '../../../../../util';
import { EditorState } from '@codemirror/state';
import { composed } from '../state';
import { Decoration, EditorView, MatchDecorator, ViewPlugin } from '@codemirror/view';
import SourceWidget from './SourceWidget';

const removedWidgetsData = {};
const regexp = /\{\{ sources\.(\w{4}) \}\}/g;

const SourceMatcher = new MatchDecorator({
    regexp,
    decoration: (match, view, pos) =>
        Decoration.replace({
            widget: new SourceWidget({
                id: match[1],
                range: {
                    from: pos,
                    to: pos + match[0].length,
                },
            }),
        }),
});

const handleDuplicates = EditorState.transactionFilter.of((tr) => {
    const { sources } = tr.state.field(composed);

    const inputPaste = tr.annotations.find((e) => e.value === 'input.paste');

    if (inputPaste) {
        const doc = tr.newDoc.toString();
        const duplicates = getDuplicates(matchAll(doc, regexp));

        const changes = [];

        duplicates.forEach((id) => {
            const regexp = new RegExp(`{{ sources.${id} }}`);
            const match = doc.match(regexp);
            const newId = uuid();

            sources[newId] = deepCopy(sources[id]);

            // replace id
            changes.push({
                from: match.index,
                to: match.index + match[0].length,
                insert: `{{ sources.${newId} }}`,
            });
        });

        if (changes) {
            return [tr, { changes }];
        }
    }

    return [tr];
});

const handleDeletions = EditorState.transactionExtender.of((tr) => {
    if (!tr.docChanged) {
        return [tr];
    }

    const { sources } = tr.state.field(composed);
    const widgets = matchAll(tr.startState.doc.toString(), regexp);
    const newWidgets = matchAll(tr.newDoc.toString(), regexp);

    const removed = widgets.filter((x) => newWidgets.indexOf(x) === -1);
    const added = newWidgets.filter((x) => widgets.indexOf(x) === -1);

    if (removed?.length) {
        removed.forEach((id) => {
            removedWidgetsData[id] = sources[id];
            delete sources[id];
        });
    }

    if (added?.length) {
        added.forEach((id) => {
            sources[id] = sources[id] || removedWidgetsData[id] || {};
        });
    }

    return [tr];
});

export default ViewPlugin.fromClass(
    class {
        decorations;
        constructor(view) {
            this.decorations = SourceMatcher.createDeco(view);

            const { sources } = view.viewState.state.field(composed);
            const widgetIds = [];

            this.decorations.between(0, view.state.doc.length, (from, to, { widget }) => {
                widgetIds.push(widget.id);

                // init sources value, if missing
                if (!sources[widget.id]) {
                    Vue.set(sources, widget.id, {});
                }
            });

            // remove orphan sources
            for (let id in sources) {
                if (!widgetIds.includes(id)) {
                    delete sources[id];
                }
            }
        }
        update(update) {
            this.decorations = SourceMatcher.updateDeco(update, this.decorations);
        }
    },
    {
        decorations: (v) => v.decorations,
        provide: (plugin) => [
            EditorView.atomicRanges.of((view) => {
                return view.plugin(plugin)?.decorations || Decoration.none;
            }),
            handleDeletions,
            handleDuplicates,
        ],
    }
);

function matchAll(doc, regexp) {
    let match;
    const matches = [];

    while ((match = regexp.exec(doc)) !== null) {
        matches.push(match[1]);
    }

    return matches;
}

function getDuplicates(array) {
    return array.filter((item, index, self) => self.indexOf(item) !== index);
}
