// pdfjs_env.js — l'ambiente minimo che pdf.js pretende da un browser, e nient'altro.
//
// pdf.js non è scritto per un motore JS nudo: si aspetta i global del web. Questo file
// li mette, UNO PER UNO e solo quelli che pdf.js tocca davvero — misurati facendolo
// partire, non indovinati. Ogni blocco dice a cosa serve; se un blocco si può togliere,
// pdf.js smette di caricarsi, e la prova è che è stato tolto per scoprirlo.
//
// ⛔ Niente di tutto questo è un'emulazione di browser. Non c'è rete, non ci sono
// eventi, non c'è canvas: il convertitore (pdf_convert_native.js) usa il back-end SVG di
// pdf.js, che disegna in un albero di nodi — mai su una superficie raster.
//
// L'ordine di caricamento, dal motore C++ (pdf_import_pdfjs.cpp):
//     pdfjs_env.js → pdfjs_dom.js → pdf.worker.js → pdf.js → pdf_convert_native.js
// pdf.worker.js PRIMA di pdf.js non è un dettaglio: caricandolo nello stesso contesto
// definisce `globalThis.pdfjsWorker`, che è la porta ufficiale del
// "main thread worker message handler" (pdf.js:4537). Senza, pdf.js proverebbe a creare
// un `Worker` (non esiste) e poi a caricarsi lo script da sé con un `<script>` (non
// esiste): con, prende la via del fake worker in-processo e non chiede niente a nessuno.

var self = globalThis; var window = globalThis;
globalThis.self = globalThis;
globalThis.window = globalThis;

// La console di pdf.js: gli `warn` sono informazione vera (operatori non implementati,
// risorse mancanti) e vanno nel log del motore, non a schermo.
globalThis.console = {
  log:   function () { __pdfconv_log(Array.prototype.join.call(arguments, " ")); },
  info:  function () { __pdfconv_log(Array.prototype.join.call(arguments, " ")); },
  warn:  function () { __pdfconv_log(Array.prototype.join.call(arguments, " ")); },
  error: function () { __pdfconv_log(Array.prototype.join.call(arguments, " ")); },
  debug: function () {}
};

// ─────────────────────────────────────────────────────────────────── i timer ──
// pdf.js usa setTimeout per rilasciare il thread fra un pezzo e l'altro del lavoro del
// fake worker. Qui non c'è un event loop: la coda la svuota il C++ chiamando `__tick()`
// finché il verdetto non c'è (pdf_import_pdfjs.cpp, `pump`). I ritardi sono ordinati ma
// NON attesi — non ha senso dormire in un convertitore sincrono, e nessun ramo di pdf.js
// dipende dal tempo che passa davvero.
(function () {
  var q = [], seq = 1;
  globalThis.setTimeout = function (fn, ms) {
    var id = seq++;
    q.push({ id: id, at: (ms || 0), ord: seq, fn: fn, args: Array.prototype.slice.call(arguments, 2) });
    return id;
  };
  globalThis.clearTimeout = function (id) {
    for (var i = 0; i < q.length; i++) if (q[i].id === id) { q.splice(i, 1); return; }
  };
  globalThis.setInterval = globalThis.setTimeout;      // nessun ramo di pdf.js lo ripete
  globalThis.clearInterval = globalThis.clearTimeout;
  // true = ho eseguito qualcosa, richiamami; false = coda vuota.
  globalThis.__tick = function () {
    if (!q.length) return false;
    q.sort(function (a, b) { return (a.at - b.at) || (a.ord - b.ord); });
    var t = q.shift();
    t.fn.apply(null, t.args);
    return true;
  };
})();

globalThis.performance = globalThis.performance || { now: function () { return 0; } };

// ────────────────────────────────────────────────────────── base64 (atob/btoa) ──
// pdf.js le usa per le risorse `data:` — i programmi di font incorporati e le bitmap che
// `convertImgDataToPng` produce (con `forceDataSchema`, vedi il convertitore).
(function () {
  var A = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  globalThis.btoa = function (s) {
    s = String(s); var o = "", i = 0;
    while (i < s.length) {
      var c1 = s.charCodeAt(i++), c2 = s.charCodeAt(i++), c3 = s.charCodeAt(i++);
      o += A[c1 >> 2] + A[((c1 & 3) << 4) | ((c2 || 0) >> 4)]
         + (isNaN(c2) ? "=" : A[((c2 & 15) << 2) | ((c3 || 0) >> 6)])
         + (isNaN(c3) ? "=" : A[c3 & 63]);
    }
    return o;
  };
  globalThis.atob = function (s) {
    s = String(s).replace(/[^A-Za-z0-9+/]/g, ""); var o = "", i = 0;
    while (i < s.length) {
      var e1 = A.indexOf(s[i++]), e2 = A.indexOf(s[i++]), e3 = A.indexOf(s[i++]), e4 = A.indexOf(s[i++]);
      o += String.fromCharCode((e1 << 2) | (e2 >> 4));
      if (e3 >= 0) o += String.fromCharCode(((e2 & 15) << 4) | (e3 >> 2));
      if (e4 >= 0) o += String.fromCharCode(((e3 & 3) << 6) | e4);
    }
    return o;
  };
})();

