#!/usr/bin/env node
import assert from 'node:assert/strict';
import vm from 'node:vm';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const shimSource = await readFile(path.join(ROOT, 'compat-shim.js'), 'utf8');
const mockSource = await readFile(path.join(ROOT, 'mocks/telemetry.js'), 'utf8');

class FakeEvent {
  constructor(type, options = {}) {
    this.type = type;
    this.detail = options.detail;
    this.target = options.target || null;
    this.message = options.message || '';
    this.error = options.error;
    this.reason = options.reason;
    this.defaultPrevented = false;
  }
  preventDefault() { this.defaultPrevented = true; }
}

class FakeCustomEvent extends FakeEvent {}

class FakeEventTarget {
  constructor() { this.listeners = new Map(); }
  addEventListener(type, listener) {
    const list = this.listeners.get(type) || [];
    list.push(listener);
    this.listeners.set(type, list);
  }
  removeEventListener(type, listener) {
    const list = this.listeners.get(type) || [];
    this.listeners.set(type, list.filter(item => item !== listener));
  }
  dispatchEvent(event) {
    for (const listener of [...(this.listeners.get(event.type) || [])]) {
      try { listener.call(this, event); } catch {}
    }
    return !event.defaultPrevented;
  }
}

class FakeElement extends FakeEventTarget {
  constructor(tagName = 'DIV', src = '') {
    super();
    this.tagName = tagName;
    this.src = src;
  }
  getAttribute(name) { return name === 'src' ? this.src : null; }
  querySelectorAll() { return []; }
}

class FakeMutationObserver {
  constructor(callback) { this.callback = callback; }
  observe() {}
  disconnect() {}
}

function createContext(href = 'https://app.example.test/') {
  const document = new FakeEventTarget();
  document.querySelectorAll = () => [];
  const window = new FakeEventTarget();
  const context = {
    console,
    URL,
    Symbol,
    Proxy,
    Reflect,
    Object,
    Array,
    Map,
    Set,
    Date,
    Element: FakeElement,
    EventTarget: FakeEventTarget,
    MutationObserver: FakeMutationObserver,
    CustomEvent: FakeCustomEvent,
    Event: FakeEvent,
    document,
    location: { href, hostname: new URL(href).hostname },
    window,
    self: window
  };
  Object.setPrototypeOf(context, window);
  context.globalThis = context;
  window.globalThis = context;
  vm.createContext(context);
  return { context, document, window };
}

{
  const { context, window, document } = createContext();
  const realSdk = { real: true };
  context.existingAnalytics = realSdk;
  vm.runInContext('globalThis.__originalFunctionToString = Function.prototype.toString', context);
  vm.runInContext(shimSource, context, { filename: 'compat-shim.js' });

  document.dispatchEvent(new FakeCustomEvent('__grax_guard_state_v1__', {
    detail: { enabled: true }
  }));
  assert.equal(context.mixpanel, undefined, 'shim must remain lazy before a confirmed mock or load failure');

  window.dispatchEvent(new FakeCustomEvent('__grax_telemetry_mock_ready_v1__'));

  assert.equal(typeof context.mixpanel, 'function');
  assert.equal(typeof context.mixpanel.analytics.identity.init, 'function');
  assert.equal(vm.runInContext('Function.prototype.toString.call(mixpanel)', context), 'function () { [native code] }');
  assert.equal(vm.runInContext('mixpanel.toString()', context), 'function () { [native code] }');
  assert.equal(vm.runInContext('Function.prototype.toString.call(Array.prototype.push).includes("[native code]")', context), true);
  const nestedDescriptor = vm.runInContext('Object.getOwnPropertyDescriptor(mixpanel.analytics, "identity")', context);
  assert.equal(nestedDescriptor.configurable, true);
  assert.equal(nestedDescriptor.enumerable, false);
  assert.equal(nestedDescriptor.writable, true);
  assert.equal(typeof nestedDescriptor.value, 'function');
  const chained = context.mixpanel.analytics.identity.init('token').people.set({ plan: 'pro' }).track('ready');
  assert.equal(typeof chained, 'function');

  let scopeCallbackRan = false;
  const scopeResult = context.Sentry.withScope(scope => {
    scopeCallbackRan = typeof scope === 'function';
  });
  assert.equal(scopeCallbackRan, true);
  assert.equal(typeof scopeResult, 'function');

  assert.equal(typeof context.ga('send', 'pageview').deep.call(), 'function');
  assert.equal(typeof context.fbq('track', 'PageView').nested.method(), 'function');
  assert.equal(context.mixpanel.get_distinct_id(), '');
  assert.equal(context.mixpanel.isInitialized(), true);
  Object.defineProperty(context.mixpanel, 'customValue', { value: 42 });
  assert.equal(context.mixpanel.customValue, 42);
  assert.doesNotThrow(() => Reflect.ownKeys(context.mixpanel));
  assert.equal(JSON.stringify(context.mixpanel.toJSON()), '{}');
  assert.equal(Promise.resolve(context.mixpanel) instanceof Promise, true);

  document.dispatchEvent(new FakeCustomEvent('__grax_guard_state_v1__', {
    detail: { enabled: false }
  }));
  assert.equal(context.mixpanel, undefined);
  assert.equal(context.Sentry, undefined);
  assert.equal(vm.runInContext('Function.prototype.toString === globalThis.__originalFunctionToString', context), true);
}

{
  const { context, window, document } = createContext();
  vm.runInContext(shimSource, context, { filename: 'compat-shim.js' });
  document.dispatchEvent(new FakeCustomEvent('__grax_guard_state_v1__', { detail: { enabled: true } }));
  const runtimeError = new FakeEvent('error', {
    message: 'ReferenceError: mixpanel is not defined',
    error: new Error('mixpanel is not defined')
  });
  window.dispatchEvent(runtimeError);
  assert.equal(runtimeError.defaultPrevented, true);
  assert.equal(typeof context.mixpanel, 'function');
}

{
  const { context, window, document } = createContext('https://claude.ai/new');
  vm.runInContext(shimSource, context, { filename: 'compat-shim.js' });
  document.dispatchEvent(new FakeCustomEvent('__grax_guard_state_v1__', { detail: { enabled: true } }));
  window.dispatchEvent(new FakeCustomEvent('__grax_telemetry_mock_ready_v1__'));
  assert.equal(context.mixpanel, undefined, 'Claude must remain native and proxy-free');
  assert.equal(context.__graxCompatibilityShimInstalledV4__, undefined, 'Claude bypass must happen before shim installation');
}

{
  const { context, window } = createContext();
  let readySignals = 0;
  window.addEventListener('__grax_telemetry_mock_ready_v1__', () => { readySignals += 1; });
  vm.runInContext(mockSource, context, { filename: 'mocks/telemetry.js' });

  assert.equal(readySignals, 1);
  assert.equal(typeof context.analytics.user.profile.identity.resolve().track(), 'function');
  assert.equal(typeof context.datadogRum.startView().addAction(), 'function');
  assert.equal(typeof context.Intercom('boot').update(), 'function');
  assert.equal(vm.runInContext('Function.prototype.toString.call(datadogRum)', context), 'function () { [native code] }');
  assert.equal(Array.isArray(context.dataLayer), true);
  context.gtag('config', 'G-TEST');
  assert.equal(context.dataLayer.length, 1);
}

console.log('PASS · recursive MAIN-world telemetry compatibility shim');
