/* ============================================================
   SelectPdf — runtime helpers (Alpine.js companion).
   Provides:
     - $store.spdf  — global theme + drawer state
     - x-data scopes you'll see in HTML: nav drawer, FAQ, code tabs,
       pricing currency/billing toggle, blog search, reading progress.
   Loaded once per page; <script defer> guarantees Alpine sees it.
   ============================================================ */
(function () {
  'use strict';

  // Silence the benign "ResizeObserver loop completed with undelivered
  // notifications" warning — browsers throw this when an observer
  // triggers another layout pass on the same frame. Not an actual error.
  var origError = window.console && window.console.error;
  if (origError) {
    window.console.error = function () {
      var msg = arguments[0];
      if (typeof msg === 'string' && msg.indexOf('ResizeObserver') !== -1) return;
      return origError.apply(window.console, arguments);
    };
  }
  window.addEventListener('error', function (e) {
    if (e && e.message && e.message.indexOf('ResizeObserver') !== -1) {
      e.stopImmediatePropagation();
      e.preventDefault();
      return false;
    }
  }, true);

  // ─── Update copyright year in footer ─────────────────────────
  document.addEventListener('DOMContentLoaded', function () {
    var yr = new Date().getFullYear();
    document.querySelectorAll('[data-spdf-year]').forEach(function (n) {
      n.textContent = String(yr);
    });
  });

  // ─── Alpine.js global store + components ─────────────────────
  document.addEventListener('alpine:init', function () {
    if (!window.Alpine) return;

    // Persistent dark/light theme.
    Alpine.store('spdf', {
      theme: 'light',
      init: function () {
        try {
          var saved = localStorage.getItem('spdf-theme');
          if (saved === 'dark' || saved === 'light') this.theme = saved;
        } catch (e) {}
        this._apply();
      },
      toggleTheme: function () {
        this.theme = this.theme === 'dark' ? 'light' : 'dark';
        try { localStorage.setItem('spdf-theme', this.theme); } catch (e) {}
        this._apply();
      },
      _apply: function () {
        if (this.theme === 'dark') {
          document.documentElement.setAttribute('data-theme', 'dark');
        } else {
          document.documentElement.removeAttribute('data-theme');
        }
      },
    });

    // Code-tab switcher — used by Quickstart, Save Link examples.
    Alpine.data('codeTabs', function (initial) {
      return {
        active: initial || '',
        select: function (key) { this.active = key; },
        isActive: function (key) { return this.active === key; },
      };
    });

    // FAQ accordion — only one open at a time.
    Alpine.data('faqGroup', function () {
      return {
        open: null,
        toggle: function (i) { this.open = (this.open === i ? null : i); },
        isOpen: function (i) { return this.open === i; },
      };
    });

    // Pricing currency + billing cycle.
    Alpine.data('pricing', function (opts) {
      var o = opts || {};
      return {
        currency: o.currency || 'USD',
        billing:  o.billing  || 'monthly',
        rates: { USD: 1, EUR: 0.92, GBP: 0.79 },
        symbols: { USD: '$', EUR: '€', GBP: '£' },
        format: function (usd) {
          var v = usd * this.rates[this.currency];
          var s = this.symbols[this.currency];
          if (this.billing === 'annual') v = Math.round(v * 10);
          return s + (Math.round(v)).toLocaleString();
        },
        period: function () {
          return this.billing === 'annual' ? '/yr' : '/mo';
        },
      };
    });

    // Live demo widget — POSTs to the server-side /demo/convert proxy, which
    // forwards to the real watermarked/clamped demo pipeline and streams the PDF.
    Alpine.data('demoWidget', function () {
      return {
        url: 'https://en.wikipedia.org/wiki/PDF',
        optionsOpen: false,
        previewOpen: false,
        loading: false,
        done: false,
        ms: null,
        pages: null,
        clamped: null,
        error: null,
        pdfUrl: null,
        fileName: 'demo.pdf',
        opts: {
          pageSize: 'A4',
          orientation: 'Portrait',
          fileName: 'Document.pdf',
          marginTop: 5, marginRight: 5, marginBottom: 5, marginLeft: 5,
          pageNumbers: false,
          cssPrint: false,
          width: 1024, height: '',
          minLoad: 1, maxLoad: 30,
        },
        convert: function () {
          var self = this;
          if (self.pdfUrl) { URL.revokeObjectURL(self.pdfUrl); self.pdfUrl = null; }
          self.error = null;
          self.done = false;
          self.loading = true;
          self.previewOpen = true;
          var o = self.opts;
          var start = performance.now();
          fetch('/demo/convert', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              url: self.url,
              pageSize: o.pageSize,
              orientation: o.orientation,
              fileName: o.fileName,
              marginTop: String(o.marginTop),
              marginRight: String(o.marginRight),
              marginBottom: String(o.marginBottom),
              marginLeft: String(o.marginLeft),
              pageNumbers: o.pageNumbers ? 'true' : 'false',
              cssPrint: o.cssPrint ? 'true' : 'false',
              width: String(o.width),
              height: o.height === '' ? null : String(o.height),
              minLoad: String(o.minLoad),
              maxLoad: String(o.maxLoad),
            }),
          }).then(function (r) {
            if (!r.ok) {
              return r.json().then(function (j) {
                throw new Error((j && j.error) || 'Conversion failed.');
              });
            }
            self.pages = r.headers.get('X-SelectPdf-Pages') || null;
            self.clamped = r.headers.get('X-SelectPdf-Demo-Clamped') || null;
            return r.arrayBuffer();
          }).then(function (buf) {
            self.ms = Math.round(performance.now() - start);
            var u8 = new Uint8Array(buf);
            self.pdfUrl = URL.createObjectURL(new Blob([u8], { type: 'application/pdf' }));
            var fn = (self.opts.fileName || 'demo.pdf').trim();
            self.fileName = /\.pdf$/i.test(fn) ? fn : fn + '.pdf';
            self.loading = false;
            self.done = true;
            self.$nextTick(function () {
              var c = self.$refs.pdfview;
              if (c) spdfRenderPdf(c, u8.slice()).catch(function () {});
            });
          }).catch(function (e) {
            self.loading = false;
            self.error = (e && e.message) || 'Something went wrong.';
          });
        },
      };
    });

    // Mobile drawer is local to <nav> (x-data="{ drawer: false }").
    // Nothing global needed.
  });

  // ─── Reading-progress bar (Blog Post) ────────────────────────
  document.addEventListener('DOMContentLoaded', function () {
    var bar = document.querySelector('[data-spdf-reading-progress] > span');
    if (!bar) return;
    function tick() {
      var h = document.documentElement;
      var max = h.scrollHeight - h.clientHeight;
      var pct = max > 0 ? (h.scrollTop / max) * 100 : 0;
      bar.style.width = pct + '%';
    }
    window.addEventListener('scroll', tick, { passive: true });
    tick();
  });

  // ─── Scroll-spy for blog-post TOC ────────────────────────────
  document.addEventListener('DOMContentLoaded', function () {
    var links = document.querySelectorAll('[data-spdf-toc-link]');
    if (!links.length) return;
    var ids = [];
    links.forEach(function (a) { ids.push(a.getAttribute('href').replace(/^#/, '')); });
    function activeNow() {
      var top = window.scrollY + 120;
      var current = ids[0];
      for (var i = 0; i < ids.length; i++) {
        var el = document.getElementById(ids[i]);
        if (el && el.offsetTop <= top) current = ids[i];
      }
      links.forEach(function (a) {
        var on = a.getAttribute('href') === '#' + current;
        if (on) a.setAttribute('aria-current', 'true');
        else a.removeAttribute('aria-current');
      });
    }
    window.addEventListener('scroll', activeNow, { passive: true });
    activeNow();
  });

  // ─── Blog search — filters [data-search-item] by data-search-keys ───
  document.addEventListener('DOMContentLoaded', function () {
    var input = document.querySelector('[data-spdf-search-input]');
    if (!input) return;
    var items = Array.prototype.slice.call(document.querySelectorAll('[data-search-item]'));
    var emptyEl = document.querySelector('[data-spdf-search-empty]');
    var promptEl = document.querySelector('[data-spdf-search-prompt]');
    var countEl  = document.querySelector('[data-spdf-search-count]');

    function applyQuery(q) {
      var qn = (q || '').trim().toLowerCase();
      var visible = 0;
      items.forEach(function (item) {
        if (!qn) { item.hidden = true; return; }
        var hay = (item.dataset.searchKeys || '').toLowerCase();
        var match = hay.indexOf(qn) !== -1;
        item.hidden = !match;
        if (match) visible++;
      });
      if (countEl)  countEl.textContent  = visible + ' ' + (visible === 1 ? 'RESULT' : 'RESULTS');
      if (promptEl) promptEl.hidden = !!qn;
      if (emptyEl)  emptyEl.hidden  = !qn || visible > 0;
    }

    input.addEventListener('input', function (e) { applyQuery(e.target.value); });
    // Suggested chips
    document.querySelectorAll('[data-spdf-search-suggest]').forEach(function (b) {
      b.addEventListener('click', function (e) {
        e.preventDefault();
        var q = b.dataset.spdfSearchSuggest || b.textContent;
        input.value = q;
        applyQuery(q);
        input.focus();
      });
    });
    // Initialise from ?q=
    var u = new URLSearchParams(location.search).get('q') || '';
    if (u) { input.value = u; applyQuery(u); }
  });

  // ─── Param reference search + side-nav scroll-spy ───────────
  // Filters [data-spdf-param-row] by data-search-keys; updates per-group
  // counts in the side nav AND the in-section group meta; hides empty
  // groups; updates total count; toggles empty-state and clear button.
  document.addEventListener('DOMContentLoaded', function () {
    var input = document.querySelector('[data-spdf-param-search-input]');
    if (!input) return;
    var rows   = Array.prototype.slice.call(document.querySelectorAll('[data-spdf-param-row]'));
    var groups = Array.prototype.slice.call(document.querySelectorAll('[data-spdf-param-group]'));
    var totalEl = document.querySelector('[data-spdf-param-search-count]');
    var emptyEl = document.querySelector('[data-spdf-param-search-empty]');
    var emptyQ  = document.querySelector('[data-spdf-param-search-q]');
    var clearBtn = document.querySelector('[data-spdf-param-search-clear]');

    function applyQuery(q) {
      var qn = (q || '').trim().toLowerCase();
      var total = 0;
      groups.forEach(function (g) {
        var id = g.getAttribute('data-spdf-param-group');
        var groupRows = Array.prototype.slice.call(g.querySelectorAll('[data-spdf-param-row]'));
        var visible = 0;
        groupRows.forEach(function (r) {
          var match = !qn || (r.dataset.searchKeys || '').indexOf(qn) !== -1;
          r.classList.toggle('is-hidden', !match);
          if (match) visible++;
        });
        g.classList.toggle('is-hidden', visible === 0);
        var sideCt = document.querySelector('[data-spdf-param-side-count="' + id + '"]');
        if (sideCt) sideCt.textContent = String(visible);
        var groupCt = document.querySelector('[data-spdf-param-group-count="' + id + '"]');
        if (groupCt) groupCt.textContent = visible + ' ' + (visible === 1 ? 'PARAMETER' : 'PARAMETERS');
        total += visible;
      });
      if (totalEl) totalEl.textContent = String(total);
      if (emptyEl) emptyEl.hidden = !(qn && total === 0);
      if (emptyQ)  emptyQ.textContent = qn;
      if (clearBtn) clearBtn.hidden = !qn;
    }

    input.addEventListener('input', function (e) { applyQuery(e.target.value); });
    if (clearBtn) clearBtn.addEventListener('click', function () {
      input.value = ''; applyQuery(''); input.focus();
    });
    // Initialise (handles back-button restoration of input value).
    if (input.value) applyQuery(input.value);
  });

  // Scroll-spy for the parameter side-nav. Marks the currently-in-view
  // group with aria-current="true" on its side-nav link.
  document.addEventListener('DOMContentLoaded', function () {
    var links = document.querySelectorAll('[data-spdf-param-side-link]');
    if (!links.length) return;
    var ids = [];
    links.forEach(function (a) { ids.push(a.getAttribute('data-spdf-param-side-link')); });
    function activeNow() {
      var top = window.scrollY + 140;
      var current = ids[0];
      for (var i = 0; i < ids.length; i++) {
        var el = document.getElementById(ids[i]);
        if (el && el.offsetTop <= top) current = ids[i];
      }
      links.forEach(function (a) {
        var on = a.getAttribute('data-spdf-param-side-link') === current;
        if (on) a.setAttribute('aria-current', 'true');
        else a.removeAttribute('aria-current');
      });
    }
    window.addEventListener('scroll', activeNow, { passive: true });
    activeNow();
  });

  // ─── Copy-to-clipboard (code blocks) ────────────────────────
  document.addEventListener('click', function (e) {
    var btn = e.target.closest('[data-spdf-copy]');
    if (!btn) return;
    var sel = btn.getAttribute('data-spdf-copy');
    var src = sel ? document.querySelector(sel) : btn.previousElementSibling;
    if (!src) return;
    var text = src.innerText || src.textContent || '';
    if (navigator.clipboard) {
      navigator.clipboard.writeText(text).then(function () {
        var prev = btn.textContent;
        btn.textContent = 'COPIED';
        setTimeout(function () { btn.textContent = prev; }, 1200);
      });
    }
  });

  // ─── PDF.js viewer (real in-page render, browser-independent) ─
  var SPDF_PDFJS    = '/assets/js/pdf.3.11.174.min.js';
  var SPDF_PDFJS_WK = '/assets/js/pdf.worker.3.11.174.min.js';
  var _pdfjsPromise;
  // pdf.js 3.11 creates its own internal canvases (genericComposeSMask compositing)
  // that read pixels via getImageData. Chrome logs a Canvas2D willReadFrequently
  // hint for each one. The fix landed in pdf.js 4.x; until we upgrade, apply
  // the hint via a one-shot prototype shim — only after pdf.js loads, only when
  // the caller didn't already pass options, only for '2d' contexts.
  function spdfPatchCanvasReadFrequently() {
    if (spdfPatchCanvasReadFrequently._done) return;
    spdfPatchCanvasReadFrequently._done = true;
    if (typeof HTMLCanvasElement === 'undefined') return;
    var orig = HTMLCanvasElement.prototype.getContext;
    HTMLCanvasElement.prototype.getContext = function (type, attrs) {
      if (type === '2d' && (attrs === undefined || attrs === null)) {
        return orig.call(this, type, { willReadFrequently: true });
      }
      return orig.call(this, type, attrs);
    };
  }
  function spdfLoadPdfJs() {
    if (window.pdfjsLib) return Promise.resolve(window.pdfjsLib);
    if (_pdfjsPromise) return _pdfjsPromise;
    spdfPatchCanvasReadFrequently();
    _pdfjsPromise = new Promise(function (res, rej) {
      var s = document.createElement('script');
      s.src = SPDF_PDFJS;
      s.onload = function () {
        try { window.pdfjsLib.GlobalWorkerOptions.workerSrc = SPDF_PDFJS_WK; res(window.pdfjsLib); }
        catch (e) { rej(e); }
      };
      s.onerror = function () { rej(new Error('pdfjs load failed')); };
      document.head.appendChild(s);
    });
    return _pdfjsPromise;
  }
  function spdfRenderPdf(container, data) {
    container.innerHTML = '';
    return spdfLoadPdfJs().then(function (pdfjsLib) {
      return pdfjsLib.getDocument({ data: data }).promise;
    }).then(function (pdf) {
      var seq = Promise.resolve();
      for (var n = 1; n <= pdf.numPages; n++) {
        (function (num) {
          seq = seq.then(function () {
            return pdf.getPage(num).then(function (page) {
              var v1 = page.getViewport({ scale: 1 });
              var cw = container.clientWidth || 520;
              var scale = (cw / v1.width) * Math.min(window.devicePixelRatio || 1, 2);
              var vp = page.getViewport({ scale: scale });
              var canvas = document.createElement('canvas');
              canvas.width = Math.floor(vp.width);
              canvas.height = Math.floor(vp.height);
              container.appendChild(canvas);
              // willReadFrequently:true silences pdf.js's genericComposeSMask Canvas2D warning
              return page.render({ canvasContext: canvas.getContext('2d', { willReadFrequently: true }), viewport: vp }).promise;
            });
          });
        })(n);
      }
      return seq;
    });
  }

  // ─── Runtime syntax highlight ───────────────────────────────
  // Source <pre> stays plain code (copy/paste clean); tokens are
  // wrapped at load using the external .spdf-tok-* classes.
  var SPDF_KW = {
    csharp: ['using','var','new','public','class','void','return','namespace','null','true','false','static','string','int','bool','async','await'],
    bash:   ['curl','POST','GET','PUT','DELETE','Bearer'],
    js:     ['import','from','const','let','var','await','async','function','return','new','null','true','false','export','default'],
    python: ['from','import','def','return','None','True','False','as','with','if','else','for','in','not','and','or'],
    php:    ['require','require_once','new','true','false','null','echo','return','function','use'],
    ruby:   ['require','def','end','do','class','module','nil','true','false','if','elsif','else','unless','return','begin','rescue','ensure','yield','self','then','new'],
    vbnet:  ['Imports','Module','Sub','Function','End','Dim','As','New','Return','If','Then','Else','ElseIf','For','Each','In','Next','While','Do','Loop','Class','Public','Private','Shared','ByVal','ByRef','Nothing','True','False','Me','Boolean','Integer','String','Main'],
  };
  function spdfHl(code, lang) {
    var KW = new Set(SPDF_KW[lang] || []);
    var COMMENT = lang === 'vbnet' ? /^'/ : (lang === 'python' || lang === 'bash' || lang === 'ruby') ? /^#/ : /^\/\//;
    var out = [], i = 0;
    var esc = function (s) { return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); };
    var push = function (t, cls) { if (!t) return; out.push(cls ? '<span class="' + cls + '">' + esc(t) + '</span>' : esc(t)); };
    while (i < code.length) {
      var ch = code[i], rest = code.slice(i);
      if (COMMENT.test(rest)) { var e = code.indexOf('\n', i); var s = e === -1 ? code.length : e; push(code.slice(i, s), 'spdf-tok-com'); i = s; continue; }
      if (ch === '"' || ch === "'" || ch === '`') {
        var q = ch, j = i + 1;
        while (j < code.length) { if (code[j] === '\\') { j += 2; continue; } if (code[j] === q) { j++; break; } if (code[j] === '\n' && q !== '`') break; j++; }
        push(code.slice(i, j), 'spdf-tok-str'); i = j; continue;
      }
      if (/[0-9]/.test(ch) && (i === 0 || /[^a-zA-Z0-9_]/.test(code[i - 1]))) {
        var j2 = i; while (j2 < code.length && /[0-9.]/.test(code[j2])) j2++;
        push(code.slice(i, j2), 'spdf-tok-num'); i = j2; continue;
      }
      if (/[a-zA-Z_$]/.test(ch)) {
        var j3 = i; while (j3 < code.length && /[a-zA-Z0-9_$]/.test(code[j3])) j3++;
        var w = code.slice(i, j3); push(w, KW.has(w) ? 'spdf-tok-kw' : null); i = j3; continue;
      }
      push(ch); i++;
    }
    return out.join('');
  }
  function spdfHighlightAll() {
    document.querySelectorAll('pre[data-hl]').forEach(function (el) {
      if (el.getAttribute('data-hl-done')) return;
      var raw = el.textContent;
      el.innerHTML = spdfHl(raw, el.getAttribute('data-hl'));
      el.setAttribute('data-hl-done', '1');
      // Build the line-number gutter from the actual line count.
      var body = el.parentNode;
      var g = body && body.querySelector('.spdf-qs-gutter');
      if (g) {
        var n = raw.replace(/\n+$/, '').split('\n').length;
        var s = '';
        for (var k = 1; k <= n; k++) s += '<span>' + k + '</span>';
        g.innerHTML = s;
      }
    });
  }
  if (document.readyState !== 'loading') spdfHighlightAll();
  else document.addEventListener('DOMContentLoaded', spdfHighlightAll);

  // Expose the highlighter for callers that inject pre[data-hl] after our
  // initial DOMContentLoaded pass (e.g. the WordPress theme's blog.js,
  // which wraps legacy <pre class="lang:..."> + Responsive-Tabs panes
  // and needs to re-run highlighting once it's done). Idempotent: each
  // pre is gated by data-hl-done so calling it repeatedly is cheap.
  window.spdfHighlightAll = spdfHighlightAll;
})();
