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

  var CHANNEL = 'vareoncalc_preview_v2';
  var PROTOCOL_VERSION = '2.0';
  var SAFE_PADDING = 0;
  var MIN_HEIGHT = 220;
  var MAX_HEIGHT = 2000;
  var LOADER_READY_POLL_MS = 25;
  var LOADER_READY_TIMEOUT_MS = 9000;

  var mountNode = null;
  var latestConfig = null;
  var resizeObserver = null;
  var mutationObserver = null;
  var resizeRafId = 0;
  var readySent = false;
  var loaderTimer = null;
  var loaderStartedAt = 0;
  var loaderWaiting = false;
  var loaderCallbacks = [];
  var loaderReady = false;
  var lastReportedHeight = 0;
  var settleResizeTimers = [];

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

  function getParentOrigin() {
    try {
      if (doc.referrer) {
        return new URL(doc.referrer).origin;
      }
    } catch {
      return '*';
    }
    return '*';
  }

  function post(type, payload) {
    if (!win.parent || win.parent === win) {
      return;
    }
    try {
      win.parent.postMessage(
        {
          channel: CHANNEL,
          protocolVersion: PROTOCOL_VERSION,
          type: type,
          payload: isObject(payload) ? payload : {}
        },
        getParentOrigin()
      );
    } catch {
      // Ignore cross-window messaging failures.
    }
  }

  function isTrustedParentMessage(event) {
    if (!event || !win.parent || win.parent === win) {
      return false;
    }

    if (event.source !== win.parent) {
      return false;
    }

    var parentOrigin = getParentOrigin();
    if (parentOrigin !== '*' && event.origin !== parentOrigin) {
      return false;
    }

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

    return data.protocolVersion === PROTOCOL_VERSION;
  }

  function ensureMountNode() {
    if (mountNode && mountNode.isConnected) {
      return mountNode;
    }
    mountNode = doc.getElementById('vareoncalc-preview-frame-mount');
    return mountNode;
  }

  function getNodeHeight(node) {
    if (!node) {
      return 0;
    }

    var rectHeight = 0;
    if (typeof node.getBoundingClientRect === 'function') {
      rectHeight = node.getBoundingClientRect().height;
    }

    return Math.max(
      node.offsetHeight || 0,
      node.clientHeight || 0,
      rectHeight || 0
    );
  }

  function shouldSkipForMeasurement(node, styles) {
    var computed = styles;
    if (!node || node.nodeType !== 1) {
      return true;
    }
    if (node.hidden || node.getAttribute('hidden') !== null || node.getAttribute('aria-hidden') === 'true') {
      return true;
    }
    if (!computed && typeof win.getComputedStyle === 'function') {
      computed = win.getComputedStyle(node);
    }
    if (!computed) {
      return false;
    }
    if (computed.display === 'none' || computed.visibility === 'hidden' || computed.visibility === 'collapse') {
      return true;
    }
    if (parseFloat(computed.opacity || '1') <= 0) {
      return true;
    }
    return computed.position === 'fixed';
  }

  function getVisibleDescendantBottom(node, rootTop, rootBottom, intrinsicHeight) {
    if (!node || typeof node.querySelectorAll !== 'function') {
      return intrinsicHeight;
    }

    var maxBottom = intrinsicHeight;
    var descendants = node.querySelectorAll('*');
    Array.prototype.forEach.call(descendants, function (element) {
      if (!element || element.nodeType !== 1) {
        return;
      }

      var styles = typeof win.getComputedStyle === 'function' ? win.getComputedStyle(element) : null;
      if (shouldSkipForMeasurement(element, styles)) {
        return;
      }
      if (typeof element.getBoundingClientRect !== 'function') {
        return;
      }

      var rect = element.getBoundingClientRect();
      if ((!rect.width && !rect.height) || !Number.isFinite(rect.top) || !Number.isFinite(rect.bottom)) {
        return;
      }
      if (rect.bottom <= rootTop || rect.top >= rootBottom) {
        return;
      }

      maxBottom = Math.max(maxBottom, Math.ceil(rect.bottom - rootTop));
    });

    return maxBottom;
  }

  function measureMountContentHeight(node) {
    if (!node) {
      return 0;
    }

    var intrinsicHeight = getNodeHeight(node);
    if (typeof node.getBoundingClientRect !== 'function') {
      return intrinsicHeight;
    }

    var rootRect = node.getBoundingClientRect();
    var rootTop = Number.isFinite(rootRect.top) ? rootRect.top : 0;
    var rootBottom = Number.isFinite(rootRect.bottom) ? rootRect.bottom : (rootTop + intrinsicHeight);
    var visibleBottom = getVisibleDescendantBottom(node, rootTop, rootBottom, intrinsicHeight);

    return Math.max(intrinsicHeight, visibleBottom);
  }

  function measureHeight() {
    var node = ensureMountNode();
    var height = measureMountContentHeight(node);

    if (node) {
      var sibling = node.nextElementSibling;
      while (sibling) {
        if (sibling.classList && sibling.classList.contains('vareoncalc-brand')) {
          height += sibling.offsetHeight || 0;
        }
        sibling = sibling.nextElementSibling;
      }
    }

    if (height > 0) {
      return Math.min(MAX_HEIGHT, Math.max(MIN_HEIGHT, Math.ceil(height + SAFE_PADDING)));
    }

    return MIN_HEIGHT;
  }

  function notifyResize() {
    var nextHeight = measureHeight();
    if (Math.abs(nextHeight - lastReportedHeight) < 1) {
      return;
    }
    lastReportedHeight = nextHeight;
    post('vc2:resize', { height: nextHeight });
  }

  function scheduleResize() {
    if (resizeRafId) {
      return;
    }
    if (typeof win.requestAnimationFrame === 'function') {
      resizeRafId = win.requestAnimationFrame(function () {
        resizeRafId = 0;
        notifyResize();
      });
      return;
    }
    resizeRafId = setTimeout(function () {
      resizeRafId = 0;
      notifyResize();
    }, 0);
  }

  function scheduleSettledResize() {
    scheduleResize();

    settleResizeTimers.forEach(function (timerId) {
      clearTimeout(timerId);
    });
    settleResizeTimers = [80, 220, 420].map(function (delayMs) {
      return setTimeout(function () {
        scheduleResize();
      }, delayMs);
    });
  }

  function bindResizeObservers() {
    if (typeof win.ResizeObserver !== 'function') {
      setInterval(notifyResize, 450);
      return;
    }

    if (resizeObserver) {
      resizeObserver.disconnect();
    }

    resizeObserver = new win.ResizeObserver(function () {
      scheduleResize();
    });

    if (ensureMountNode()) {
      resizeObserver.observe(mountNode);
    }

    if (typeof win.MutationObserver !== 'function' || !mountNode) {
      return;
    }

    if (mutationObserver) {
      mutationObserver.disconnect();
    }

    mutationObserver = new win.MutationObserver(function () {
      scheduleSettledResize();
    });
    mutationObserver.observe(mountNode, {
      childList: true,
      subtree: true,
      attributes: true,
      characterData: true
    });
  }

  function bindWheelForwarding() {
    doc.addEventListener('wheel', function (event) {
      if (!event || (!event.ctrlKey && !event.metaKey)) {
        return;
      }

      event.preventDefault();
      post('vc2:wheel-intent', {
        deltaY: Number.isFinite(event.deltaY) ? event.deltaY : 0
      });
    }, { passive: false, capture: true });
  }

  function bindRightClickPanForwarding() {
    var rightPanActive = false;
    var lastScreenX = 0;
    var lastScreenY = 0;

    function onMouseMove(event) {
      if (!rightPanActive) {
        return;
      }
      event.preventDefault();
      var nextScreenX = Number.isFinite(event.screenX) ? event.screenX : lastScreenX;
      var nextScreenY = Number.isFinite(event.screenY) ? event.screenY : lastScreenY;
      var deltaX = nextScreenX - lastScreenX;
      var deltaY = nextScreenY - lastScreenY;
      lastScreenX = nextScreenX;
      lastScreenY = nextScreenY;
      if (!deltaX && !deltaY) {
        return;
      }
      post('vc2:pan-intent', {
        phase: 'move',
        dx: deltaX,
        dy: deltaY
      });
    }

    function onMouseUp(event) {
      if (!rightPanActive) {
        return;
      }
      event.preventDefault();
      rightPanActive = false;
      post('vc2:pan-intent', { phase: 'end' });
      win.removeEventListener('mousemove', onMouseMove, true);
      win.removeEventListener('mouseup', onMouseUp, true);
    }

    doc.addEventListener('mousedown', function (event) {
      if (!event || event.button !== 2) {
        return;
      }
      event.preventDefault();
      rightPanActive = true;
      lastScreenX = Number.isFinite(event.screenX) ? event.screenX : 0;
      lastScreenY = Number.isFinite(event.screenY) ? event.screenY : 0;
      post('vc2:pan-intent', {
        phase: 'start'
      });
      win.addEventListener('mousemove', onMouseMove, true);
      win.addEventListener('mouseup', onMouseUp, true);
    }, true);

    doc.addEventListener('contextmenu', function (event) {
      event.preventDefault();
    }, true);
  }

  function flushLoaderCallbacks(isReady) {
    if (loaderTimer) {
      clearTimeout(loaderTimer);
      loaderTimer = null;
    }
    loaderWaiting = false;
    var callbacks = loaderCallbacks.slice();
    loaderCallbacks = [];
    callbacks.forEach(function (callback) {
      try {
        callback(!!isReady);
      } catch {
        // Ignore callback failures.
      }
    });
  }

  function waitForLoaderReady(callback) {
    if (typeof callback === 'function') {
      loaderCallbacks.push(callback);
    }

    if (loaderReady || typeof win.vareoncalcMount === 'function') {
      loaderReady = true;
      flushLoaderCallbacks(true);
      return;
    }

    if (loaderWaiting) {
      return;
    }

    loaderWaiting = true;
    loaderStartedAt = Date.now();

    function pollLoader() {
      if (typeof win.vareoncalcMount === 'function') {
        loaderReady = true;
        flushLoaderCallbacks(true);
        return;
      }

      if ((Date.now() - loaderStartedAt) >= LOADER_READY_TIMEOUT_MS) {
        flushLoaderCallbacks(false);
        return;
      }

      loaderTimer = setTimeout(pollLoader, LOADER_READY_POLL_MS);
    }

    pollLoader();
  }

  function applyPreviewSurfaceWidth(node, config) {
    if (!node || !node.style) {
      return;
    }

    node.style.removeProperty('max-width');
    node.style.removeProperty('margin');
    node.style.removeProperty('box-sizing');

    var design = isObject(config && config.design) ? config.design : null;
    var previewWidth = design ? parseInt(design.preview_surface_width, 10) : NaN;
    if (!Number.isFinite(previewWidth) || previewWidth <= 0) {
      return;
    }

    node.style.maxWidth = String(previewWidth) + 'px';
    node.style.margin = '0 auto';
    node.style.boxSizing = 'border-box';
  }

  function syncGlobalConfig(config) {
    if (!isObject(config)) {
      return;
    }

    var current = isObject(win.VAREONCALC_CFG) ? win.VAREONCALC_CFG : {};
    var nextConfig = Object.assign({}, current);

    [
      'type',
      'theme',
      'layout',
      'branding',
      'showBrandingText',
      'proEnabled',
      'version',
      'assetVersion',
      'edition',
      'pluginUrl',
      'devNonce',
      'calculatorOptions'
    ].forEach(function (key) {
      if (typeof config[key] !== 'undefined') {
        nextConfig[key] = config[key];
      }
    });

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

    win.VAREONCALC_CFG = nextConfig;
    win.vareoncalc_CFG = nextConfig;
  }

  function mountCalculator(config) {
    var node = ensureMountNode();
    if (!node) {
      post('vc2:error', { message: 'Preview mount node not found.' });
      return;
    }

    latestConfig = isObject(config) ? config : latestConfig;
    if (!latestConfig) {
      return;
    }

    syncGlobalConfig(latestConfig);
    applyPreviewSurfaceWidth(node, latestConfig);
    node.setAttribute('data-config', JSON.stringify(latestConfig));
    node.removeAttribute('data-vareoncalc-ready');
    node.innerHTML = '';

    if (typeof win.vareoncalcMount !== 'function') {
      waitForLoaderReady(function (isReady) {
        if (!isReady) {
          post('vc2:error', { message: 'Preview loader did not become ready in time.' });
          return;
        }
        mountCalculator(latestConfig);
      });
      return;
    }

    try {
      win.vareoncalcMount(node);
      scheduleSettledResize();
    } catch (error) {
      post('vc2:error', {
        message: (error && error.message) ? error.message : 'Failed to mount calculator preview.'
      });
    }
  }

  function handleMessage(event) {
    if (!isTrustedParentMessage(event)) {
      return;
    }

    var data = event.data;

    if (data.type === 'vc2:update') {
      var payload = isObject(data.payload) ? data.payload : {};
      if (isObject(payload.config)) {
        mountCalculator(payload.config);
      }
    }
  }

  function init() {
    ensureMountNode();
    bindResizeObservers();
    bindWheelForwarding();
    bindRightClickPanForwarding();
    win.addEventListener('message', handleMessage, false);
    win.addEventListener('resize', scheduleResize, false);

    if (!readySent) {
      readySent = true;
      post('vc2:ready', { protocolVersion: PROTOCOL_VERSION });
    }

    waitForLoaderReady(function (isReady) {
      if (!isReady || !latestConfig) {
        return;
      }
      mountCalculator(latestConfig);
    });
  }

  if (doc.readyState === 'loading') {
    doc.addEventListener('DOMContentLoaded', init, { once: true });
  } else {
    init();
  }
})(window, document);