// ───────────────────────────────────────────────── TextDecoder / TextEncoder ──
// QuickJS-ng non li porta nel core (stanno nella sua libc, che noi non linkiamo). pdf.js
// li usa per le stringhe UTF-8/UTF-16 dei metadati e delle CMap.
if (typeof TextDecoder === "undefined") {
  globalThis.TextDecoder = function (enc) { this.encoding = String(enc || "utf-8").toLowerCase(); };
  TextDecoder.prototype.decode = function (buf) {
    if (!buf) return "";
    var b = (buf instanceof Uint8Array) ? buf : new Uint8Array(buf.buffer || buf);
    var out = "", i = 0;
    if (this.encoding.indexOf("utf-8") >= 0 || this.encoding.indexOf("utf8") >= 0) {
      while (i < b.length) {
        var c = b[i++];
        if (c < 0x80) out += String.fromCharCode(c);
        else if (c < 0xE0) out += String.fromCharCode(((c & 0x1F) << 6) | (b[i++] & 0x3F));
        else if (c < 0xF0) out += String.fromCharCode(((c & 0x0F) << 12) | ((b[i++] & 0x3F) << 6) | (b[i++] & 0x3F));
        else {
          var cp = (((c & 7) << 18) | ((b[i++] & 0x3F) << 12) | ((b[i++] & 0x3F) << 6) | (b[i++] & 0x3F)) - 0x10000;
          out += String.fromCharCode(0xD800 + (cp >> 10), 0xDC00 + (cp & 0x3FF));
        }
      }
    } else if (this.encoding.indexOf("utf-16") >= 0) {
      var be = this.encoding.indexOf("be") >= 0;
      for (; i + 1 < b.length; i += 2) out += String.fromCharCode(be ? ((b[i] << 8) | b[i + 1]) : ((b[i + 1] << 8) | b[i]));
    } else {
      for (; i < b.length; i++) out += String.fromCharCode(b[i]);   // latin1
    }
    return out;
  };
  globalThis.TextEncoder = function () {};
  TextEncoder.prototype.encode = function (s) {
    s = String(s); var a = [];
    for (var i = 0; i < s.length; i++) {
      var c = s.charCodeAt(i);
      if (c < 0x80) a.push(c);
      else if (c < 0x800) a.push(0xC0 | (c >> 6), 0x80 | (c & 63));
      else a.push(0xE0 | (c >> 12), 0x80 | ((c >> 6) & 63), 0x80 | (c & 63));
    }
    return new Uint8Array(a);
  };
}

// ───────────────────────────────────────────────────────────── structuredClone ──
// La `LoopbackPort` di pdf.js (la finta MessagePort fra "worker" e "main" quando girano
// nello stesso contesto) chiede structuredClone per copiare i messaggi. Clona in
// profondità rispettando i cicli e i TypedArray, che è tutto ciò che ci passa.
if (typeof structuredClone === "undefined") {
  globalThis.structuredClone = function (v) {
    var seen = new Map();
    function cl(x) {
      if (x === null || typeof x !== "object") return x;
      if (seen.has(x)) return seen.get(x);
      if (ArrayBuffer.isView(x)) { var t = new x.constructor(x); seen.set(x, t); return t; }
      if (x instanceof ArrayBuffer) return x.slice(0);
      if (Array.isArray(x)) { var a = []; seen.set(x, a); for (var i = 0; i < x.length; i++) a.push(cl(x[i])); return a; }
      if (x instanceof Map) { var m = new Map(); seen.set(x, m); x.forEach(function (vv, k) { m.set(cl(k), cl(vv)); }); return m; }
      if (x instanceof Set) { var s = new Set(); seen.set(x, s); x.forEach(function (vv) { s.add(cl(vv)); }); return s; }
      var o = Object.create(Object.getPrototypeOf(x)); seen.set(x, o);
      for (var k in x) if (Object.prototype.hasOwnProperty.call(x, k)) o[k] = cl(x[k]);
      return o;
    }
    return cl(v);
  };
}

