(function (win) {
  'use strict';

  var PROTOCOL_VERSION = '1.0';
  var CHANNEL = 'vareoncalc_preview';
  var DEFAULT_DEBOUNCE_MS = 80;
  var DEFAULT_TIMEOUT_MS = 5000;

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

  function isDebugEnabled() {
    return !!(win.VAREON_DEBUG || (win.VAREONCALC_CFG && win.VAREONCALC_CFG.debug));
  }

  function log() {
    if (!isDebugEnabled() || !win.console || typeof win.console.log !== 'function') {
      return;
    }
    var args = Array.prototype.slice.call(arguments);
    args.unshift('[Vareon Preview Bridge]');
    win.console.log.apply(win.console, args);
  }

  function cloneShallow(value) {
    if (Array.isArray(value)) {
      return value.slice();
    }
    if (isObject(value)) {
      return Object.assign({}, value);
    }
    return value;
  }

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

  function pruneEmptyObject(value) {
    if (!isObject(value)) {
      return value;
    }

    var out = {};
    Object.keys(value).forEach(function (key) {
      var raw = value[key];
      if (raw === undefined || raw === null || raw === '') {
        return;
      }
      if (isObject(raw)) {
        var pruned = pruneEmptyObject(raw);
        if (Object.keys(pruned).length > 0) {
          out[key] = pruned;
        }
        return;
      }
      out[key] = raw;
    });
    return out;
  }

  function normalizeConfig(rawConfig) {
    var source = isObject(rawConfig) ? rawConfig : {};
    var config = {};

    config.type = (typeof source.type === 'string' && source.type.trim() !== '') ? source.type.trim() : 'bmi';

    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;
      }
      var pruned = pruneEmptyObject(cloneShallow(source[key]));
      if (Object.keys(pruned).length > 0) {
        config[key] = pruned;
      }
    });

    return config;
  }

  function buildFrameUrl(baseUrl, nonce, context) {
    if (!baseUrl) {
      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 PreviewBridge(options) {
    this.options = options || {};
    this.container = this.options.container || null;
    this.frame = null;
    this.ready = false;
    this.destroyed = false;
    this.handshakeTimer = null;
    this.pendingConfig = null;
    this.messageHandler = this.handleMessage.bind(this);
    this.updateTimer = null;
    this.latestMessage = null;
    this.lastHeight = 0;
  }

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

  PreviewBridge.prototype.getAllowedOrigins = function () {
    var origins = [];

    var expectedOrigin = parseOrigin(this.options.expectedOrigin);
    if (expectedOrigin) {
      origins.push(expectedOrigin);
    }

    var frameOrigin = parseOrigin(this.frame && this.frame.src ? this.frame.src : '');
    if (frameOrigin && origins.indexOf(frameOrigin) === -1) {
      origins.push(frameOrigin);
    }

    var frameUrlOrigin = parseOrigin(this.options.frameUrl);
    if (frameUrlOrigin && origins.indexOf(frameUrlOrigin) === -1) {
      origins.push(frameUrlOrigin);
    }

    var currentOrigin = parseOrigin(win.location.origin);
    if (currentOrigin && origins.indexOf(currentOrigin) === -1) {
      origins.push(currentOrigin);
    }

    return origins;
  };

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

    this.teardownFrame();

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

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

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

    this.ready = false;
    this.pendingConfig = null;
    this.lastHeight = 0;

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

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

    log('mount', { src: src });
    return true;
  };

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

  PreviewBridge.prototype.reportError = function (message) {
    if (typeof this.options.onError === 'function') {
      this.options.onError(message || 'Preview failed');
    }
    log('error', message);
  };

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

  PreviewBridge.prototype.applyHeight = function (height) {
    if (!this.frame) {
      return;
    }

    var minHeight = Number.isFinite(this.options.minHeight) ? this.options.minHeight : 420;
    var maxHeight = Number.isFinite(this.options.maxHeight) ? this.options.maxHeight : 2200;
    var nextHeight = parseInt(height, 10);
    if (!Number.isFinite(nextHeight)) {
      return;
    }

    nextHeight = Math.max(minHeight, Math.min(maxHeight, nextHeight));
    if (Math.abs(nextHeight - this.lastHeight) < 2) {
      return;
    }
    this.lastHeight = nextHeight;
    this.frame.style.height = nextHeight + 'px';
  };

  PreviewBridge.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 (isDebugEnabled() && data.type !== 'vc_preview:resize') {
      log('message', data.type, data.payload || {});
    }

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

    if (data.type === 'vc_preview:resize') {
      var payload = isObject(data.payload) ? data.payload : {};
      this.applyHeight(payload.height);
      return;
    }

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

  PreviewBridge.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 send preview update.');
    }
  };

  PreviewBridge.prototype.queueConfig = function (config) {
    var normalized = normalizeConfig(config);
    if (!this.ready) {
      this.pendingConfig = normalized;
      return;
    }
    this.postMessage('vc_preview:update', { config: normalized });
  };

  PreviewBridge.prototype.sendConfig = function (config, debounceMs) {
    if (this.destroyed) {
      return;
    }

    this.latestMessage = normalizeConfig(config);
    log('queue-config', this.latestMessage);
    if (!this.ready) {
      if (this.updateTimer) {
        clearTimeout(this.updateTimer);
        this.updateTimer = null;
      }
      this.pendingConfig = this.latestMessage;
      return;
    }

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

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

  PreviewBridge.prototype.retry = function () {
    if (this.destroyed) {
      return;
    }
    this.mount();
    if (this.latestMessage) {
      this.pendingConfig = this.latestMessage;
    }
  };

  PreviewBridge.prototype.destroy = function () {
    this.destroyed = true;
    if (this.updateTimer) {
      clearTimeout(this.updateTimer);
      this.updateTimer = null;
    }
    if (this.handshakeTimer) {
      clearTimeout(this.handshakeTimer);
      this.handshakeTimer = null;
    }
    win.removeEventListener('message', this.messageHandler, false);
    this.teardownFrame();
  };

  win.VareoncalcPreviewBridge = {
    protocolVersion: PROTOCOL_VERSION,
    normalizeConfig: normalizeConfig,
    buildFrameUrl: buildFrameUrl,
    create: function (options) {
      return new PreviewBridge(options || {});
    }
  };
})(window);
