export default class TangledMarkdownEditor extends HTMLElement {
    static tag = "markdown-editor";

    static define(tag = this.tag) {
        this.tag = tag;

        const name = customElements.getName(this);
        if (name && name !== tag) return console.warn(`${this.name} already defined as <${name}>!`);

        const ce = customElements.get(tag);
        if (ce && ce !== this) return console.warn(`${tag} already defined as ${ce.name}!`);

        customElements.define(tag, this);
    }

    static {
        const tag = new URL(import.meta.url).searchParams.get("tag") || this.tag;
        if (tag != "none") this.define(tag);
    }

    #dragHoverClass = "drag-hover";

    constructor() {
        super();
        this.textarea = this.querySelector("textarea");
        if (!this.textarea) {
            console.error("textarea is missing in markdown-editor");
            return;
        }

        this.querySelectorAll('[data-md-mode]').forEach(btn => {
            btn.addEventListener("click", () => {
                const mode = btn.dataset.mdMode;
                this.querySelectorAll('[data-md-panel]').forEach(p => {
                    p.classList.toggle('hidden', p.dataset.mdPanel !== mode);
                });
                this.querySelectorAll('[data-md-mode]').forEach(b => {
                    b.classList.toggle('active', b === btn);
                });
            });
        });

        this.textarea.addEventListener("paste", (ev) => this.#onPaste(ev));
        this.textarea.addEventListener("dragover", (ev) => this.#onDragOver(ev));
        this.textarea.addEventListener("dragleave", (ev) => this.#onDragLeave(ev));
        this.textarea.addEventListener("drop", (ev) => this.#onDrop(ev));
    }

    async insertFile() {
        const input = document.createElement("input");
        input.type = "file";
        input.accept = "image/*";
        input.multiple = true;
        input.style.display = "none";
        input.addEventListener("change", () => {
            if (!input.files) return;
            for (const file of input.files) {
                this.#handleFile(file);
            }
        });
        this.appendChild(input);
        input.click();
        this.removeChild(input);
    }

    /** @param {ClipboardEvent} ev */
    async #onPaste(ev) {
        const dt = ev.clipboardData;
        if (!dt || !dt.files || dt.files.length === 0) return;

        ev.preventDefault();

        for (const file of dt.files) {
            if (!file.type.startsWith("image/")) continue;

            await this.#handleFile(file);
        }
    }

    /** @param {DragEvent} ev */
    async #onDragOver(ev) {
        ev.preventDefault();
        this.classList.add(this.#dragHoverClass);
    }

    /** @param {DragEvent} ev */
    async #onDragLeave(ev) {
        ev.preventDefault();
        this.classList.remove(this.#dragHoverClass);
    }

    /** @param {DragEvent} ev */
    async #onDrop(ev) {
        this.classList.remove(this.#dragHoverClass);

        const dt = ev.dataTransfer;
        if (!dt || !dt.files || dt.files.length === 0) return;

        ev.preventDefault();

        for (const file of dt.files) {
            if (!file.type.startsWith("image/")) continue;

            await this.#handleFile(file);
        }
    }

    /** @param {File} file */
    async #handleFile(file) {
        const textarea = this.textarea;
        if (!textarea) return;

        const placeholder = `<!-- Uploading "${file.name}"... -->`;

        this.#insertTextAtCursor(placeholder);

        try {
            const blob = await this.#upload(file);

            // append json-encoded blob to form
            const input = document.createElement("input");
            input.name = "blob";
            input.type = "text";
            input.hidden = true;
            input.value = JSON.stringify(blob);
            this.appendChild(input);

            // insert image to markdown
            const cid = blob.ref["$link"]
            textarea.value = textarea.value.replace(placeholder, `![Image](blob://${cid})`);
        } catch (e) {
            console.error("failed to upload blob", e)
            textarea.value = textarea.value.replace(placeholder, `<!-- Failed to upload "${file.name}". -->`);
            return
        }
    }

    /** @param {string} text */
    #insertTextAtCursor(text) {
        const textarea = this.textarea;
        if (!textarea) return;
        const start = textarea.selectionStart;
        const end = textarea.selectionEnd;

        const before = textarea.value.slice(0, start);
        const after = textarea.value.slice(end);

        // add surrounding newlines if it's mid-line
        if (before && !before.endsWith("\n")) text = "\n\n" + text;
        if (after && !after.startsWith("\n")) text = text + "\n\n";

        textarea.value = before + text + after;

        const newPos = start + text.length;
        textarea.selectionStart = textarea.selectionEnd = newPos;

        textarea.dispatchEvent(
            new InputEvent("input", { bubbles: true, inputType: "insertText", data: text })
        );
        // textarea.dispatchEvent(new Event("input", { bubbles: true }));
        textarea.dispatchEvent(new Event("change", { bubbles: true }));
    }

    /** @param {File} file */
    async #upload(file) {
        await new Promise(r => setTimeout(r, 500));

        const host = this.getAttribute("host") ?? "";
        const res = await fetch(host + "/xrpc/com.atproto.repo.uploadBlob", {
            method: "POST",
            body: file,
            headers: {
                "Content-Type": file.type,
            },
        });
        const output = await res.json();
        console.warn("com.atproto.repo.uploadBlob out:", output);
        return output.blob;
    }
}
