import {MaterialIconProvider} from "@js-draw/material-icons";
import {PluginAsset, PluginFile} from "@/file";
import {JSON_MIME, STORAGE_PATH, SVG_MIME, TOOLBAR_FILENAME} from "@/const";
import Editor, {
    BackgroundComponentBackgroundType,
    BaseWidget,
    Color4,
    EditorEventType, getLocalizationTable,
    Mat33,
    Vec2,
    Viewport
} from "js-draw";
import {Dialog, getFrontend, openTab, Plugin} from "siyuan";
import {findSyncIDInProtyle, replaceSyncID} from "@/protyle";
import DrawJSPlugin from "@/index";
import {EditorOptions} from "@/config";
import 'js-draw/styles';
import {
    ErrorReporter,
    GenericSaveError, InternationalizedError, NoFileIDError, SyncIDNotFoundError,
    UnchangedProtyleError
} from "@/errors";

export class PluginEditor {

    private readonly element: HTMLElement;
    private readonly editor: Editor;

    private drawingFile: PluginAsset;
    private toolbarFile: PluginFile;

    private readonly fileID: string;
    private syncID: string;

    getElement(): HTMLElement { return this.element; }
    getEditor(): Editor { return this.editor; }
    getFileID(): string { return this.fileID; }
    getSyncID(): string { return this.syncID; }
    setSyncID(syncID: string) { this.syncID = syncID; }

    private constructor(fileID: string) {

        this.fileID = fileID;

        this.element = document.createElement("div");
        this.element.style.height = '100%';
        this.editor = new Editor(this.element, {
            localization: getLocalizationTable([window.siyuan.config.lang]),
            iconProvider: new MaterialIconProvider(),
        });

        const styleElement = document.createElement('style');
        styleElement.innerHTML = `
            canvas.wetInkCanvas {
                cursor: url('/plugins/siyuan-jsdraw-plugin/webapp/cursor.png') 3 3, none;
            }
        `;
        this.element.appendChild(styleElement);

        this.editor.dispatch(this.editor.setBackgroundStyle({ autoresize: true }), false);
        this.editor.getRootElement().style.height = '100%';

    }

    static async create(fileID: string, defaultEditorOptions: EditorOptions): Promise<PluginEditor> {

        const instance = new PluginEditor(fileID);

        await instance.genToolbar();
        let syncID = await findSyncIDInProtyle(fileID);

        if(syncID == null) {
            throw new SyncIDNotFoundError();
        }
        instance.setSyncID(syncID);
        await instance.restoreOrInitFile(defaultEditorOptions);

        return instance;

    }

    async restoreOrInitFile(defaultEditorOptions: EditorOptions) {

        this.drawingFile = new PluginAsset(this.fileID, this.syncID, SVG_MIME);
        await this.drawingFile.loadFromSiYuanFS();
        const drawingContent = this.drawingFile.getContent();

        if(drawingContent != null) {

            await this.editor.loadFromSVG(drawingContent);

            // restore position and zoom
            const svgElem = new DOMParser().parseFromString(drawingContent, SVG_MIME).documentElement;
            const editorViewStr = svgElem.getAttribute('editorView');
            if(editorViewStr != null && defaultEditorOptions.restorePosition) {
                try {
                    const [viewBoxOriginX, viewBoxOriginY, zoom] = editorViewStr.split(' ').map(x => parseFloat(x));
                    this.editor.dispatch(Viewport.transformBy(Mat33.scaling2D(zoom)));
                    this.editor.dispatch(Viewport.transformBy(Mat33.translation(Vec2.of(
                        - viewBoxOriginX,
                        - viewBoxOriginY
                    ))));
                }catch (e){}
            }

        }else{
            // it's a new drawing
            this.editor.dispatch(this.editor.setBackgroundStyle({
                color: Color4.fromHex(defaultEditorOptions.background),
                type: defaultEditorOptions.grid ? BackgroundComponentBackgroundType.Grid : BackgroundComponentBackgroundType.SolidColor,
                autoresize: true
            }));
        }

    }

