// retoor <retoor@molodetz.nl>

import { Component } from "./Component.js";

export class AppUpload extends Component {
    connectedCallback() {
        if (this._built) {
            return;
        }
        this._built = true;
        this.mode = this.attr("mode", "attachment");
        this.endpoint = this.attr("endpoint", "/uploads/upload");
        this.fieldName = this.attr("field-name", "file");
        this.uidsName = this.attr("name", "attachment_uids");
        this.maxBytes = this.intAttr("max-size", 10) * 1024 * 1024;
        this.maxFiles = this.intAttr("max-files", 10);
        const declaredTypes = this.attr("allowed-types", "")
            .split(",").map((t) => t.trim().toLowerCase()).filter(Boolean);
        const wildcard = declaredTypes.some((t) => t === "*" || t === ".*" || t === "*.*");
        this.allowedTypes = wildcard ? [] : declaredTypes;
        this.showChips = this.boolAttr("show-chips");
        this.extraFields = {};
        this.items = [];
        this.busyCount = 0;
        this.build();
    }

    build() {
        this.button = document.createElement("button");
        this.button.type = "button";
        this.button.className = "dp-upload-btn";
        this.button.innerHTML =
            '<span class="dp-upload-icon" aria-hidden="true">&#x1F4CE;</span>' +
            '<span class="dp-upload-label"></span>' +
            '<span class="dp-upload-count" hidden></span>';
        const label = this.attr("label", "");
        const labelEl = this.button.querySelector(".dp-upload-label");
        labelEl.textContent = label;
        labelEl.hidden = !label;
        if (!label) this.button.setAttribute("aria-label", "Upload file");
        this.countEl = this.button.querySelector(".dp-upload-count");

        this.input = document.createElement("input");
        this.input.type = "file";
        this.input.className = "dp-upload-input";
        this.input.hidden = true;
        if (this.boolAttr("multiple")) {
            this.input.multiple = true;
        }
        if (this.boolAttr("directory")) {
            this.input.webkitdirectory = true;
        }
        const accept = this.attr("accept", "");
        if (accept) {
            this.input.accept = accept;
        }
        if (this.mode === "field") {
            this.input.name = this.fieldName;
        }

        this.chips = document.createElement("div");
        this.chips.className = "dp-upload-chips";
        this.errorEl = document.createElement("div");
        this.errorEl.className = "dp-upload-error";

        this.button.addEventListener("click", (e) => {
            e.preventDefault();
            this.open();
        });
        this.button.addEventListener("dragover", (e) => {
            e.preventDefault();
            this.button.classList.add("dragover");
        });
        this.button.addEventListener("dragleave", () => this.button.classList.remove("dragover"));
        this.button.addEventListener("drop", (e) => {
            e.preventDefault();
            this.button.classList.remove("dragover");
            this.handleFiles(e.dataTransfer.files);
        });
        this.input.addEventListener("change", () => this.handleFiles(this.input.files));

        this.append(this.button, this.input, this.chips, this.errorEl);

        if (this.mode === "attachment") {
            this.uidsInput = document.createElement("input");
            this.uidsInput.type = "hidden";
            this.uidsInput.name = this.uidsName;
            this.append(this.uidsInput);
        }

        this.pendingSubmitForm = null;
        const form = this.closest("form");
        if (form) {
            if (this.boolAttr("paste")) {
                form.addEventListener("paste", (event) => this.handlePaste(event));
            }
            form.addEventListener("submit", (event) => {
                if (this.busyCount > 0) {
                    event.preventDefault();
                    event.stopImmediatePropagation();
                    this.pendingSubmitForm = form;
                    if (window.app && window.app.toast) {
                        window.app.toast.show("Waiting for upload to finish...", { type: "info" });
                    }
                }
            }, true);
        }
    }

    open() {
        this.input.click();
    }

    handlePaste(event) {
        const data = event.clipboardData;
        const files = Array.from(data ? data.files : [])
            .filter((file) => file.type.startsWith("image/"));
        if (!files.length) {
            return;
        }
        if (!data.getData("text/plain")) {
            event.preventDefault();
        }
        this.handleFiles(files);
    }

    clear() {
        this.items = [];
        if (this.mode === "field") {
            this.input.value = "";
        }
        if (this.uidsInput) {
            this.uidsInput.value = "";
        }
        this.renderChips();
        this.updateCount();
    }

    async handleFiles(fileList) {
        this.clearError();
        const files = Array.from(fileList || []);
        if (!files.length) {
            return;
        }
        if (this.mode === "field") {
            this.setFieldFiles(files);
            return;
        }
        if (this.mode === "attachment") {
            const remaining = this.maxFiles - this.items.length;
            if (files.length > remaining) {
                this.showError(`You can only add ${Math.max(0, remaining)} more file(s).`);
                return;
            }
        }
        for (const file of files) {
            if (this.validate(file)) {
                await this.upload(file);
            }
        }
        this.input.value = "";
        if (this.mode === "direct") {
            this.emit("done", {});
        }
    }