// ───────────────────────────────────────────────────────────── ReadableStream ──
// `MessageHandler.sendWithStream` (pdf.js:13846) costruisce uno stream per ogni risposta
// a pezzi — ed è la via da cui arriva `GetOperatorList`, cioè TUTTA la geometria. Serve
// quindi davvero, ma serve solo il sottoinsieme che quel codice usa: sorgente con
// start/pull/cancel, `controller.enqueue/close/error/desiredSize`, un reader con
// `read()/cancel()`. Niente tee, niente pipe, niente byte streams.
if (typeof ReadableStream === "undefined") {
  (function () {
    function Controller(stream, src, strategy) {
      this._s = stream; this._src = src;
      this._hwm = (strategy && typeof strategy.highWaterMark === "number") ? strategy.highWaterMark : 1;
      this._sizeOf = (strategy && strategy.size) ? strategy.size : function () { return 1; };
      this._q = []; this._qs = 0; this._closed = false;
    }
    Controller.prototype = {
      get desiredSize() {
        if (this._s._state === "errored") return null;
        return this._closed ? 0 : (this._hwm - this._qs);
      },
      enqueue: function (chunk) {
        if (this._s._reads.length) { this._s._reads.shift().resolve({ value: chunk, done: false }); return; }
        this._q.push(chunk); this._qs += this._sizeOf(chunk);
      },
      close: function () {
        this._closed = true;
        while (this._s._reads.length && !this._q.length) this._s._reads.shift().resolve({ value: undefined, done: true });
      },
      error: function (e) {
        this._s._state = "errored"; this._s._err = e;
        while (this._s._reads.length) this._s._reads.shift().reject(e);
      }
    };
    function RS(src, strategy) {
      src = src || {};
      this._state = "readable"; this._reads = []; this._c = new Controller(this, src, strategy);
      var me = this;
      if (src.start) Promise.resolve().then(function () {
        try { return src.start(me._c); } catch (e) { me._c.error(e); }
      });
    }
    RS.prototype.getReader = function () {
      var s = this, c = this._c;
      function pull() {
        if (c._src.pull && !c._closed) Promise.resolve().then(function () {
          try { c._src.pull(c); } catch (e) { c.error(e); }
        });
      }
      return {
        read: function () {
          if (s._state === "errored") return Promise.reject(s._err);
          if (c._q.length) {
            var v = c._q.shift(); c._qs = Math.max(0, c._qs - 1); pull();
            return Promise.resolve({ value: v, done: false });
          }
          if (c._closed) return Promise.resolve({ value: undefined, done: true });
          var p = new Promise(function (res, rej) { s._reads.push({ resolve: res, reject: rej }); });
          pull();
          return p;
        },
        cancel: function (reason) {
          c._closed = true;
          if (c._src.cancel) { try { return Promise.resolve(c._src.cancel(reason)); } catch (e) { return Promise.reject(e); } }
          return Promise.resolve();
        },
        releaseLock: function () {},
        get closed() { return Promise.resolve(); }
      };
    };
    globalThis.ReadableStream = RS;
  })();
}

// ─────────────────────────────────────────────────────────── i global residui ──
// `navigator` — pdf.js lo interroga per decidere le sue scorciatoie di piattaforma.
// `location`/`URL`/`URLSearchParams` — servono a `createValidAbsoluteUrl`, che il
// convertitore non usa mai (il PDF arriva come byte, non come URL) ma che pdf.js
// referenzia al caricamento.
globalThis.navigator = { userAgent: "QuickJS", platform: "Win32", language: "en-US" };
globalThis.location = { href: "file:///", origin: "file://", protocol: "file:", pathname: "/" };

if (typeof URL === "undefined") {
  globalThis.URL = function (u, base) {
    this.href = String(u); this.protocol = "file:"; this.origin = "file://";
    this.pathname = String(u); this.hash = ""; this.search = "";
  };
  URL.prototype.toString = function () { return this.href; };
  // Mai chiamate: `forceDataSchema` fa emettere a pdf.js `data:` al posto di `blob:`
  // (vedi il commento nel convertitore). Ci sono perché pdf.js le referenzia.
  URL.createObjectURL = function () { return "blob:unsupported"; };
  URL.revokeObjectURL = function () {};
}

if (typeof URLSearchParams === "undefined") {
  globalThis.URLSearchParams = function (init) {
    this._a = [];
    if (typeof init === "string") {
      var me = this;
      init.replace(/^\?/, "").split("&").forEach(function (kv) {
        if (!kv) return;
        var i = kv.indexOf("=");
        me._a.push([decodeURIComponent(i < 0 ? kv : kv.slice(0, i)),
                    i < 0 ? "" : decodeURIComponent(kv.slice(i + 1).replace(/\+/g, " "))]);
      });
    } else if (init && typeof init === "object") {
      for (var k in init) this._a.push([k, String(init[k])]);
    }
  };
  URLSearchParams.prototype.append = function (k, v) { this._a.push([String(k), String(v)]); };
  URLSearchParams.prototype.get = function (k) {
    k = String(k);
    for (var i = 0; i < this._a.length; i++) if (this._a[i][0] === k) return this._a[i][1];
    return null;
  };
  URLSearchParams.prototype.has = function (k) { return this.get(k) !== null; };
  URLSearchParams.prototype.toString = function () {
    return this._a.map(function (e) { return encodeURIComponent(e[0]) + "=" + encodeURIComponent(e[1]); }).join("&");
  };
}