    async genToolbar() {

        const toolbar = this.editor.addToolbar();

        // save button
        const saveButton = toolbar.addSaveButton(async () => {
            await this.saveCallback(saveButton);
        });

        // restore toolbarFile state
        this.toolbarFile = new PluginFile(STORAGE_PATH, TOOLBAR_FILENAME, JSON_MIME);
        await this.toolbarFile.loadFromSiYuanFS();
        if(this.toolbarFile.getContent() != null) {
            toolbar.deserializeState(this.toolbarFile.getContent());
        }

        // save toolbar config on tool change (toolbar state is not saved in SVGs!)
        this.editor.notifier.on(EditorEventType.ToolUpdated, () => {
            this.toolbarFile.setContent(toolbar.serializeState());
            this.toolbarFile.save();
        });

    }

    private async saveCallback(saveButton: BaseWidget) {

        const svgElem = this.editor.toSVG();
        let newSyncID: string;
        const oldSyncID = this.syncID;

        const rect = this.editor.viewport.visibleRect;
        const zoom = this.editor.viewport.getScaleFactor();
        svgElem.setAttribute('editorView', `${rect.x} ${rect.y} ${zoom}`)

        try {
            this.drawingFile.setContent(svgElem.outerHTML);
            await this.drawingFile.save();
            newSyncID = this.drawingFile.getSyncID();
            if(newSyncID != oldSyncID) { // supposed to replace protyle
                const changed = await replaceSyncID(this.fileID, oldSyncID, newSyncID); // try to change protyle
                if(!changed) throw new UnchangedProtyleError();
                await this.drawingFile.removeOld(oldSyncID);
            }
            saveButton.setDisabled(true);
            setTimeout(() => { // @todo improve save button feedback
                saveButton.setDisabled(false);
            }, 500);
        } catch (error) {
            if(error instanceof InternationalizedError) {
                ErrorReporter.error(error);
            }else{
                ErrorReporter.error(new GenericSaveError());
                console.error(error);
            }
            await navigator.clipboard.writeText(svgElem.outerHTML);
            console.log("Couldn't save SVG: ", svgElem.outerHTML)
            return;
        }

        this.syncID = newSyncID;

    }

}

export class EditorManager {

    private editor: PluginEditor
    setEditor(editor: PluginEditor) { this.editor = editor;}

    static async create(fileID: string, p: DrawJSPlugin) {
        let instance = new EditorManager();
        try {
            let editor = await PluginEditor.create(fileID, p.config.options.editorOptions);
            instance.setEditor(editor);
        }catch (error) {
            ErrorReporter.error(error);
        }
        return instance;
    }

    static registerTab(p: DrawJSPlugin) {
        p.addTab({
            'type': "whiteboard",
            async init() {
                const fileID = this.data.fileID;
                if (fileID == null) {
                    ErrorReporter.error(new NoFileIDError());
                    return;
                }
                try {
                    const editor = await PluginEditor.create(fileID, p.config.options.editorOptions);
                    this.element.appendChild(editor.getElement());
                }catch (error){
                    ErrorReporter.error(error);
                }
            }
        });
    }

    toTab(p: Plugin) {
        openTab({
            app: p.app,
            custom: {
                title: p.i18n.whiteboard,
                icon: 'iconDraw',
                id: "siyuan-jsdraw-pluginwhiteboard",
                data: {
                    fileID: this.editor.getFileID(),
                }
            }
        });
    }

    toDialog() {
        const dialog = new Dialog({
            width: "100vw",
            height: getFrontend() == "mobile" ? "100vh" : "90vh",
            content: `<div id="DrawingPanel" style="width:100%; height: 100%;"></div>`,
        });
        dialog.element.querySelector("#DrawingPanel").appendChild(this.editor.getElement());
    }

    open(p: DrawJSPlugin) {
        if(getFrontend() != "mobile" && !p.config.options.dialogOnDesktop) {
            this.toTab(p);
        } else {
            this.toDialog();
        }
    }

}