/*jshint esversion: 11 */

const TOO_LARGE_TEXT = "Too large!";
const ZERO_TEXT = "File is blank!";
const ERROR_TEXT = "Error!";

async function formSubmit() {
    const form = document.getElementById("uploadForm");
    const files = form.elements.fileUpload.files;
    const duration = form.elements.duration.value;
    const maxSize = form.elements.fileUpload.dataset.maxFilesize;

    await sendFiles(files, duration, maxSize);

    // Reset the form file data since we've successfully submitted it
    form.elements.fileUpload.value = "";
}

async function dragDropSubmit(evt) {
    const form = document.getElementById("uploadForm");
    const duration = form.elements.duration.value;
    const maxSize = form.elements.fileUpload.dataset.maxFilesize;

    evt.preventDefault();

    const files = [];
    if (evt.dataTransfer.items) {
        // Use DataTransferItemList interface to access the file(s)
        [...evt.dataTransfer.items].forEach((item, _) => {
            // If dropped items aren't files, reject them
            if (item.kind === "file") {
                files.push(item.getAsFile());
            }
        });
    } else {
        // Use DataTransfer interface to access the file(s)
        [...evt.dataTransfer.files].forEach((file, _) => {
            files.push(file.name);
        });
    }

    await sendFiles(files, duration, maxSize);
}

async function pasteSubmit(evt) {
    const form = document.getElementById("uploadForm");
    const duration = form.elements.duration.value;
    const maxSize = form.elements.fileUpload.dataset.maxFilesize;

    const files = [];
    const len = evt.clipboardData.files.length;
    for (let i = 0; i < len; i++) {
        const file = evt.clipboardData.files[i];
        files.push(file);
    }

    await sendFiles(files, duration, maxSize);
}

async function sendFiles(files, duration, maxSize) {
    const inProgressUploads = new Set();
    const concurrencyLimit = 10;

    for (const file of files) {
        // Start the upload and add it to the set of in-progress uploads
        const uploadPromise = uploadFile(file, duration, maxSize);
        inProgressUploads.add(uploadPromise);

        // Once an upload finishes, remove it from the set
        uploadPromise.finally(() => inProgressUploads.delete(uploadPromise));

        // If we reached the concurrency limit, wait for one of the uploads to complete
        if (inProgressUploads.size >= concurrencyLimit) {
            await Promise.race(inProgressUploads);
        }
    }

    // Wait for any remaining uploads to complete
    await Promise.allSettled(inProgressUploads);
}

async function uploadFile(file, duration, maxSize) {
    const [linkRow, progressBar, progressText] = await addNewToList(file.name);
    if (file.size > maxSize) {
        console.error("Provided file is too large", file.size, "bytes; max", maxSize, "bytes");
        return;
    } else if (file.size == 0) {
        console.error("Provided file has 0 bytes");
        return;
    }

    // Get preliminary upload information
    let chunkedResponse;
    try {
        const response = await fetch("/upload/chunked", {
            method: "POST",
            body: JSON.stringify({
                "name": file.name,
                "size": file.size,
                "expire_duration": parseInt(duration),
            }),
        });
        if (!response.ok) {
            throw new Error(`Response status: ${response.status}`);
        }
        chunkedResponse = await response.json();
    } catch (error) {
        console.error(error);
    }

    // Upload the file in `chunk_size` chunks
    const chunkUploads = new Set();
    const progressValues = [];
    const concurrencyLimit = 4;
    for (let start = 0; start < file.size; start += chunkedResponse.chunk_size) {
        const chunk = file.slice(start, start + chunkedResponse.chunk_size)
        const url = "/upload/chunked/" + chunkedResponse.uuid + "?offset=" + start;
        const ID = progressValues.push(0);

        let upload = new Promise(function (resolve, reject) {
            let request = new XMLHttpRequest();
            request.open("POST", url, true);
            request.upload.addEventListener('progress',
                (p) => {uploadProgress(p, progressBar, progressText, progressValues, file.size, ID);}, true
            );

            request.onload = (e) => {
                if (e.target.status >= 200 && e.target.status < 300) {
                    resolve(request.response);
                } else {
                    reject({status: e.target.status, statusText: request.statusText});
                }
            };
            request.onerror = (e) => {
                reject({status: e.target.status, statusText: request.statusText})
            };
            request.send(chunk);
        });

        chunkUploads.add(upload);
        upload.finally(() => chunkUploads.delete(upload));
        if (chunkUploads.size >= concurrencyLimit) {
            await Promise.race(chunkUploads);
        }
    }
    await Promise.allSettled(chunkUploads);

    // Finish the request and update the progress box
    const result = await fetch("/upload/chunked/" + chunkedResponse.uuid + "?finish");
    uploadComplete(result, progressBar, progressText, linkRow);
}

