/**
 * monitor-client.js
 *
 * Minimal, dependency-free SDK for browsers + WebView-based mobile.
 * Drop into your web app / mobile HTML, initialise once, forget it.
 *
 *   - Auto-instruments fetch() and XMLHttpRequest
 *   - Captures window.onerror + unhandledrejection
 *   - Batches events and flushes every few seconds (or on pagehide)
 *   - Uses navigator.sendBeacon on unload for reliable delivery
 */

(function (global) {
'use strict';

function MonitorClient(opts) {
  if (!(this instanceof MonitorClient)) return new MonitorClient(opts);
  this.endpoint  = opts.endpoint;
  this.token     = opts.token;
  this.system    = opts.system || 'web_app';
  this.flushMs   = opts.flushMs || 3000;
  this.maxBatch  = opts.maxBatch || 25;
  this._queue    = [];
  this._timer    = null;

  this._installFetch();
  this._installXhr();
  this._installErrorHandlers();
  this._installPageHide();
}

MonitorClient.prototype.metric = function (endpoint, method, rt, status) {
  this._enqueue({
    type: 'metric',
    system_name: this.system,
    endpoint: endpoint,
    method: method,
    response_time: Math.max(0, Math.round(rt)),
    status_code: status || 0,
  });
};

MonitorClient.prototype.error = function (message, file, line, severity) {
  this._enqueue({
    type: 'error',
    system_name: this.system,
    message: String(message).slice(0, 2000),
    file: file || null,
    line: line || null,
    severity: severity || 'error',
  });
};

MonitorClient.prototype._enqueue = function (evt) {
  this._queue.push(evt);
  if (this._queue.length >= this.maxBatch) {
    this.flush();
  } else if (!this._timer) {
    this._timer = setTimeout(this.flush.bind(this), this.flushMs);
  }
};

MonitorClient.prototype.flush = function () {
  if (this._timer) { clearTimeout(this._timer); this._timer = null; }
  if (!this._queue.length) return;
  var batch = this._queue.splice(0, this._queue.length);
  var self = this;
  batch.forEach(function (evt) {
    try {
      fetch(self.endpoint, {
        method: 'POST',
        mode: 'cors',
        keepalive: true,
        headers: {
          'Content-Type': 'application/json',
          'X-Ingest-Token': self.token,
        },
        body: JSON.stringify(evt),
      }).catch(function () { /* best-effort */ });
    } catch (_) { /* noop */ }
  });
};

MonitorClient.prototype._installFetch = function () {
  if (!global.fetch) return;
  var orig = global.fetch.bind(global);
  var self = this;
  global.fetch = function (input, init) {
    var url = typeof input === 'string' ? input : (input && input.url) || '';
    var method = (init && init.method) || (input && input.method) || 'GET';
    var t0 = performance.now();
    return orig(input, init).then(function (res) {
      self.metric(self._stripQuery(url), method, performance.now() - t0, res.status);
      return res;
    }, function (err) {
      self.metric(self._stripQuery(url), method, performance.now() - t0, 0);
      throw err;
    });
  };
};

MonitorClient.prototype._installXhr = function () {
  if (!global.XMLHttpRequest) return;
  var self = this;
  var origOpen = XMLHttpRequest.prototype.open;
  var origSend = XMLHttpRequest.prototype.send;
  XMLHttpRequest.prototype.open = function (method, url) {
    this._mon = { method: method, url: url, t0: 0 };
    return origOpen.apply(this, arguments);
  };
  XMLHttpRequest.prototype.send = function () {
    if (this._mon) {
      this._mon.t0 = performance.now();
      this.addEventListener('loadend', function () {
        self.metric(self._stripQuery(this._mon.url), this._mon.method,
                    performance.now() - this._mon.t0, this.status);
      });
    }
    return origSend.apply(this, arguments);
  };
};

MonitorClient.prototype._installErrorHandlers = function () {
  var self = this;
  global.addEventListener('error', function (e) {
    self.error(e.message || 'Script error', e.filename, e.lineno, 'error');
  });
  global.addEventListener('unhandledrejection', function (e) {
    var msg = (e.reason && (e.reason.message || String(e.reason))) || 'Unhandled rejection';
    self.error(msg, null, null, 'error');
  });
};

MonitorClient.prototype._installPageHide = function () {
  var self = this;
  global.addEventListener('pagehide', function () {
    if (!self._queue.length) return;
    try {
      var blob = new Blob(
        [JSON.stringify(self._queue.splice(0, self._queue.length))],
        { type: 'application/json' }
      );
      navigator.sendBeacon && navigator.sendBeacon(self.endpoint, blob);
    } catch (_) { /* noop */ }
  });
};

MonitorClient.prototype._stripQuery = function (url) {
  var i = url.indexOf('?');
  return (i === -1 ? url : url.slice(0, i)).slice(0, 255);
};

global.MonitorClient = MonitorClient;
})(typeof window !== 'undefined' ? window : this);
