// Canary fingerprint payload — the JS that runs inside an opened HTML/SVG
// canary, harvests browser primitives, and beacons the result back to the
// canary worker.  Ported from canary-self-test.html with the rendering UI
// stripped out.
//
// Three placeholders are substituted by the Python builder BEFORE
// javascript-obfuscator runs:
//
//   {{BEACON_URL}}  → full URL to /c/<callback_token> (no trailing slash)
//   {{MINT_UUID}}   → per-mint UUID, baked into the string-array post-obf
//   {{MINT_NONCE}}  → 16-hex HMAC nonce; the worker rejects ?d=/?o= without it
//
// Beacon strategy (MVP): a bare GET pixel for "I was opened" reliability,
// then a fingerprint payload sent as a base64-URL query param on a second
// GET so the existing worker records the hit even before step-4 POST
// support lands.  Both fail-open: any error short-circuits to next step.

(async function () {
  var BEACON_URL = "{{BEACON_URL}}";
  var MINT_UUID = "{{MINT_UUID}}";
  var MINT_NONCE = "{{MINT_NONCE}}";
  var fp = { mint: MINT_UUID };

  function fire(url) {
    try {
      var img = new Image();
      img.src = url;
    } catch (e) { /* swallow */ }
  }

  // 1) bare-open beacon — fires regardless of whether the rest succeeds
  fire(BEACON_URL + "?o=1&k=" + MINT_NONCE);

  function sha256(str) {
    var buf = new TextEncoder().encode(str);
    return crypto.subtle.digest("SHA-256", buf).then(function (h) {
      return Array.from(new Uint8Array(h))
        .map(function (b) { return b.toString(16).padStart(2, "0"); })
        .join("");
    });
  }

  // navigator
  try {
    fp.nav = {
      ua: navigator.userAgent,
      pl: navigator.platform,
      lg: navigator.language,
      lgs: (navigator.languages || []).join(","),
      ck: navigator.cookieEnabled,
      dnt: navigator.doNotTrack,
      hc: navigator.hardwareConcurrency,
      dm: navigator.deviceMemory || null,
      tp: navigator.maxTouchPoints,
      wd: navigator.webdriver === true,
      pdf: navigator.pdfViewerEnabled || null,
    };
  } catch (e) { fp.nav = { err: String(e) }; }

  // screen
  try {
    fp.scr = {
      w: screen.width, h: screen.height,
      aw: screen.availWidth, ah: screen.availHeight,
      cd: screen.colorDepth, pd: screen.pixelDepth,
      dpr: window.devicePixelRatio,
      iw: window.innerWidth, ih: window.innerHeight,
      or: (screen.orientation && screen.orientation.type) || null,
    };
  } catch (e) { fp.scr = { err: String(e) }; }

  // tz / locale
  try {
    var dtf = Intl.DateTimeFormat().resolvedOptions();
    fp.tz = {
      z: dtf.timeZone, lc: dtf.locale,
      ca: dtf.calendar, ns: dtf.numberingSystem,
      off: new Date().getTimezoneOffset(),
    };
  } catch (e) { fp.tz = { err: String(e) }; }

  // connection
  try {
    var c = navigator.connection;
    fp.cn = c ? {
      t: c.effectiveType, dl: c.downlink, rtt: c.rtt, sd: c.saveData,
    } : null;
  } catch (e) { fp.cn = { err: String(e) }; }

  // canvas
  try {
    var cv = document.createElement("canvas");
    cv.width = 280; cv.height = 60;
    var ctx = cv.getContext("2d");
    ctx.textBaseline = "top";
    ctx.font = "14px Arial";
    ctx.fillStyle = "#f60";
    ctx.fillRect(125, 1, 62, 20);
    ctx.fillStyle = "#069";
    ctx.fillText("c-" + String.fromCharCode(0x1f600), 2, 15);
    ctx.fillStyle = "rgba(102,204,0,0.7)";
    ctx.fillText("c-" + String.fromCharCode(0x1f600), 4, 17);
    var dataURL = cv.toDataURL();
    fp.cv = { h: await sha256(dataURL), n: dataURL.length };
  } catch (e) { fp.cv = { err: String(e) }; }

  // webgl
  try {
    var gc = document.createElement("canvas");
    var gl = gc.getContext("webgl") || gc.getContext("experimental-webgl");
    if (gl) {
      var ext = gl.getExtension("WEBGL_debug_renderer_info");
      fp.gl = {
        v: gl.getParameter(gl.VENDOR),
        r: gl.getParameter(gl.RENDERER),
        ver: gl.getParameter(gl.VERSION),
        sl: gl.getParameter(gl.SHADING_LANGUAGE_VERSION),
        uv: ext ? gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) : null,
        ur: ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : null,
      };
    } else { fp.gl = { err: "unavailable" }; }
  } catch (e) { fp.gl = { err: String(e) }; }

  // audio
  try {
    var ACtx = window.OfflineAudioContext || window.webkitOfflineAudioContext;
    if (ACtx) {
      var actx = new ACtx(1, 44100, 44100);
      var osc = actx.createOscillator();
      var cmp = actx.createDynamicsCompressor();
      osc.type = "triangle"; osc.frequency.value = 10000;
      cmp.threshold.value = -50; cmp.knee.value = 40;
      cmp.ratio.value = 12; cmp.attack.value = 0; cmp.release.value = 0.25;
      osc.connect(cmp); cmp.connect(actx.destination);
      osc.start(0);
      var buf = await actx.startRendering();
      var data = buf.getChannelData(0).slice(4500, 5000);
      var sum = 0;
      for (var i = 0; i < data.length; i++) sum += Math.abs(data[i]);
      fp.au = { h: await sha256(sum.toString()), s: sum.toFixed(8) };
    } else { fp.au = { err: "unavailable" }; }
  } catch (e) { fp.au = { err: String(e) }; }

  // fonts
  try {
    var bases = ["monospace", "sans-serif", "serif"];
    var tests = [
      "Arial", "Helvetica", "Times New Roman", "Courier New", "Verdana",
      "Georgia", "Trebuchet MS", "Comic Sans MS", "Impact",
      "Calibri", "Cambria", "Consolas", "Segoe UI", "Tahoma",
      "JetBrains Mono", "Fira Code", "Cascadia Code", "SF Mono",
      "Menlo", "Monaco", "Source Code Pro", "Inconsolata", "Hack",
      "San Francisco", "Helvetica Neue", "Lucida Grande",
      "DejaVu Sans", "DejaVu Sans Mono", "Liberation Sans",
      "Liberation Mono", "Ubuntu", "Ubuntu Mono", "Roboto",
      "Noto Sans", "Noto Mono",
      "Microsoft YaHei", "SimSun", "PingFang SC", "Hiragino Sans",
      "Hiragino Kaku Gothic Pro", "Yu Gothic", "Meiryo",
      "Malgun Gothic", "Noto Sans CJK",
      "Adobe Garamond Pro", "Myriad Pro", "Minion Pro",
      "Bahnschrift", "Cyberpunk",
    ];
    var sp = document.createElement("span");
    sp.style.fontSize = "72px";
    sp.style.position = "absolute";
    sp.style.left = "-9999px";
    sp.innerHTML = "mmmmmmmmmmlli";
    document.body.appendChild(sp);
    var bs = {};
    for (var bi = 0; bi < bases.length; bi++) {
      sp.style.fontFamily = bases[bi];
      bs[bases[bi]] = { w: sp.offsetWidth, h: sp.offsetHeight };
    }
    var det = [];
    for (var ti = 0; ti < tests.length; ti++) {
      for (var bj = 0; bj < bases.length; bj++) {
        sp.style.fontFamily = "'" + tests[ti] + "'," + bases[bj];
        if (sp.offsetWidth !== bs[bases[bj]].w ||
            sp.offsetHeight !== bs[bases[bj]].h) {
          det.push(tests[ti]); break;
        }
      }
    }
    document.body.removeChild(sp);
    fp.ft = {
      h: await sha256(det.slice().sort().join(",")),
      n: det.length, t: tests.length, d: det,
    };
  } catch (e) { fp.ft = { err: String(e) }; }

  // webrtc local ip leak
  try {
    var ips = {}; var cands = [];
    var RPC = window.RTCPeerConnection || window.webkitRTCPeerConnection ||
              window.mozRTCPeerConnection;
    if (RPC) {
      var pc = new RPC({ iceServers: [{ urls: "stun:stun.l.google.com:19302" }] });
      pc.createDataChannel("");
      pc.onicecandidate = function (e) {
        if (!e.candidate) return;
        cands.push(e.candidate.candidate);
        var m = e.candidate.candidate.match(
          /(\d+\.\d+\.\d+\.\d+|[a-f0-9:]+::[a-f0-9:]+)/);
        if (m) ips[m[1]] = 1;
      };
      var off = await pc.createOffer();
      await pc.setLocalDescription(off);
      await new Promise(function (r) { setTimeout(r, 1500); });
      pc.close();
      fp.rtc = { ip: Object.keys(ips), n: cands.length, c: cands.slice(0, 3) };
    } else { fp.rtc = { err: "unavailable" }; }
  } catch (e) { fp.rtc = { err: String(e) }; }

  // battery
  try {
    if (navigator.getBattery) {
      var bat = await navigator.getBattery();
      fp.bt = {
        c: bat.charging, l: bat.level,
        ct: bat.chargingTime === Infinity ? "inf" : bat.chargingTime,
        dt: bat.dischargingTime === Infinity ? "inf" : bat.dischargingTime,
      };
    } else { fp.bt = { err: "unavailable" }; }
  } catch (e) { fp.bt = { err: String(e) }; }

  // perf timing jitter
  try {
    var samples = [];
    for (var pi = 0; pi < 1000; pi++) {
      var pa = performance.now();
      var x = 0;
      for (var pj = 0; pj < 1000; pj++) x += Math.sqrt(pj);
      samples.push(performance.now() - pa);
    }
    samples.sort(function (a, b) { return a - b; });
    fp.pf = {
      med: samples[500].toFixed(4),
      p95: samples[950].toFixed(4),
      mn: samples[0].toFixed(4),
      mx: samples[999].toFixed(4),
    };
  } catch (e) { fp.pf = { err: String(e) }; }

  // permissions
  try {
    if (navigator.permissions) {
      var names = ["geolocation", "notifications", "camera", "microphone",
                   "persistent-storage", "clipboard-read", "clipboard-write"];
      var st = {};
      for (var ni = 0; ni < names.length; ni++) {
        try {
          var r = await navigator.permissions.query({ name: names[ni] });
          st[names[ni]] = r.state;
        } catch (e) { st[names[ni]] = "unsupported"; }
      }
      fp.pm = st;
    } else { fp.pm = { err: "unavailable" }; }
  } catch (e) { fp.pm = { err: String(e) }; }

  // composite identity hash — stable inputs only
  try {
    var stable = [
      fp.cv && fp.cv.h, fp.au && fp.au.h, fp.ft && fp.ft.h,
      fp.gl && fp.gl.ur, fp.nav && fp.nav.pl,
      fp.nav && fp.nav.hc, fp.tz && fp.tz.z,
      fp.scr && (fp.scr.w + "x" + fp.scr.h),
    ].filter(Boolean).join("|");
    fp.id = await sha256(stable);
  } catch (e) { fp.id = { err: String(e) }; }

  // 2) ship the payload as base64url JSON on a GET query param.
  //    The current worker records the hit on /c/<slug>; step-4 worker
  //    will decode ?d= and persist the fingerprint blob.
  try {
    var json = JSON.stringify(fp);
    var b64 = btoa(unescape(encodeURIComponent(json)))
      .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
    // chunk if URL would exceed safe limit (~6KB)
    var MAX = 6000;
    if (b64.length <= MAX) {
      fire(BEACON_URL + "?d=" + b64 + "&k=" + MINT_NONCE);
    } else {
      var sid = (Math.random() * 1e9 | 0).toString(36);
      var total = Math.ceil(b64.length / MAX);
      for (var ci = 0; ci < total; ci++) {
        var part = b64.substr(ci * MAX, MAX);
        fire(BEACON_URL + "?s=" + sid + "&i=" + ci + "&n=" + total + "&d=" + part + "&k=" + MINT_NONCE);
      }
    }
  } catch (e) { /* swallow */ }
})();
