import {App, editorViewField, MarkdownView, TFile} from "obsidian";
import {SuperchargedLinksSettings} from "../settings/SuperchargedLinksSettings";
import {Decoration, DecorationSet, EditorView, ViewPlugin, ViewUpdate, WidgetType} from "@codemirror/view";
import {RangeSetBuilder} from "@codemirror/rangeset";
import {syntaxTree} from "@codemirror/language";
import {tokenClassNodeProp} from "@codemirror/stream-parser";
import {fetchTargetAttributesSync} from "./linkAttributes";

export function buildCMViewPlugin(app: App, _settings: SuperchargedLinksSettings)
{
    // Implements the live preview supercharging
    // Code structure based on https://github.com/nothingislost/obsidian-cm6-attributes/blob/743d71b0aa616407149a0b6ea5ffea28e2154158/src/main.ts
    // Code help credits to @NothingIsLost! They have been a great help getting this to work properly.
    class HeaderWidget extends WidgetType {
        attributes: Record<string, string>
        after: boolean

        constructor(attributes: Record<string, string>, after: boolean) {
            super();
            this.attributes = attributes
            this.after = after
        }

        toDOM() {
            let headerEl = document.createElement("span");
            headerEl.setAttrs(this.attributes);
            if (this.after) {
                headerEl.addClass('data-link-icon-after');
            }
            else {
                headerEl.addClass('data-link-icon')
            }
            // create a naive bread crumb
            return headerEl;
        }

        ignoreEvent() {
            return true;
        }
    }

    const settings = _settings;
    const viewPlugin = ViewPlugin.fromClass(
        class {
            decorations: DecorationSet;

            constructor(view: EditorView) {
                this.decorations = this.buildDecorations(view);
            }

            update(update: ViewUpdate) {
                if (update.docChanged || update.viewportChanged) {
                    this.decorations = this.buildDecorations(update.view);
                }
            }

            destroy() {
            }

            buildDecorations(view: EditorView) {
                let builder = new RangeSetBuilder<Decoration>();
                if (!settings.enableEditor) {
                    return builder.finish();
                }
                const mdView = view.state.field(editorViewField) as MarkdownView;
                let lastAttributes = {};
                let iconDecoAfter: Decoration = null;
                let iconDecoAfterWhere: number = null;

                let mdAliasFrom: number = null;
                let mdAliasTo: number = null;
                for (let {from, to} of view.visibleRanges) {
                    syntaxTree(view.state).iterate({
                        from,
                        to,
                        enter: (type, from, to) => {


                            const tokenProps = type.prop(tokenClassNodeProp);
                            if (tokenProps) {
                                const props = new Set(tokenProps.split(" "));
                                const isLink = props.has("hmd-internal-link");
                                const isAlias = props.has("link-alias");
                                const isPipe = props.has("link-alias-pipe");

                                // The 'alias' of the md link
                                const isMDLink = props.has('link');
                                // The 'internal link' of the md link
                                const isMDUrl = props.has('url');
                                const isMDFormatting = props.has('formatting-link');

                                if (isMDLink && !isMDFormatting) {
                                    // Link: The 'alias'
                                    // URL: The internal link
                                    mdAliasFrom = from;
                                    mdAliasTo = to;
                                }

                                if (!isPipe && !isAlias) {
                                    if (iconDecoAfter) {
                                        builder.add(iconDecoAfterWhere, iconDecoAfterWhere, iconDecoAfter);
                                        iconDecoAfter = null;
                                        iconDecoAfterWhere = null;
                                    }
                                }
                                if (isLink && !isAlias && !isPipe || isMDUrl) {
                                    let linkText = view.state.doc.sliceString(from, to);
                                    linkText = linkText.split("#")[0];
                                    let file = app.metadataCache.getFirstLinkpathDest(linkText, mdView.file.basename);
                                    if (isMDUrl && !file) {
                                        try {
                                            file = app.vault.getAbstractFileByPath(decodeURIComponent(linkText)) as TFile;
                                        }
                                        catch(e) {}
                                    }
                                    if (file) {
                                        let _attributes = fetchTargetAttributesSync(app, settings, file, true);
                                        let attributes: Record<string, string> = {};
                                        for (let key in _attributes) {
                                            attributes["data-link-" + key] = _attributes[key];
                                        }
                                        let deco = Decoration.mark({
                                            attributes,
                                            class: "data-link-text"
                                        });
                                        let iconDecoBefore = Decoration.widget({
                                            widget: new HeaderWidget(attributes, false),
                                        });
                                        iconDecoAfter = Decoration.widget({
                                            widget: new HeaderWidget(attributes, true),
                                        });

                                        if (isMDUrl) {
                                            // Apply retroactively to the alias found before
                                            let deco = Decoration.mark({
                                                attributes: attributes,
                                                class: "data-link-text"
                                            });
                                            builder.add(mdAliasFrom, mdAliasFrom, iconDecoBefore);
                                            builder.add(mdAliasFrom, mdAliasTo, deco);
                                            if (iconDecoAfter) {
                                                builder.add(mdAliasTo, mdAliasTo, iconDecoAfter);
                                                iconDecoAfter = null;
                                                iconDecoAfterWhere = null;
                                                mdAliasFrom = null;
                                                mdAliasTo = null;
                                            }
                                        }
                                        else {
                                            builder.add(from, from, iconDecoBefore);
                                        }

                                        builder.add(from, to, deco);
                                        lastAttributes = attributes;
                                        iconDecoAfterWhere = to;
                                    }
                                } else if (isLink && isAlias) {
                                    let deco = Decoration.mark({
                                        attributes: lastAttributes,
                                        class: "data-link-text"
                                    });
                                    builder.add(from, to, deco);
                                    if (iconDecoAfter) {
                                        builder.add(to, to, iconDecoAfter);
                                        iconDecoAfter = null;
                                        iconDecoAfterWhere = null;
                                    }
                                }
                            }
                        }
                    })

                }
                return builder.finish();
            }
        },
        {
            decorations: v => v.decorations
        }
    );
    return viewPlugin;
}