import { App, MarkdownPostProcessorContext, Plugin, PluginSettingTab, Setting, ToggleComponent } from 'obsidian';

import * as pako from 'pako';

interface KrokiSettings {
    server_url: string,
    header: string;
    diagramTypes: {
        prettyName: string;
        krokiBlockName: string;
        obsidianBlockName: string;
        description: string;
        url: string;
        enabled: boolean;
        toggle: ToggleComponent;
    }[]
}

const DEFAULT_SETTINGS: KrokiSettings = {
    server_url: 'https://kroki.io/',
    header: '',
    diagramTypes: [
        { prettyName: "BlockDiag", krokiBlockName: "blockdiag", obsidianBlockName: "blockdiag", description: "block diag !!", url: "https://github.com/blockdiag/blockdiag", enabled: true, toggle: null },
        { prettyName: "BPMN", krokiBlockName: "bpmn", obsidianBlockName: "bpmn", description: "", url: "https://github.com/bpmn-io/bpmn-js", enabled: true, toggle: null },
        { prettyName: "Bytefield", krokiBlockName: "bytefield", obsidianBlockName: "bytefield", description: "", url: "https://github.com/Deep-Symmetry/bytefield-svg/", enabled: true, toggle: null },
        { prettyName: "SeqDiag", krokiBlockName: "seqdiag", obsidianBlockName: "seqdiag", description: "", url: "https://github.com/blockdiag/seqdiag", enabled: true, toggle: null },
        { prettyName: "ActDiag", krokiBlockName: "actdiag", obsidianBlockName: "actdiag", description: "", url: "https://github.com/blockdiag/actdiag", enabled: true, toggle: null },
        { prettyName: "NwDiag", krokiBlockName: "nwdiag", obsidianBlockName: "nwdiag", description: "Nw Diag !!!", url: "https://github.com/blockdiag/nwdiag", enabled: true, toggle: null },
        { prettyName: "PacketDiag", krokiBlockName: "packetdiag", obsidianBlockName: "packetdiag", description: "", url: "https://github.com/blockdiag/nwdiag", enabled: true, toggle: null },
        { prettyName: "RackDiag", krokiBlockName: "rackdiag", obsidianBlockName: "rackdiag", description: "", url: "https://github.com/blockdiag/nwdiag", enabled: true, toggle: null },
        { prettyName: "C4 with PlantUML", krokiBlockName: "c4plantuml", obsidianBlockName: "c4plantuml", description: "", url: "https://github.com/RicardoNiepel/C4-PlantUML", enabled: true, toggle: null },
        { prettyName: "Ditaa", krokiBlockName: "ditaa", obsidianBlockName: "ditaa", description: "", url: "http://ditaa.sourceforge.net/", enabled: true, toggle: null },
        { prettyName: "Erd", krokiBlockName: "erd", obsidianBlockName: "erd", description: "", url: "https://github.com/BurntSushi/erd", enabled: true, toggle: null },
        { prettyName: "Excalidraw", krokiBlockName: "excalidraw", obsidianBlockName: "excalidraw", description: "", url: "https://github.com/excalidraw/excalidraw", enabled: true, toggle: null },
        { prettyName: "GraphViz", krokiBlockName: "graphviz", obsidianBlockName: "graphviz", description: "", url: "https://www.graphviz.org/", enabled: true, toggle: null },
        { prettyName: "Mermaid", krokiBlockName: "mermaid", obsidianBlockName: "mermaid", description: "", url: "https://github.com/knsv/mermaid", enabled: false, toggle: null },
        { prettyName: "Nomnoml", krokiBlockName: "nomnoml", obsidianBlockName: "nomnoml", description: "", url: "https://github.com/skanaar/nomnoml", enabled: true, toggle: null },
        { prettyName: "Pikchr", krokiBlockName: "pikchr", obsidianBlockName: "pikchr", description: "", url: "https://github.com/drhsqlite/pikchr", enabled: true, toggle: null },
        { prettyName: "PlantUML", krokiBlockName: "plantuml", obsidianBlockName: "plantuml", description: "", url: "https://github.com/plantuml/plantuml", enabled: false, toggle: null },
        { prettyName: "Structurizr", krokiBlockName: "structurizr", obsidianBlockName: "structurizr", description: "", url: "https://structurizr.com/", enabled: true, toggle: null },
        { prettyName: "Svgbob", krokiBlockName: "svgbob", obsidianBlockName: "svgbob", description: "", url: "https://github.com/ivanceras/svgbob", enabled: true, toggle: null },
        { prettyName: "UMlet", krokiBlockName: "umlet", obsidianBlockName: "umlet", description: "", url: "https://github.com/umlet/umlet", enabled: true, toggle: null },
        { prettyName: "Vega", krokiBlockName: "vega", obsidianBlockName: "vega", description: "", url: "https://github.com/vega/vega", enabled: true, toggle: null },
        { prettyName: "Vega-Lite", krokiBlockName: "vegalite", obsidianBlockName: "vegalite", description: "", url: "https://github.com/vega/vega-lite", enabled: true, toggle: null },
        { prettyName: "WaveDrom", krokiBlockName: "wavedrom", obsidianBlockName: "wavedrom", description: "", url: "https://github.com/wavedrom/wavedrom", enabled: true, toggle: null }
    ]

}

