(function () {
    function ensureChunkDelayPatch() {
        if (window.__progressLabChunkDelayPatch) {
            return;
        }

        var originalOpen = XMLHttpRequest.prototype.open;
        var originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
        var originalSend = XMLHttpRequest.prototype.send;
        var originalAbort = XMLHttpRequest.prototype.abort;

        XMLHttpRequest.prototype.open = function (method, url) {
            this.__progressLabMeta = { url: url, headers: {} };
            this.__progressLabAborted = false;
            return originalOpen.apply(this, arguments);
        };

        XMLHttpRequest.prototype.setRequestHeader = function (name, value) {
            if (this.__progressLabMeta) {
                this.__progressLabMeta.headers[String(name).toLowerCase()] = value;
            }
            return originalSetRequestHeader.apply(this, arguments);
        };

        XMLHttpRequest.prototype.abort = function () {
            this.__progressLabAborted = true;
            return originalAbort.apply(this, arguments);
        };

        XMLHttpRequest.prototype.send = function () {
            var xhr = this;
            var args = arguments;
            var meta = xhr.__progressLabMeta || {};
            var delay = parseInt((meta.headers || {})["x-demo-chunk-delay"], 10);
            var shouldDelay = delay > 0 && /\/ajaxupload\.axd\/upload\/chunk/i.test(meta.url || "");

            if (!shouldDelay) {
                return originalSend.apply(xhr, args);
            }

            window.setTimeout(function () {
                if (!xhr.__progressLabAborted) {
                    originalSend.apply(xhr, args);
                }
            }, delay);
        };

        window.__progressLabChunkDelayPatch = true;
    }

    var root = document.getElementById("progressLabRoot");
    var host = document.getElementById("progressLabUploader");
    if (!root || !host || !window.AjaxUploader) {
        return;
    }

    ensureChunkDelayPatch();

    var presets = {
        realtime: { label: "Realtime Feel", delay: 120, chunkSize: 384 * 1024, copy: "A gentle pace that still feels close to a real network." },
        slow: { label: "Demo Slow", delay: 420, chunkSize: 192 * 1024, copy: "Balanced pacing for visual QA and screen recordings." },
        cinematic: { label: "Cinematic", delay: 660, chunkSize: 96 * 1024, copy: "Extra slow for presentations and detailed inspection." }
    };

    var state = {
        profile: "slow",
        delay: presets.slow.delay,
        chunkSize: presets.slow.chunkSize
    };

    var elements = {
        browseButton: document.getElementById("progressBrowseButton"),
        startButton: document.getElementById("progressStartButton"),
        pauseButton: document.getElementById("progressPauseButton"),
        resumeButton: document.getElementById("progressResumeButton"),
        clearButton: document.getElementById("progressClearButton"),
        delayRange: document.getElementById("delayRange"),
        chunkRange: document.getElementById("chunkRange"),
        delayValue: document.getElementById("delayValue"),
        chunkValue: document.getElementById("chunkValue"),
        profileValue: document.getElementById("profileValue"),
        overallStatus: document.getElementById("overallStatus"),
        overallPercent: document.getElementById("overallPercent"),
        overallFill: document.getElementById("overallFill"),
        overallTransfer: document.getElementById("overallTransfer"),
        overallPacing: document.getElementById("overallPacing"),
        speedValue: document.getElementById("speedValue"),
        etaValue: document.getElementById("etaValue"),
        queueValue: document.getElementById("queueValue"),
        queueCopy: document.getElementById("queueCopy"),
        moodValue: document.getElementById("moodValue"),
        moodCopy: document.getElementById("moodCopy"),
        progressRing: document.getElementById("progressRing"),
        ringPercent: document.getElementById("ringPercent"),
        ringLabel: document.getElementById("ringLabel"),
        pulseRail: document.getElementById("pulseRail"),
        fileEmpty: document.getElementById("fileEmpty"),
        fileList: document.getElementById("fileList")
    };

    if (!hasRequiredElements(elements)) {
        return;
    }

    var uploader = AjaxUploader.create(host, {
        uploadUrl: host.getAttribute("data-upload-url"),
        chunked: true,
        chunkSize: state.chunkSize,
        autoUpload: false,
        multiple: true,
        concurrency: 1,
        retries: 1,
        paste: false,
        showThumbnails: false,
        showToasts: false,
        showFileSize: false,
        browseButton: elements.browseButton,
        dropTarget: document.getElementById("progressDrop"),
        headers: function () {
            return {
                "X-Demo-Chunk-Delay": String(state.delay),
                "X-Demo-Progress-Lab": "1"
            };
        },
        onTaskStart: renderState,
        onTaskComplete: renderState,
        onTaskError: renderState,
        onTaskPause: renderState,
        onTaskResume: renderState,
        onChange: renderState,
        onQueueComplete: renderState,
        onProgress: renderState
    });

    setupProgressbar(elements.overallFill, "Overall upload progress", 0);
    buildPulseRail();
    bindEvents();
    applyPreset(state.profile);

    function bindEvents() {
        elements.startButton.addEventListener("click", function () {
            uploader.setOptions({ chunkSize: state.chunkSize });
            uploader.upload();
            renderState();
        });

        elements.pauseButton.addEventListener("click", function () {
            uploader.pauseAll();
            renderState();
        });

        elements.resumeButton.addEventListener("click", function () {
            uploader.resumeAll();
            renderState();
        });

        elements.clearButton.addEventListener("click", function () {
            uploader.clear();
            renderState();
        });

        elements.delayRange.addEventListener("input", function () {
            state.delay = parseInt(this.value, 10) || 0;
            state.profile = "custom";
            updateControls();
            renderState();
        });

        elements.chunkRange.addEventListener("input", function () {
            state.chunkSize = (parseInt(this.value, 10) || 96) * 1024;
            state.profile = "custom";
            uploader.setOptions({ chunkSize: state.chunkSize });
            updateControls();
            renderState();
        });

        Array.prototype.forEach.call(document.querySelectorAll("[data-preset]"), function (button) {
            button.addEventListener("click", function () {
                applyPreset(button.getAttribute("data-preset"));
            });
        });

        Array.prototype.forEach.call(document.querySelectorAll("[data-sample-size]"), function (button) {
            button.addEventListener("click", function () {
                var megabytes = parseInt(button.getAttribute("data-sample-size"), 10) || 1;
                uploader.addFiles([createSampleFile(megabytes)]);
                renderState();
            });
        });
    }

    function hasRequiredElements(map) {
        for (var key in map) {
            if (Object.prototype.hasOwnProperty.call(map, key) && !map[key]) {
                return false;
            }
        }

        return true;
    }

    function applyPreset(name) {
        var preset = presets[name];
        if (!preset) {
            updateControls();
            return;
        }

        state.profile = name;
        state.delay = preset.delay;
        state.chunkSize = preset.chunkSize;
        uploader.setOptions({ chunkSize: state.chunkSize });
        updateControls();
        renderState();
    }

    function updateControls() {
        elements.delayRange.value = Math.round(state.delay / 30) * 30;
        elements.chunkRange.value = Math.round((state.chunkSize / 1024) / 32) * 32;
        elements.delayValue.textContent = state.delay + " ms";
        elements.chunkValue.textContent = formatChunkSize(state.chunkSize);
        elements.profileValue.textContent = getProfileLabel();
        elements.overallPacing.textContent = "Pacing: " + state.delay + " ms delay / " + formatChunkSize(state.chunkSize) + " chunks";
        elements.moodValue.textContent = getProfileLabel();
        elements.moodCopy.textContent = getProfileCopy();

        Array.prototype.forEach.call(document.querySelectorAll("[data-preset]"), function (button) {
            button.classList.toggle("is-active", button.getAttribute("data-preset") === state.profile);
        });
    }

    function renderState() {
        var tasks = uploader.getFiles().slice().sort(compareTasks);
        var stats = uploader.getStats();
        var totalBytes = stats.totalBytes || 0;
        var uploadedBytes = stats.uploadedBytes || 0;
        var percent = clampPercent(totalBytes > 0 ? Math.round((uploadedBytes / totalBytes) * 100) : 0);
        var activeSpeed = getActiveSpeed(tasks);
        var remainingBytes = Math.max(totalBytes - uploadedBytes, 0);
        var queueEta = activeSpeed > 0 ? remainingBytes / activeSpeed : 0;
        var status = resolveQueueStatus(stats);

        elements.overallFill.style.width = percent + "%";
        elements.overallFill.setAttribute("aria-valuenow", percent);
        elements.overallPercent.textContent = percent + "%";
        elements.overallStatus.className = "progress-lab-status-pill " + status.className;
        elements.overallStatus.textContent = status.label;
        elements.overallTransfer.textContent = "Transferred: " + formatSize(uploadedBytes) + " of " + formatSize(totalBytes);
        elements.speedValue.textContent = activeSpeed > 0 ? formatSpeed(activeSpeed) : (stats.completed > 0 && stats.completed === stats.total ? "Settled" : "--");
        elements.etaValue.textContent = status.key === "complete" ? "Done" : (activeSpeed > 0 ? AjaxUploader.formatTime(queueEta) : "--");
        elements.queueValue.textContent = stats.total > 0 ? (stats.completed + "/" + stats.total + " complete") : "No files";
        elements.queueCopy.textContent = "Pending: " + stats.pending + " / Paused: " + stats.paused;
        elements.ringPercent.textContent = percent + "%";
        elements.ringLabel.textContent = status.label;
        elements.progressRing.style.background = "conic-gradient(#38bdf8 " + (percent * 3.6) + "deg, #cbd5e1 " + (percent * 3.6) + "deg)";

        updatePulseRail(percent);
        updateButtons(stats);
        renderFiles(tasks);
    }

    function renderFiles(tasks) {
        if (!tasks.length) {
            elements.fileEmpty.classList.remove("progress-lab-hidden");
            elements.fileList.classList.add("progress-lab-hidden");
            elements.fileList.replaceChildren();
            return;
        }

        elements.fileEmpty.classList.add("progress-lab-hidden");
        elements.fileList.classList.remove("progress-lab-hidden");
        elements.fileList.replaceChildren();
        tasks.forEach(function (task) {
            elements.fileList.appendChild(renderTaskCard(task));
        });
    }

    function renderTaskCard(task) {
        var percent = clampPercent(task.progress || 0);
        var chunkSize = task.metadata && task.metadata.chunkSize ? task.metadata.chunkSize : state.chunkSize;
        var totalChunks = task.metadata && task.metadata.totalChunks ? task.metadata.totalChunks : Math.max(1, Math.ceil((task.fileSize || 0) / Math.max(chunkSize, 1)));
        var speed = task.status === "uploading" && task.speed > 0 ? formatSpeed(task.speed) : "--";
        var eta = task.status === "uploading" && task.eta > 0 ? AjaxUploader.formatTime(task.eta) : (task.status === "completed" ? "Done" : "--");
        var statusClass = getSafeStatusClass(task.status);
        var fillClass = "progress-lab-file-fill is-" + statusClass;
        var pillClass = "progress-lab-file-pill is-" + (statusClass === "completed" ? "completed" : statusClass);

        var article = document.createElement("article");
        article.className = "progress-lab-file";

        var top = document.createElement("div");
        top.className = "progress-lab-file-top";

        var details = document.createElement("div");
        var name = document.createElement("p");
        name.className = "progress-lab-file-name";
        name.textContent = task.fileName || "file";

        var meta = document.createElement("div");
        meta.className = "progress-lab-file-meta";
        appendTextSpan(meta, formatSize(task.fileSize || 0));
        appendTextSpan(meta, "Chunks: " + totalChunks);
        appendTextSpan(meta, "Speed: " + speed);
        appendTextSpan(meta, "ETA: " + eta);

        details.appendChild(name);
        details.appendChild(meta);

        var pill = document.createElement("span");
        pill.className = pillClass;
        pill.textContent = getTaskStatusLabel(task.status);

        top.appendChild(details);
        top.appendChild(pill);

        var bar = document.createElement("div");
        bar.className = "progress-lab-file-bar";
        var fill = document.createElement("div");
        fill.className = fillClass;
        fill.setAttribute("role", "progressbar");
        fill.setAttribute("aria-label", (task.fileName || "File") + " upload progress");
        fill.setAttribute("aria-valuemin", "0");
        fill.setAttribute("aria-valuemax", "100");
        fill.setAttribute("aria-valuenow", percent);
        fill.style.width = percent + "%";
        bar.appendChild(fill);

        var legend = document.createElement("div");
        legend.className = "progress-lab-file-legend";
        appendTextSpan(legend, percent + "% uploaded");
        appendTextSpan(legend, getTaskCaption(task, chunkSize, totalChunks));

        var segments = document.createElement("div");
        segments.className = "progress-lab-segments";
        renderSegments(segments, percent, task.status);

        article.appendChild(top);
        article.appendChild(bar);
        article.appendChild(legend);
        article.appendChild(segments);

        if (task.error) {
            var error = document.createElement("div");
            error.className = "progress-lab-file-error";
            error.textContent = task.error;
            article.appendChild(error);
        }

        return article;
    }

    function renderSegments(container, percent, status) {
        var segments = 14;
        var completeSegments = Math.floor((percent / 100) * segments);
        var activeSegment = status === "uploading" ? Math.min(segments - 1, completeSegments) : -1;

        for (var i = 0; i < segments; i++) {
            var segment = document.createElement("span");
            if (i < completeSegments) segment.classList.add("is-complete");
            if (i === activeSegment && percent < 100) segment.classList.add("is-active");
            container.appendChild(segment);
        }
    }

    function appendTextSpan(container, text) {
        var span = document.createElement("span");
        span.textContent = text;
        container.appendChild(span);
    }

    function clampPercent(value) {
        value = Number(value);
        if (!isFinite(value)) return 0;
        return Math.max(0, Math.min(100, Math.round(value)));
    }

    function setupProgressbar(element, label, value) {
        if (!element) return;
        element.setAttribute("role", "progressbar");
        element.setAttribute("aria-label", label);
        element.setAttribute("aria-valuemin", "0");
        element.setAttribute("aria-valuemax", "100");
        element.setAttribute("aria-valuenow", clampPercent(value));
    }

    function getSafeStatusClass(status) {
        switch (status) {
            case "pending":
            case "uploading":
            case "paused":
            case "completed":
            case "failed":
            case "cancelled":
                return status;
            default:
                return "pending";
        }
    }

    function buildPulseRail() {
        elements.pulseRail.replaceChildren();
        for (var i = 0; i < 12; i++) {
            var bar = document.createElement("span");
            bar.style.height = (28 + (i % 4) * 8) + "px";
            elements.pulseRail.appendChild(bar);
        }
    }

    function updatePulseRail(percent) {
        var bars = elements.pulseRail.querySelectorAll("span");
        var activeBars = Math.max(1, Math.round((percent / 100) * bars.length));
        Array.prototype.forEach.call(bars, function (bar, index) {
            bar.classList.toggle("is-active", index < activeBars);
        });
    }

    function updateButtons(stats) {
        var hasTasks = stats.total > 0;
        elements.startButton.disabled = !hasTasks || stats.pending === 0;
        elements.pauseButton.disabled = stats.uploading === 0;
        elements.resumeButton.disabled = stats.paused === 0;
        elements.clearButton.disabled = !hasTasks;
    }

    function resolveQueueStatus(stats) {
        if (stats.uploading > 0) return { key: "uploading", label: "Uploading", className: "is-uploading" };
        if (stats.paused > 0) return { key: "paused", label: "Paused", className: "is-paused" };
        if (stats.failed > 0 && stats.pending === 0) return { key: "error", label: "Needs Attention", className: "is-error" };
        if (stats.total > 0 && stats.completed === stats.total) return { key: "complete", label: "Complete", className: "is-complete" };
        if (stats.total > 0) return { key: "queued", label: "Queued", className: "is-queued" };
        return { key: "idle", label: "Ready", className: "is-idle" };
    }

    function compareTasks(a, b) {
        var order = { uploading: 0, paused: 1, pending: 2, failed: 3, completed: 4, cancelled: 5 };
        var left = Object.prototype.hasOwnProperty.call(order, a.status) ? order[a.status] : 99;
        var right = Object.prototype.hasOwnProperty.call(order, b.status) ? order[b.status] : 99;
        if (left !== right) return left - right;
        return String(a.fileName || "").localeCompare(String(b.fileName || ""));
    }

    function getActiveSpeed(tasks) {
        return tasks.reduce(function (sum, task) {
            return task.status === "uploading" && task.speed > 0 ? sum + task.speed : sum;
        }, 0);
    }

    function getTaskStatusLabel(status) {
        switch (status) {
            case "uploading": return "Uploading";
            case "completed": return "Completed";
            case "failed": return "Failed";
            case "paused": return "Paused";
            case "cancelled": return "Cancelled";
            default: return "Queued";
        }
    }

    function getTaskCaption(task, chunkSize, totalChunks) {
        if (task.status === "completed") return "Finished in " + totalChunks + " chunks";
        if (task.status === "failed") return "Upload stopped before completion";
        if (task.status === "paused") return "Paused with " + clampPercent(task.progress || 0) + "% transferred";
        if (task.status === "uploading") return "Paced with " + formatChunkSize(chunkSize) + " chunks";
        return "Waiting to upload at " + formatChunkSize(chunkSize) + " per chunk";
    }

    function createSampleFile(megabytes) {
        var totalBytes = megabytes * 1024 * 1024;
        var blockSize = 256 * 1024;
        var pattern = "AjaxUploader Progress Lab Sample ";
        var parts = [];
        var remaining = totalBytes;

        while (remaining > 0) {
            var size = Math.min(blockSize, remaining);
            var chunk = new Uint8Array(size);
            for (var i = 0; i < size; i++) chunk[i] = pattern.charCodeAt(i % pattern.length);
            parts.push(chunk);
            remaining -= size;
        }

        return new File(parts, "progress-lab-" + megabytes + "mb.bin", { type: "application/octet-stream", lastModified: Date.now() });
    }

    function formatChunkSize(bytes) {
        return bytes >= 1048576 ? (bytes / 1048576).toFixed(bytes % 1048576 === 0 ? 0 : 1) + " MB" : Math.round(bytes / 1024) + " KB";
    }

    function formatSize(bytes) {
        return bytes > 0 ? AjaxUploader.formatSize(bytes) : "0 B";
    }

    function formatSpeed(bytesPerSecond) {
        return formatSize(bytesPerSecond) + "/s";
    }

    function getProfileLabel() {
        return presets[state.profile] ? presets[state.profile].label : "Custom Mix";
    }

    function getProfileCopy() {
        return presets[state.profile] ? presets[state.profile].copy : "Custom delay and chunk-size tuning.";
    }

})();