    validate(file) {
        if (file.size > this.maxBytes) {
            this.showError(`"${file.name}" exceeds the ${this.intAttr("max-size", 10)}MB limit.`);
            return false;
        }
        if (this.allowedTypes.length) {
            const ext = "." + file.name.split(".").pop().toLowerCase();
            if (!this.allowedTypes.includes(ext)) {
                this.showError(`"${file.name}" type is not allowed.`);
                return false;
            }
        }
        return true;
    }

    async upload(file, termsRetry = false) {
        this.button.classList.add("uploading");
        this.setBusy(true);
        const body = new FormData();
        body.append(this.fieldName, file);
        for (const [key, value] of Object.entries(this.extraFields)) {
            body.append(key, value);
        }
        try {
            const response = await fetch(this.endpoint, {
                method: "POST",
                headers: { "Accept": "application/json", "X-Requested-With": "fetch" },
                body,
            });
            const data = await response.json().catch(() => ({}));
            if (!response.ok || data.ok === false || data.error) {
                const gate = window.app && window.app.termsGate;
                if (!termsRetry && gate && gate.matches(data)) {
                    return gate.intercept(data.error, () => this.upload(file, true));
                }
                const message = (data.error && (data.error.message || data.error)) || "Upload failed";
                throw new Error(message);
            }
            if (this.mode === "attachment" && data.uid) {
                this.items.push({ uid: data.uid, name: file.name });
                this.syncUids();
                this.renderChips();
            }
            this.emit("uploaded", { file, result: data });
        } catch (error) {
            this.showError(error.message);
            this.emit("error", { file, message: error.message });
        } finally {
            this.button.classList.remove("uploading");
            this.setBusy(false);
            this.updateCount();
        }
    }

    setBusy(busy) {
        this.busyCount = Math.max(0, this.busyCount + (busy ? 1 : -1));
        const active = this.busyCount > 0;
        if (active === this._lastBusy) {
            return;
        }
        this._lastBusy = active;
        this.emit("busy", { busy: active });
        if (!active && this.pendingSubmitForm) {
            const form = this.pendingSubmitForm;
            this.pendingSubmitForm = null;
            if (form.requestSubmit) {
                form.requestSubmit();
            } else {
                form.submit();
            }
        }
    }

    setFieldFiles(files) {
        if (this.input.multiple) {
            this.items.push(...files.map((file) => ({ file, name: file.name })));
        } else {
            this.items = files.slice(0, 1).map((file) => ({ file, name: file.name }));
        }
        this.applyFieldInput();
        this.renderChips();
        this.updateCount();
    }

    applyFieldInput() {
        const transfer = new DataTransfer();
        for (const item of this.items) {
            transfer.items.add(item.file);
        }
        this.input.files = transfer.files;
    }

    removeAt(index) {
        this.items.splice(index, 1);
        if (this.mode === "attachment") {
            this.syncUids();
        }
        if (this.mode === "field") {
            this.applyFieldInput();
        }
        this.renderChips();
        this.updateCount();
    }

    renderChips() {
        this.chips.textContent = "";
        if (!this.showChips) {
            return;
        }
        this.items.forEach((item, index) => {
            const chip = document.createElement("span");
            chip.className = "dp-upload-chip";
            const name = document.createElement("span");
            name.className = "dp-upload-chip-name";
            name.textContent = item.name;
            const remove = document.createElement("button");
            remove.type = "button";
            remove.className = "dp-upload-chip-remove";
            remove.innerHTML = "&times;";
            remove.setAttribute("aria-label", `Remove ${item.name}`);
            remove.addEventListener("click", () => this.removeAt(index));
            chip.append(name, remove);
            this.chips.appendChild(chip);
        });
    }

    updateCount() {
        const count = this.items.length;
        this.countEl.hidden = count === 0;
        this.countEl.textContent = count ? `(${count})` : "";
        this.emit("change", { count });
    }

    syncUids() {
        if (this.uidsInput) {
            this.uidsInput.value = this.items.map((item) => item.uid).join(",");
        }
    }

    emit(name, detail) {
        this.dispatchEvent(new CustomEvent(`dp-upload:${name}`, { detail, bubbles: true }));
    }

    showError(message) {
        this.errorEl.textContent = message;
        if (window.app && window.app.toast) {
            window.app.toast.show(message, { type: "error" });
        }
    }

    clearError() {
        this.errorEl.textContent = "";
    }
}

customElements.define("dp-upload", AppUpload);
