<script>
/* mChunkUpload — uploads a File to admin/upload-chunk.php in small chunks (so it
   never hits PHP upload_max_filesize / post_max_size), reporting progress, then
   finalizes (server reassembles + ingests). 100% browser-driven — no cPanel.
   opts: { file, kind, reportId, csrf, confirm, endpoint,
           onProgress(pct,phase), onDone(json), onError(message) } */
window.mChunkUpload = function (opts) {
    var CHUNK = 1024 * 1024;                      // 1 MB — safe under every host's default limits
    var file  = opts.file;
    var total = Math.max(1, Math.ceil(file.size / CHUNK));
    var token = '';
    for (var i = 0; i < 24; i++) token += '0123456789abcdef'[Math.floor(Math.random() * 16)];
    var idx = 0, tries = 0;

    function post(fd) {
        return fetch(opts.endpoint, { method: 'POST', body: fd, credentials: 'same-origin' })
            .then(function (r) {
                return r.json().catch(function () { throw new Error('Unexpected server response (HTTP ' + r.status + ').'); });
            });
    }
    function sendChunk() {
        var start = idx * CHUNK;
        var blob  = file.slice(start, Math.min(start + CHUNK, file.size));
        var fd = new FormData();
        fd.append('action', 'chunk'); fd.append('token', token); fd.append('index', idx);
        fd.append('total', total);   fd.append('kind', opts.kind); fd.append('report_id', opts.reportId);
        fd.append('_csrf', opts.csrf); fd.append('chunk', blob, 'chunk');
        post(fd).then(function (j) {
            if (!j.ok) throw new Error(j.error || 'Chunk failed.');
            tries = 0; idx++;
            opts.onProgress(Math.round(idx / total * 100), 'upload');
            if (idx < total) sendChunk(); else (opts.storeOnly ? storeAssembled() : finalize());
        }).catch(function (e) {
            // transient network hiccup → retry the same chunk a few times before failing
            if (tries++ < 4) { setTimeout(sendChunk, 1000 * tries); }
            else opts.onError(e.message || String(e));
        });
    }
    /* Store-only: assemble the uploaded chunks into a file on the server, no ingest.
       onDone receives { ok, token } so the caller can run processing separately later. */
    function storeAssembled() {
        var fd = new FormData();
        fd.append('action', 'store'); fd.append('token', token); fd.append('kind', opts.kind);
        fd.append('report_id', opts.reportId); fd.append('filename', file.name); fd.append('_csrf', opts.csrf);
        post(fd).then(function (j) {
            if (j.ok) { j.token = j.token || token; opts.onDone(j); }
            else opts.onError(j.error || 'Upload failed.');
        }).catch(function (e) { opts.onError(e.message || String(e)); });
    }
    function finalize() {
        var started = Date.now();
        opts.onProgress(100, 'process', 0);
        var timer = setInterval(function () { opts.onProgress(100, 'process', Math.round((Date.now() - started) / 1000)); }, 1000);

        function done(j) { clearInterval(timer); opts.onDone(j); }
        function fail(msg) { clearInterval(timer); opts.onError(msg); }

        // Poll the server for the real ingest outcome.  Used when the ingest runs
        // detached (processing:true) OR when the finalize reply is lost to a gateway
        // timeout (HTTP 503/504) — the work keeps running server-side either way.
        function poll() {
            if (Date.now() - started > 45 * 60 * 1000) {       // safety cap (45 min)
                return fail('Still processing after 45 minutes — reopen the report shortly; the upload may finish in the background.');
            }
            var sfd = new FormData();
            sfd.append('action', 'status'); sfd.append('token', token); sfd.append('kind', opts.kind);
            sfd.append('report_id', opts.reportId); sfd.append('_csrf', opts.csrf);
            post(sfd).then(function (s) {
                if (s.status === 'completed')   { done(s); }
                else if (s.status === 'failed') { fail(s.note || 'Ingest failed.'); }
                else                            { setTimeout(poll, 3000); }   // pending | processing
            }).catch(function () { setTimeout(poll, 4000); });   // transient/proxy hiccup → keep polling
        }

        var fd = new FormData();
        fd.append('action', 'finalize'); fd.append('token', token); fd.append('kind', opts.kind);
        fd.append('report_id', opts.reportId); fd.append('filename', file.name); fd.append('_csrf', opts.csrf);
        if (opts.confirm) fd.append('confirm', opts.confirm);
        if (opts.defer)   fd.append('defer', opts.defer);
        post(fd).then(function (j) {
            if (j.processing) { poll(); return; }              // detached → switch to polling
            if (j.ok)         { done(j); }
            else              { fail(j.error || 'Ingest failed.'); }   // explicit error → don't poll
        }).catch(function () {
            // No JSON reply (gateway 503/504 on a long request) — ingest is likely
            // still running, so poll for the real result instead of failing.
            poll();
        });
    }
    sendChunk();
};
</script>