async function addNewToList(origFileName) {
    const uploadedFilesDisplay = document.getElementById("uploadedFilesDisplay");
    const linkRow = uploadedFilesDisplay.appendChild(document.createElement("div"));
    const fileName = linkRow.appendChild(document.createElement("p"));
    const progressBar = linkRow.appendChild(document.createElement("progress"));
    const progressTxt = linkRow.appendChild(document.createElement("p"));

    fileName.textContent = origFileName;
    fileName.classList.add("file_name");
    progressTxt.classList.add("status");
    progressBar.max="100";
    progressBar.value="0";

    return [linkRow, progressBar, progressTxt];
}

function uploadProgress(progress, progressBar, progressText, progressValues, fileSize, ID) {
    if (progress.lengthComputable) {
        progressValues[ID] = progress.loaded;

        const progressPercent = Math.floor((progressValues.reduce((a, b) => a + b, 0) / fileSize) * 100);
        if (progressPercent == 100) {
            progressBar.removeAttribute("value");
            progressText.textContent = "⏳";
        } else {
            progressBar.value = progressPercent;
            progressText.textContent = progressPercent + "%";
        }
    }
}

async function uploadComplete(response, progressBar, progressText, linkRow) {
    if (response.status === 200) {
        const responseJson = await response.json();
        console.log("Successfully uploaded file", responseJson);
        makeFinished(progressBar, progressText, linkRow, responseJson);
    } else if (response.status === 413) {
        makeErrored(progressBar, progressText, linkRow, TOO_LARGE_TEXT);
    } else {
        makeErrored(progressBar, progressText, linkRow, ERROR_TEXT);
    }
}

function makeErrored(progressBar, progressText, linkRow, errorMessage) {
    progressText.textContent = errorMessage;
    progressBar.style.display = "none";
    linkRow.classList.add("upload_failed");
}

function makeFinished(progressBar, progressText, linkRow, response) {
    progressText.textContent = "";
    const link = progressText.appendChild(document.createElement("a"));
    link.textContent = response.mmid;
    link.href = "/f/" + response.mmid;
    link.target = "_blank";

    let button = linkRow.appendChild(document.createElement("button"));
    button.textContent = "📝";
    let buttonTimeout = null;
    button.addEventListener('click', function(_e) {
        const mmid = response.mmid;
        if (buttonTimeout) {
            clearTimeout(buttonTimeout);
        }
        navigator.clipboard.writeText(
                window.location.protocol + "//" + window.location.host + "/f/" + mmid
        );
        button.textContent = "✅";
        buttonTimeout = setTimeout(function() {
            button.textContent = "📝";
        }, 750);
    });

    progressBar.style.display = "none";
    linkRow.classList.add("upload_done");
}

async function initEverything() {
    const durationBox = document.getElementById("durationBox");
    const durationButtons = durationBox.getElementsByTagName("button");
    for (const b of durationButtons) {
        b.addEventListener("click", function (_e) {
            if (this.classList.contains("selected")) {
                return;
            }
            document.getElementById("uploadForm").elements.duration.value = this.dataset.durationSeconds;
            let selected = this.parentNode.getElementsByClassName("selected");
            selected[0].classList.remove("selected");
            this.classList.add("selected");
        });
    }
}

// This is the entrypoint for everything basically
document.addEventListener("DOMContentLoaded", function(_event) {
    // Respond to form submissions
    const form = document.getElementById("uploadForm");
    form.addEventListener("submit", formSubmit);

    // Respond to file paste events
    window.addEventListener("paste", (event) => {
        pasteSubmit(event)
    });

    // Respond to drag and drop stuff
    let fileButton = document.getElementById("fileButton");
    document.addEventListener("drop", (e) => {e.preventDefault();}, false);
    document.addEventListener("dragover", (e) => {e.preventDefault()}, false);
    fileButton.addEventListener("dragover", (e) => {e.preventDefault();}, false);
    fileButton.addEventListener("drop", dragDropSubmit, false);

    initEverything();
});