(function (win, doc) {
  'use strict';

  var CHANNEL = 'vareoncalc_preview_v2';
  var PROTOCOL_VERSION = '2.0';
  var DEFAULT_DEBOUNCE_MS = 90;
  var DEFAULT_TIMEOUT_MS = 9000;
  var DEFAULT_MIN_HEIGHT = 220;
  var MIN_WIDTH = 320;
  var MAX_WIDTH = 2000;
  var MIN_HEIGHT = 220;
  var MAX_HEIGHT = 2000;
  var MIN_ZOOM = 30;
  var MAX_ZOOM = 200;
  var MIN_PAN = -12000;
  var MAX_PAN = 12000;

  function isObject(value) {
    return !!value && typeof value === 'object' && !Array.isArray(value);
  }

  function parseOrigin(value) {
    if (typeof value !== 'string' || !value) {
      return '';
    }
    try {
      return new URL(value, win.location.origin).origin;
    } catch {
      return '';
    }
  }

  function sanitizeCalcType(value) {
    if (typeof value !== 'string') {
      return 'loan';
    }
    var normalized = value.toLowerCase().trim().replace(/[^a-z0-9\-]/g, '');
    return normalized || 'loan';
  }

  function clampNumber(rawValue, min, max, fallback) {
    var parsed = parseFloat(rawValue);
    if (!Number.isFinite(parsed)) {
      return fallback;
    }
    return Math.min(max, Math.max(min, parsed));
  }

  function normalizeZoom(value) {
    if (typeof value === 'string') {
      var normalized = value.toLowerCase().trim();
      if (normalized === 'fit') {
        return 'fit';
      }
    }
    return Math.round(clampNumber(value, MIN_ZOOM, MAX_ZOOM, 100));
  }

  function normalizeViewport(rawViewport) {
    var source = isObject(rawViewport) ? rawViewport : {};
    return {
      width: Math.round(clampNumber(source.width, MIN_WIDTH, MAX_WIDTH, 1024)),
      height: Math.round(clampNumber(source.height, MIN_HEIGHT, MAX_HEIGHT, 220)),
      zoom: normalizeZoom(source.zoom),
      panX: Math.round(clampNumber(source.panX, MIN_PAN, MAX_PAN, 0)),
      panY: Math.round(clampNumber(source.panY, MIN_PAN, MAX_PAN, 0))
    };
  }

  function normalizeConfig(rawConfig) {
    var source = isObject(rawConfig) ? rawConfig : {};
    var config = {
      type: sanitizeCalcType(source.type),
      viewport: normalizeViewport(source.viewport)
    };

    if (typeof source.theme === 'string' && source.theme.trim() !== '') {
      config.theme = source.theme.trim();
    }

    if (typeof source.branding !== 'undefined') {
      config.branding = !!source.branding;
    }

    ['colors', 'design', 'formatting', 'measurement', 'assets', 'accessibility', 'energyDefaults'].forEach(function (key) {
      if (!isObject(source[key])) {
        return;
      }
      config[key] = Object.assign({}, source[key]);
    });

    return config;
  }

  function buildFrameUrl(baseUrl, nonce, context) {
    if (!baseUrl || typeof baseUrl !== 'string') {
      return '';
    }

    var url;
    try {
      url = new URL(baseUrl, win.location.origin);
    } catch {
      return '';
    }

    if (nonce) {
      url.searchParams.set('nonce', String(nonce));
    }
    if (context) {
      url.searchParams.set('context', String(context));
    }
    url.searchParams.set('preview_protocol', PROTOCOL_VERSION);
    return url.toString();
  }

  function PreviewV2(options) {
    this.options = options || {};
    this.container = this.options.container || null;
    this.frame = null;
    this.ready = false;
    this.destroyed = false;
    this.lastHeight = DEFAULT_MIN_HEIGHT;
    this.pendingConfig = null;
    this.latestConfig = normalizeConfig(this.options.initialConfig || {});
    this.handshakeTimer = null;
    this.updateTimer = null;
    this.messageHandler = this.handleMessage.bind(this);
  }

  PreviewV2.prototype.getAllowedOrigins = function () {
    var origins = [];
    var expectedOrigin = parseOrigin(this.options.expectedOrigin);
    var frameOrigin = parseOrigin(this.frame && this.frame.src ? this.frame.src : '');
    var frameUrlOrigin = parseOrigin(this.options.frameUrl);
    var currentOrigin = parseOrigin(win.location.origin);

    [expectedOrigin, frameOrigin, frameUrlOrigin, currentOrigin].forEach(function (origin) {
      if (origin && origins.indexOf(origin) === -1) {
        origins.push(origin);
      }
    });

    return origins;
  };

  PreviewV2.prototype.getExpectedOrigin = function () {
    var origins = this.getAllowedOrigins();
    return origins.length ? origins[0] : '';
  };

  PreviewV2.prototype.reportError = function (message) {
    if (typeof this.options.onError === 'function') {
      this.options.onError(message || 'Preview unavailable.');
    }
  };

  PreviewV2.prototype.reportReady = function () {
    if (typeof this.options.onReady === 'function') {
      this.options.onReady();
    }
  };

  PreviewV2.prototype.reportResize = function (height) {
    if (typeof this.options.onResize === 'function') {
      this.options.onResize(height);
    }
  };

  PreviewV2.prototype.reportWheelIntent = function (payload) {
    if (typeof this.options.onWheelIntent === 'function') {
      this.options.onWheelIntent(payload || {});
    }
  };

  PreviewV2.prototype.reportPanIntent = function (payload) {
    if (typeof this.options.onPanIntent === 'function') {
      this.options.onPanIntent(payload || {});
    }
  };

  PreviewV2.prototype.postMessage = function (type, payload) {
    if (!this.frame || !this.frame.contentWindow) {
      return;
    }
    try {
      this.frame.contentWindow.postMessage(
        {
          channel: CHANNEL,
          protocolVersion: PROTOCOL_VERSION,
          type: type,
          payload: isObject(payload) ? payload : {}
        },
        this.getExpectedOrigin() || '*'
      );
    } catch {
      this.reportError('Unable to communicate with preview frame.');
    }
  };

  PreviewV2.prototype.flushLatestConfig = function () {
    if (!this.ready) {
      this.pendingConfig = this.latestConfig;
      return;
    }
    this.postMessage('vc2:update', { config: this.latestConfig });
  };

  PreviewV2.prototype.updateConfig = function (config, debounceMs) {
    if (this.destroyed) {
      return;
    }
    this.latestConfig = normalizeConfig(config);
    if (!this.ready) {
      this.pendingConfig = this.latestConfig;
      return;
    }

    var delay = Number.isFinite(debounceMs) ? debounceMs : DEFAULT_DEBOUNCE_MS;
    if (delay <= 0) {
      this.flushLatestConfig();
      return;
    }

    if (this.updateTimer) {
      clearTimeout(this.updateTimer);
    }
    this.updateTimer = setTimeout(function () {
      this.flushLatestConfig();
    }.bind(this), delay);
  };

  PreviewV2.prototype.setViewport = function (viewport, debounceMs) {
    var next = normalizeConfig(
      Object.assign({}, this.latestConfig || {}, {
        viewport: viewport || {}
      })
    );
    this.updateConfig(next, debounceMs);
  };

  PreviewV2.prototype.handleMessage = function (event) {
    if (this.destroyed || !this.frame || !this.frame.contentWindow) {
      return;
    }

    if (event.source !== this.frame.contentWindow) {
      return;
    }

    var allowedOrigins = this.getAllowedOrigins();
    if (event.origin && allowedOrigins.length > 0 && allowedOrigins.indexOf(event.origin) === -1) {
      return;
    }

    var data = event.data;
    if (!isObject(data) || data.channel !== CHANNEL || typeof data.type !== 'string') {
      return;
    }

    if (data.type === 'vc2:ready') {
      this.ready = true;
      if (this.handshakeTimer) {
        clearTimeout(this.handshakeTimer);
        this.handshakeTimer = null;
      }
      this.reportReady();
      if (this.pendingConfig) {
        this.postMessage('vc2:update', { config: this.pendingConfig });
        this.pendingConfig = null;
      } else if (this.latestConfig) {
        this.postMessage('vc2:update', { config: this.latestConfig });
      }
      return;
    }

    if (data.type === 'vc2:resize') {
      var payload = isObject(data.payload) ? data.payload : {};
      var nextHeight = parseInt(payload.height, 10);
      if (!Number.isFinite(nextHeight)) {
        return;
      }
      this.lastHeight = Math.max(DEFAULT_MIN_HEIGHT, Math.min(MAX_HEIGHT, nextHeight));
      this.reportResize(this.lastHeight);
      return;
    }

    if (data.type === 'vc2:error') {
      var errPayload = isObject(data.payload) ? data.payload : {};
      this.reportError(errPayload.message || 'Preview frame reported an error.');
      return;
    }

    if (data.type === 'vc2:wheel-intent') {
      var wheelPayload = isObject(data.payload) ? data.payload : {};
      this.reportWheelIntent(wheelPayload);
      return;
    }

    if (data.type === 'vc2:pan-intent') {
      var panPayload = isObject(data.payload) ? data.payload : {};
      this.reportPanIntent(panPayload);
    }
  };

  PreviewV2.prototype.teardownFrame = function () {
    if (this.frame && this.frame.parentNode) {
      this.frame.parentNode.removeChild(this.frame);
    }
    this.frame = null;
    this.ready = false;
  };

  PreviewV2.prototype.mount = function () {
    if (!this.container || this.destroyed) {
      return false;
    }

    if (this.handshakeTimer) {
      clearTimeout(this.handshakeTimer);
      this.handshakeTimer = null;
    }

    this.teardownFrame();

    var src = buildFrameUrl(this.options.frameUrl, this.options.nonce, this.options.context);
    if (!src) {
      this.reportError('Preview URL is missing.');
      return false;
    }

    while (this.container.firstChild) {
      this.container.removeChild(this.container.firstChild);
    }

    var frame = doc.createElement('iframe');
    frame.className = this.options.frameClassName || 'vareoncalc-preview-v2-iframe';
    frame.setAttribute('sandbox', 'allow-scripts allow-same-origin');
    frame.setAttribute('title', this.options.title || 'Calculator preview');
    frame.setAttribute('loading', 'eager');
    frame.setAttribute('referrerpolicy', 'same-origin');
    frame.style.width = '100%';
    frame.style.display = 'block';
    frame.style.border = '0';
    frame.style.minHeight = (this.options.minHeight || DEFAULT_MIN_HEIGHT) + 'px';
    frame.src = src;

    this.container.appendChild(frame);
    this.frame = frame;
    this.ready = false;
    this.pendingConfig = null;

    win.addEventListener('message', this.messageHandler, false);

    var timeoutMs = Number.isFinite(this.options.timeoutMs) ? this.options.timeoutMs : DEFAULT_TIMEOUT_MS;
    this.handshakeTimer = setTimeout(function () {
      if (!this.ready) {
        this.reportError('Preview frame did not respond.');
      }
    }.bind(this), timeoutMs);

    return true;
  };

  PreviewV2.prototype.retry = function () {
    if (this.destroyed) {
      return;
    }
    this.mount();
  };

  PreviewV2.prototype.destroy = function () {
    this.destroyed = true;

    if (this.handshakeTimer) {
      clearTimeout(this.handshakeTimer);
      this.handshakeTimer = null;
    }
    if (this.updateTimer) {
      clearTimeout(this.updateTimer);
      this.updateTimer = null;
    }

    win.removeEventListener('message', this.messageHandler, false);
    this.teardownFrame();
  };

  win.VAREONCALC_PREVIEW_V2 = {
    create: function (options) {
      return new PreviewV2(options || {});
    },
    normalizeConfig: normalizeConfig,
    normalizeViewport: normalizeViewport,
    constants: {
      channel: CHANNEL,
      protocolVersion: PROTOCOL_VERSION,
      minWidth: MIN_WIDTH,
      maxWidth: MAX_WIDTH,
      minHeight: MIN_HEIGHT,
      maxHeight: MAX_HEIGHT,
      minZoom: MIN_ZOOM,
      maxZoom: MAX_ZOOM,
      minPan: MIN_PAN,
      maxPan: MAX_PAN
    }
  };
})(window, document);