export default class KrokiPlugin extends Plugin {
    settings: KrokiSettings;

    svgProcessor = async (diagType: string, source: string, el: HTMLElement, _: MarkdownPostProcessorContext) => {
        const dest = document.createElement('div');
        const urlPrefix = this.settings.server_url + diagType + "/svg/";
        source = source.replace(/ /gi, " ");

        // encode the source 
        // per: https://docs.kroki.io/kroki/setup/encode-diagram/#javascript
        const data = new TextEncoder().encode(source);
        const compressed = pako.deflate(data, { level: 9 });
        const encodedSource = Buffer.from(compressed)
            .toString('base64')
            .replace(/\+/g, '-').replace(/\//g, '_');

        const img = document.createElement("img");
        img.src = urlPrefix + encodedSource;
        img.useMap = "#" + encodedSource;

        const result = await fetch(urlPrefix + encodedSource, {
            method: "GET"
        });

        if (result.ok) {
            dest.innerHTML = await result.text();
            dest.children[0].setAttr("name", encodedSource);
        }
        el.appendChild(dest);
    };

    async onload(): Promise<void> {
        console.log('loading plugin kroki');
        await this.loadSettings();

        this.addSettingTab(new KrokiSettingsTab(this.app, this));

        // register a processor for each of the enabled diagram types
        for (let diagramType of this.settings.diagramTypes) {
            if (diagramType.enabled === true) {
                console.log("kroki is     enabling: " + diagramType.prettyName);
                this.registerMarkdownCodeBlockProcessor(diagramType.obsidianBlockName,
                    (source: string, el: HTMLElement, _: MarkdownPostProcessorContext) => {
                        this.svgProcessor(diagramType.krokiBlockName, source, el, _)  // this name is used to build the url, so it must be the kroki one
                    })
            } else {
                console.log("kroki is not enabling:", diagramType.prettyName);
            }
        }

    }

    onunload(): void {
        console.log('unloading plugin kroki');
    }

    async loadSettings(): Promise<void> {
        this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
    }

    async saveSettings(): Promise<void> {
        await this.saveData(this.settings);
    }
}

class KrokiSettingsTab extends PluginSettingTab {
    plugin: KrokiPlugin;

    constructor(app: App, plugin: KrokiPlugin) {
        super(app, plugin);
        this.plugin = plugin;
    }

    diagramTypeUrl(blockName: string, url: string) {
        let fragment = document.createDocumentFragment();
        let a = document.createElement('a');
        a.textContent = url
        a.setAttribute("href", url);

        fragment.append(a);
        return fragment;
    }

    display(): void {
        const { containerEl } = this;

        containerEl.empty();

        this.containerEl.createEl("h3", {
            text: "General",
        });

        new Setting(containerEl).setName("Server URL")
            .setDesc("Kroki Server URL")
            .addText(text => text.setPlaceholder(DEFAULT_SETTINGS.server_url)
                .setValue(this.plugin.settings.server_url)
                .onChange(async (value) => {
                    this.plugin.settings.server_url = ensureTrailingSlash(value);
                    await this.plugin.saveSettings();
                }
                )
            );
        new Setting(containerEl).setName("Header")
            .setDesc("Included at the head in every diagram. Useful for specifying a common theme (.puml file)")
            .addTextArea(text => {
                text.setPlaceholder("!include https://raw.githubusercontent.com/....puml\n")
                    .setValue(this.plugin.settings.header)
                    .onChange(async (value) => {
                        this.plugin.settings.header = value;
                        await this.plugin.saveSettings();
                    }
                    )
                text.inputEl.setAttr("rows", 4);
                text.inputEl.addClass("settings_area")
            }
            );

        this.containerEl.createEl("h3", {
            text: "Diagram Type (enable and 'language')",
        });
        this.containerEl.createEl("p", {
            text: "Enable each diagram type individually. If there are multiple possible processors for a given type, then you can specify an alternate name for the diagram's 'language'",
        });
        this.containerEl.createEl("p", {
            text: "NB that any changes here will require a re-load before becoming effective.",
        });

        // loop through all the diagram types
        for (var i = 0; i < this.plugin.settings.diagramTypes.length; i++) {
            let diagramType = this.plugin.settings.diagramTypes[i];
            new Setting(containerEl).setName(diagramType.prettyName)
                .setDesc(this.diagramTypeUrl(diagramType.krokiBlockName, diagramType.url))

                .addToggle((t) => {
                    t.setValue(diagramType.enabled);
                    t.onChange(async (v) => {
                        diagramType.enabled = v;
                        await this.plugin.saveSettings();
                    });
                    // save the control for this diagram along with the diagram's other data
                    diagramType.toggle = t;
                })

                .addText(text => {
                    text.setValue(diagramType.obsidianBlockName)
                        .onChange(async (value) => {
                            diagramType.obsidianBlockName = value;
                            await this.plugin.saveSettings();
                        });
                });
        }
    }
}

function ensureTrailingSlash(url: string): string {
    if (!url.endsWith('/')) {
        return url + '/';
    }
    return url;
}