const TEXT_SIZES = ['small', 'normal', 'big'];
const ARROW_STYLES = ['normal', 'left', 'right'];
const SHAPE_TYPES = ['rectangle', 'rounded', 'oval', 'line'];
const REDACT_MODES = ['pixelate', 'solid'];

(function () {
  var DEFAULT_ANNOTATION_COLORS = {
    text: '#ff0000',
    arrow: '#ff0000',
    shape: '#ff0000',
    redact: '#000000',
  };
  var TEXT_SIZE_PRESETS = {
    small: 16,
    normal: 32,
    big: 56,
  };

  var STATE = {
    currentTextSize: 'normal',
    currentArrowSize: 'normal',
    currentArrowStyle: 'normal',
    currentShapeType: 'rectangle',
    annotationDefaults: Object.assign(
      {},
      DEFAULT_ANNOTATION_COLORS,
      window.XNAPPER_ANNOTATION_DEFAULTS || {}
    ),
    textAnnotationDefaults: Object.assign(
      { size: 32 },
      window.XNAPPER_TEXT_ANNOTATION_DEFAULTS || {}
    ),
    redactDefaults: Object.assign(
      { mode: 'pixelate' },
      window.XNAPPER_REDACT_DEFAULTS || {}
    ),
    arrowDefaults: Object.assign(
      { style: 'normal', weight: 'medium' },
      window.XNAPPER_ARROW_DEFAULTS || {}
    ),
    imageSize: Object.assign({ width: 0, height: 0 }, window.XNAPPER_IMAGE_SIZE || {}),
    redactPixelPatterns: {},
    redactPixelPatternFailed: false,
    redactPixelPatternPending: false,
    redactPixelPatternLoadHandler: null,
    redactInteractionState: null,
    captureGenerationID: null,
    annotations: {},
    selectedAnnotationID: null,
    currentController: null,
    // avoid the native app from closing too early
    skipNextCancelOperation: false,
    // Save mouse position (clientX, clientY) at all time
    // Used when we don't have access to mouse event
    mouse: {},
    xOffset: {},
    yOffset: {},
    documentSerial: 0,
    suppressDocumentChanges: false,
    documentSnapshotTimer: null,
    documentSnapshotCommitPending: false,
    documentChangeFramePending: false,
    mode: 'free',
    setMode: function (mode) {
      this.mode = mode;
      try {
        if (window.webkit) {
          window.webkit.messageHandlers.events.postMessage({
            name: 'CHANGE_MODE',
            payload: mode,
          });
        }
      } catch (e) {
        console.error(e);
      }
    },
    maxZ: 1,
  };

  window.STATE = STATE;

  function postEvent(name, payload) {
    try {
      if (window.webkit) {
        window.webkit.messageHandlers.events.postMessage({
          name: name,
          payload: payload,
        });
      }
    } catch (e) {
      console.error(e);
    }
  }

  function finiteNumber(value, fallback) {
    var number = Number(value);
    return Number.isFinite(number) ? number : fallback;
  }

  function rectPayload(rect) {
    return {
      x: finiteNumber(rect && rect.x, 0),
      y: finiteNumber(rect && rect.y, 0),
      width: finiteNumber(rect && rect.width, 0),
      height: finiteNumber(rect && rect.height, 0),
    };
  }

  function scheduleDocumentSnapshot() {
    if (STATE.suppressDocumentChanges) {
      return;
    }
    if (STATE.documentSnapshotTimer) {
      clearTimeout(STATE.documentSnapshotTimer);
    }
    STATE.documentSnapshotTimer = setTimeout(function () {
      STATE.documentSnapshotTimer = null;
      STATE.publishDocumentSnapshot();
    }, 200);
  }

  function publishCommittedDocumentSnapshot() {
    if (STATE.suppressDocumentChanges || STATE.documentSnapshotCommitPending) {
      return;
    }
    if (STATE.documentSnapshotTimer) {
      clearTimeout(STATE.documentSnapshotTimer);
      STATE.documentSnapshotTimer = null;
    }
    STATE.documentSnapshotCommitPending = true;
    setTimeout(function () {
      STATE.documentSnapshotCommitPending = false;
      STATE.publishDocumentSnapshot();
    }, 0);
  }

  function reportDocumentChanged() {
    if (STATE.suppressDocumentChanges || STATE.documentChangeFramePending) {
      return;
    }
    STATE.documentChangeFramePending = true;
    requestAnimationFrame(function () {
      STATE.documentChangeFramePending = false;
      postEvent('documentChanged', {
        generationID: STATE.captureGenerationID,
        serial: STATE.documentSerial,
      });
    });
  }

  STATE.markDocumentChanged = function (committed) {
    if (STATE.suppressDocumentChanges) {
      return;
    }
    STATE.documentSerial += 1;
    reportDocumentChanged();
    if (committed) {
      publishCommittedDocumentSnapshot();
    } else {
      scheduleDocumentSnapshot();
    }
  };

  STATE.noteDOMMutations = function (mutations) {
    if (STATE.suppressDocumentChanges) {
      return;
    }
    var editableMutation = mutations.some(function (mutation) {
      var target =
        mutation.target && mutation.target.nodeType === Node.TEXT_NODE
          ? mutation.target.parentElement
          : mutation.target;
      if (!target || !target.closest) {
        return false;
      }
      if (target.closest('.shape-resize-handle, .arrow-resize-handle')) {
        return false;
      }
      var annotationElement = target.closest('[data-annotation-id]');
      if (!annotationElement) {
        return false;
      }
      var provenance = annotationElement.getAttribute(
        'data-redact-provenance'
      );
      if (provenance && provenance !== 'user') {
        return false;
      }
      if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
        return false;
      }
      return true;
    });
    if (editableMutation) {
      STATE.markDocumentChanged(false);
    }
  };

  function anyParentContainsClass(element, className) {
    if (element.classList && element.classList.contains(className)) {
      return element;
    }
    return (
      element.parentNode &&
      anyParentContainsClass(element.parentNode, className)
    );
  }

  function normalizeRedactMode(mode) {
    return REDACT_MODES.indexOf(mode) >= 0 ? mode : 'pixelate';
  }

  function getRedactSvgElement() {
    return document.querySelector('#rects svg');
  }

  function getImageSize() {
    var image = document.querySelector('#target .image img');
    var width =
      parseFloat(STATE.imageSize.width) ||
      (image && image.naturalWidth) ||
      1;
    var height =
      parseFloat(STATE.imageSize.height) ||
      (image && image.naturalHeight) ||
      1;
    return { width: width, height: height };
  }

  STATE.ensureRedactLayer = function () {
    var svg = getRedactSvgElement();
    if (svg) {
      return svg;
    }

    var imageContainer = document.querySelector('#target .image');
    if (!imageContainer) {
      return null;
    }

    var size = getImageSize();
    var rectsElement = document.createElement('div');
    rectsElement.setAttribute('id', 'rects');
    rectsElement.setAttribute('class', 'rects');

    svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('style', 'width: 100%; height: 100%');
    svg.setAttribute(
      'viewBox',
      '0 0 ' + Math.max(1, size.width) + ' ' + Math.max(1, size.height)
    );
    svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');

    rectsElement.appendChild(svg);
    imageContainer.appendChild(rectsElement);
    return svg;
  };

  // change translate3d value without affecting other transforms of el
  function setTranslate(xPos, yPos, el) {
    var currentTransform = el.style.transform;
    var translateRegex = /translate3d\(.*?\)/;

    if (translateRegex.test(currentTransform)) {
      currentTransform = currentTransform.replace(
        translateRegex,
        'translate3d(' + xPos + 'px, ' + yPos + 'px, 0)'
      );
    } else {
      currentTransform += ' translate3d(' + xPos + 'px, ' + yPos + 'px, 0)';
    }

    el.style.transform = currentTransform;
  }

  function setUpDrag(container) {
    var activeElement = null;
    var currentX;
    var currentY;
    var initialX;
    var initialY;

    container.addEventListener('mousedown', dragStart, false);
    container.addEventListener('mouseup', dragEnd, false);
    container.addEventListener('mousemove', drag, false);

    function dragStart(e) {
      var element = anyParentContainsClass(e.target, 'draggable');

      if (!element) {
        var hitShape = shapeAnnotationAtPoint(e.clientX, e.clientY);
        element = hitShape && hitShape.element;
      }

      if (!element) {
        return;
      }

      if (element.getAttribute('contenteditable')) {
        // check if mousedown inside the element

        var rect = element.getBoundingClientRect();
        var x = e.clientX;
        var y = e.clientY;

        if (
          x < rect.left ||
          x > rect.right ||
          y < rect.top ||
          y > rect.bottom
        ) {
          // user try to drag
        } else {
          // user try to select text
          return;
        }
      }

      if (e.target.classList.contains('prevent-drag')) {
        return;
      }

      if (STATE.mode !== 'free') {
        return;
      }

      // Avoid trigger a file drag event in the native app while dragging a
      // draggable element outside of the webview.
      window._PREVENT_DRAG_SCRIPT = true;

      activeElement = element;
      activeElement.classList.add('dragging');
      var itemID = activeElement.getAttribute('data-item-id');
      if (itemID && STATE.annotations[itemID]) {
        setSelectedAnnotation(itemID);
      }

      initialX = e.clientX - (STATE.xOffset[itemID] || 0);
      initialY = e.clientY - (STATE.yOffset[itemID] || 0);
    }

    function dragEnd(e) {
      initialX = currentX;
      initialY = currentY;

      if (activeElement) {
        activeElement.classList.remove('dragging');
      }
      activeElement = null;
      window._PREVENT_DRAG_SCRIPT = false;
    }

    function drag(e) {
      if (activeElement) {
        e.preventDefault();
        var itemID = activeElement.getAttribute('data-item-id');

        currentX = e.clientX - initialX;
        currentY = e.clientY - initialY;

        STATE.xOffset[itemID] = currentX;
        STATE.yOffset[itemID] = currentY;

        setTranslate(currentX, currentY, activeElement);
        if (
          STATE.moveArrowAnnotationBody &&
          STATE.annotations[itemID] &&
          STATE.annotations[itemID].type === 'arrow'
        ) {
          STATE.moveArrowAnnotationBody(STATE.annotations[itemID], currentX, currentY);
        }
        if (
          STATE.syncSelectedShapeResizeHandles &&
          STATE.annotations[itemID] &&
          STATE.annotations[itemID].type === 'shape'
        ) {
          STATE.syncSelectedShapeResizeHandles();
        }
      }
    }
  }

  function applyAnnotationColor(annotation) {
    if (!annotation || !annotation.element) {
      return;
    }

    var color = annotation.color || STATE.annotationDefaults[annotation.type];
    var element = annotation.element;

    element.setAttribute('data-annotation-color', color);
    element.style.setProperty('--annotation-color', color);

    if (annotation.type === 'text') {
      element.style.color = color;
      return;
    }

    if (annotation.type === 'arrow') {
      element.style.color = color;
      return;
    }

    if (annotation.type === 'shape') {
      element.style.borderColor = color;
      return;
    }

    if (annotation.type === 'redact') {
      applyRedactAnnotationStyle(annotation);
    }
  }

  function applyTextAnnotationSize(annotation) {
    if (!annotation || !annotation.element || annotation.type !== 'text') {
      return;
    }

    var fontSize = annotation.fontSize || STATE.textAnnotationDefaults.size;
    annotation.fontSize = fontSize;
    annotation.element.setAttribute('data-text-size', String(fontSize));
    annotation.element.style.setProperty('--text-font-size', fontSize + 'px');
    annotation.element.style.fontSize = fontSize + 'px';
  }

  function clampRedactPixelBlockSize(size) {
    return Math.max(5, Math.min(22, size));
  }

  function closestRedactPixelBlockSize(size) {
    var allowedSizes = [5, 8, 10, 12, 16, 18, 22];
    var clampedSize = clampRedactPixelBlockSize(size);
    return allowedSizes.reduce(function (closest, candidate) {
      return Math.abs(candidate - clampedSize) < Math.abs(closest - clampedSize)
        ? candidate
        : closest;
    }, allowedSizes[0]);
  }

  function svgRectForElement(element) {
    if (!element) {
      return null;
    }

    return {
      x: parseFloat(element.getAttribute('x')) || 0,
      y: parseFloat(element.getAttribute('y')) || 0,
      width: parseFloat(element.getAttribute('width')) || 0,
      height: parseFloat(element.getAttribute('height')) || 0,
    };
  }

  function rectsOverlap(a, b) {
    if (!a || !b || a.width <= 0 || a.height <= 0 || b.width <= 0 || b.height <= 0) {
      return false;
    }

    return (
      a.x < b.x + b.width &&
      a.x + a.width > b.x &&
      a.y < b.y + b.height &&
      a.y + a.height > b.y
    );
  }

  function median(values) {
    if (!values.length) {
      return null;
    }

    var sortedValues = values.slice().sort(function (a, b) {
      return a - b;
    });
    var middle = Math.floor(sortedValues.length / 2);

    if (sortedValues.length % 2 === 0) {
      return (sortedValues[middle - 1] + sortedValues[middle]) / 2;
    }

    return sortedValues[middle];
  }

  function redactPixelBlockSizeForAnnotation(annotation) {
    if (!annotation || !annotation.element) {
      return 5;
    }

    var redactRect = svgRectForElement(annotation.element);
    var textHeights = Array.prototype.map
      .call(document.querySelectorAll('rect.ocr-text-rect'), function (element) {
        var textRect = svgRectForElement(element);
        return rectsOverlap(redactRect, textRect) ? textRect.height : null;
      })
      .filter(function (height) {
        return height && height > 0;
      });
    var contentHeight = median(textHeights) || (redactRect && redactRect.height) || 25;

    return closestRedactPixelBlockSize(Math.round(contentHeight / 5));
  }

  function ensurePixelatedRedactPattern(blockSize) {
    var svg = STATE.ensureRedactLayer();
    var sourceImage = document.querySelector('#target .image img');
    var normalizedBlockSize = closestRedactPixelBlockSize(blockSize || 5);
    var patternID = 'redact-pixelate-pattern-' + normalizedBlockSize;

    if (!svg || !sourceImage) {
      STATE.redactPixelPatternFailed = true;
      return false;
    }

    if (STATE.redactPixelPatterns[normalizedBlockSize]) {
      return true;
    }

    if (!sourceImage.complete || !sourceImage.naturalWidth) {
      if (!STATE.redactPixelPatternPending) {
        var generationID = STATE.captureGenerationID;
        STATE.redactPixelPatternPending = true;
        var loadHandler = function () {
          if (STATE.redactPixelPatternLoadHandler === loadHandler) {
            STATE.redactPixelPatternLoadHandler = null;
          }
          if (STATE.captureGenerationID !== generationID) {
            return;
          }
          STATE.redactPixelPatternPending = false;
          ensurePixelatedRedactPattern();
          refreshRedactAnnotationStyles();
        };
        STATE.redactPixelPatternLoadHandler = loadHandler;
        sourceImage.addEventListener('load', loadHandler, {
          once: true,
        });
      }
      return false;
    }

    try {
      var viewBox = svg.viewBox && svg.viewBox.baseVal;
      var size = getImageSize();
      var width = Math.max(1, Math.round((viewBox && viewBox.width) || size.width));
      var height = Math.max(1, Math.round((viewBox && viewBox.height) || size.height));
      var smallCanvas = document.createElement('canvas');
      var pixelCanvas = document.createElement('canvas');
      var smallContext = smallCanvas.getContext('2d');
      var pixelContext = pixelCanvas.getContext('2d');

      smallCanvas.width = Math.max(1, Math.ceil(width / normalizedBlockSize));
      smallCanvas.height = Math.max(1, Math.ceil(height / normalizedBlockSize));
      pixelCanvas.width = width;
      pixelCanvas.height = height;

      smallContext.imageSmoothingEnabled = false;
      pixelContext.imageSmoothingEnabled = false;
      smallContext.drawImage(sourceImage, 0, 0, smallCanvas.width, smallCanvas.height);
      pixelContext.drawImage(smallCanvas, 0, 0, width, height);

      var defs = svg.querySelector('defs');
      if (!defs) {
        defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
        svg.insertBefore(defs, svg.firstChild);
      }

      var pattern = svg.querySelector('#' + patternID);
      if (!pattern) {
        pattern = document.createElementNS('http://www.w3.org/2000/svg', 'pattern');
        pattern.setAttribute('id', patternID);
        defs.appendChild(pattern);
      }

      pattern.setAttribute('patternUnits', 'userSpaceOnUse');
      pattern.setAttribute('x', '0');
      pattern.setAttribute('y', '0');
      pattern.setAttribute('width', width);
      pattern.setAttribute('height', height);

      while (pattern.firstChild) {
        pattern.removeChild(pattern.firstChild);
      }

      var patternImage = document.createElementNS(
        'http://www.w3.org/2000/svg',
        'image'
      );
      var pixelatedURL = pixelCanvas.toDataURL('image/png');
      patternImage.setAttribute('x', '0');
      patternImage.setAttribute('y', '0');
      patternImage.setAttribute('width', width);
      patternImage.setAttribute('height', height);
      patternImage.setAttribute('preserveAspectRatio', 'none');
      patternImage.setAttribute('href', pixelatedURL);
      patternImage.setAttributeNS(
        'http://www.w3.org/1999/xlink',
        'href',
        pixelatedURL
      );
      pattern.appendChild(patternImage);

      STATE.redactPixelPatterns[normalizedBlockSize] = true;
      STATE.redactPixelPatternFailed = false;
      return true;
    } catch (e) {
      console.error('Failed to generate redact pixelation pattern', e);
      STATE.redactPixelPatternFailed = true;
      return false;
    }
  }

  function applyRedactAnnotationStyle(annotation) {
    if (!annotation || !annotation.element || annotation.type !== 'redact') {
      return;
    }

    var mode = normalizeRedactMode(annotation.redactMode || STATE.redactDefaults.mode);
    annotation.redactMode = mode;
    annotation.element.setAttribute('data-redact-mode', mode);

    var blockSize = redactPixelBlockSizeForAnnotation(annotation);
    if (mode === 'pixelate' && ensurePixelatedRedactPattern(blockSize)) {
      annotation.element.setAttribute(
        'fill',
        'url(#redact-pixelate-pattern-' + blockSize + ')'
      );
      annotation.element.setAttribute('fill-opacity', '1');
      return;
    }

    var color =
      mode === 'pixelate'
        ? DEFAULT_ANNOTATION_COLORS.redact
        : annotation.color || STATE.annotationDefaults.redact;
    annotation.element.setAttribute('fill', color);
    annotation.element.setAttribute('fill-opacity', '1');
  }

  function refreshRedactAnnotationStyles() {
    Object.keys(STATE.annotations).forEach(function (id) {
      var annotation = STATE.annotations[id];
      if (annotation && annotation.type === 'redact') {
        applyRedactAnnotationStyle(annotation);
      }
    });
  }

  function numberFromStyle(value, fallback) {
    var parsed = parseFloat(value);
    return Number.isFinite(parsed) ? parsed : fallback;
  }

  function shapeAngle(annotation) {
    if (!annotation || !annotation.element) {
      return 0;
    }

    if (Number.isFinite(annotation.angle)) {
      return annotation.angle;
    }

    var match = (annotation.element.style.transform || '').match(
      /rotateZ\(([-0-9.]+)rad\)/
    );
    return match ? parseFloat(match[1]) || 0 : 0;
  }

  function shapeGeometry(annotation) {
    var id = annotation && annotation.id;
    var element = annotation && annotation.element;
    return {
      x: STATE.xOffset[id] || 0,
      y: STATE.yOffset[id] || 0,
      width: numberFromStyle(element && element.style.width, annotation.width || 50),
      height: numberFromStyle(element && element.style.height, annotation.height || 50),
      angle: shapeAngle(annotation),
    };
  }

  function shapeAnnotationAtPoint(x, y) {
    var result = null;
    var resultZ = -Infinity;

    Object.keys(STATE.annotations).forEach(function (id) {
      var annotation = STATE.annotations[id];
      if (!shapeContainsPoint(annotation, x, y)) {
        return;
      }

      var zIndex = parseInt(annotation.element.style.zIndex || '0', 10);
      if (!result || zIndex >= resultZ) {
        result = annotation;
        resultZ = zIndex;
      }
    });

    return result;
  }

  function shapeContainsPoint(annotation, x, y) {
    if (!annotation || annotation.type !== 'shape' || !annotation.element) {
      return false;
    }

    var geometry = shapeGeometry(annotation);
    if (annotation.shapeType === 'line') {
      var start = { x: geometry.x, y: geometry.y };
      var end = {
        x: geometry.x + geometry.width * Math.cos(geometry.angle),
        y: geometry.y + geometry.width * Math.sin(geometry.angle),
      };
      var lineLengthSquared =
        Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2);
      if (lineLengthSquared <= 0) {
        return false;
      }

      var t =
        ((x - start.x) * (end.x - start.x) +
          (y - start.y) * (end.y - start.y)) /
        lineLengthSquared;
      t = Math.max(0, Math.min(1, t));

      var nearest = {
        x: start.x + t * (end.x - start.x),
        y: start.y + t * (end.y - start.y),
      };
      var distance = Math.sqrt(
        Math.pow(x - nearest.x, 2) + Math.pow(y - nearest.y, 2)
      );
      return distance <= 10;
    }

    return (
      x >= geometry.x &&
      x <= geometry.x + geometry.width &&
      y >= geometry.y &&
      y <= geometry.y + geometry.height
    );
  }

  function applyShapeGeometry(annotation, geometry) {
    if (!annotation || !annotation.element) {
      return;
    }

    var width = Math.max(12, geometry.width);
    var height = annotation.shapeType === 'line' ? 0 : Math.max(12, geometry.height);
    var angle = annotation.shapeType === 'line' ? geometry.angle || 0 : 0;

    annotation.width = width;
    annotation.height = height;
    annotation.angle = angle;
    annotation.element.style.width = width + 'px';
    annotation.element.style.height = height + 'px';
    STATE.xOffset[annotation.id] = geometry.x;
    STATE.yOffset[annotation.id] = geometry.y;
    annotation.element.style.transform =
      'translate3d(' +
      geometry.x +
      'px, ' +
      geometry.y +
      'px, 0)' +
      (annotation.shapeType === 'line' ? ' rotateZ(' + angle + 'rad)' : '');
  }

  function shapeSelectionPayload(annotation) {
    return {
      id: annotation.id,
      type: annotation.type,
      color: annotation.color,
      fontSize: annotation.fontSize,
      redactMode: annotation.redactMode,
      shapeType: annotation.shapeType,
      width: annotation.width,
      height: annotation.height,
      angle: annotation.angle,
      startPoint: annotation.startPoint,
      endPoint: annotation.endPoint,
      length: annotation.length,
      arrowStyle: annotation.arrowStyle,
      arrowWeight: annotation.arrowWeight,
    };
  }

  function postAnnotationSelected(annotation) {
    if (annotation && annotation.type === 'shape') {
      var geometry = shapeGeometry(annotation);
      annotation.width = geometry.width;
      annotation.height = geometry.height;
      annotation.angle = geometry.angle;
    }
    if (annotation && annotation.type === 'arrow') {
      arrowGeometry(annotation);
    }
    postEvent('ANNOTATION_SELECTED', shapeSelectionPayload(annotation));
  }

  function editableRedactAnnotation(annotation) {
    return (
      annotation &&
      annotation.type === 'redact' &&
      (annotation.provenance ||
        annotation.element.getAttribute('data-redact-provenance')) === 'user'
    );
  }

  function redactGeometry(annotation) {
    return svgRectForElement(annotation && annotation.element);
  }

  function applyRedactGeometry(annotation, geometry) {
    if (!editableRedactAnnotation(annotation) || !geometry) {
      return;
    }
    annotation.element.setAttribute('x', geometry.x);
    annotation.element.setAttribute('y', geometry.y);
    annotation.element.setAttribute('width', geometry.width);
    annotation.element.setAttribute('height', geometry.height);
  }

  function redactPointFromClientPoint(point) {
    var svg = STATE.ensureRedactLayer();
    if (!svg || !point) {
      return { x: 0, y: 0 };
    }
    if (svg.createSVGPoint && svg.getScreenCTM()) {
      var svgPoint = svg.createSVGPoint();
      svgPoint.x = point.x;
      svgPoint.y = point.y;
      return svgPoint.matrixTransform(svg.getScreenCTM().inverse());
    }
    var bounds = svg.getBoundingClientRect();
    var viewBox = svg.viewBox && svg.viewBox.baseVal;
    return {
      x:
        ((point.x - bounds.left) / (bounds.width || 1)) *
        ((viewBox && viewBox.width) || bounds.width || 1),
      y:
        ((point.y - bounds.top) / (bounds.height || 1)) *
        ((viewBox && viewBox.height) || bounds.height || 1),
    };
  }

  function redactHandleSize() {
    var svg = STATE.ensureRedactLayer();
    var bounds = svg && svg.getBoundingClientRect();
    var viewBox = svg && svg.viewBox && svg.viewBox.baseVal;
    return {
      width: Math.max(
        1,
        (10 * ((viewBox && viewBox.width) || 1)) /
          ((bounds && bounds.width) || 1)
      ),
      height: Math.max(
        1,
        (10 * ((viewBox && viewBox.height) || 1)) /
          ((bounds && bounds.height) || 1)
      ),
    };
  }

  function removeRedactResizeHandles() {
    Array.prototype.forEach.call(
      document.querySelectorAll('.redact-resize-handle'),
      function (handle) {
        handle.remove();
      }
    );
    STATE.redactInteractionState = null;
  }

  function syncRedactResizeHandles(annotation) {
    annotation =
      annotation ||
      (STATE.selectedAnnotationID && STATE.annotations[STATE.selectedAnnotationID]);
    if (!editableRedactAnnotation(annotation)) {
      removeRedactResizeHandles();
      return;
    }

    var geometry = redactGeometry(annotation);
    var handleSize = redactHandleSize();
    var centerX = geometry.x + geometry.width / 2;
    var centerY = geometry.y + geometry.height / 2;
    var positions = {
      nw: [geometry.x, geometry.y],
      n: [centerX, geometry.y],
      ne: [geometry.x + geometry.width, geometry.y],
      e: [geometry.x + geometry.width, centerY],
      se: [geometry.x + geometry.width, geometry.y + geometry.height],
      s: [centerX, geometry.y + geometry.height],
      sw: [geometry.x, geometry.y + geometry.height],
      w: [geometry.x, centerY],
    };
    Array.prototype.forEach.call(
      document.querySelectorAll(
        '.redact-resize-handle[data-redact-id="' + annotation.id + '"]'
      ),
      function (handle) {
        var position = positions[handle.getAttribute('data-handle')];
        handle.setAttribute('x', position[0] - handleSize.width / 2);
        handle.setAttribute('y', position[1] - handleSize.height / 2);
        handle.setAttribute('width', handleSize.width);
        handle.setAttribute('height', handleSize.height);
      }
    );
  }

  function createRedactResizeHandle(annotation, kind) {
    var handle = document.createElementNS(
      'http://www.w3.org/2000/svg',
      'rect'
    );
    handle.setAttribute('class', 'redact-resize-handle');
    handle.setAttribute('data-redact-id', annotation.id);
    handle.setAttribute('data-handle', kind);
    STATE.ensureRedactLayer().appendChild(handle);
    handle.addEventListener('mousedown', function (event) {
      if (STATE.mode !== 'free' || event.button !== 0) {
        return;
      }
      event.preventDefault();
      event.stopPropagation();
      STATE.redactInteractionState = {
        annotation: annotation,
        action: 'resize',
        handle: kind,
        changed: false,
        startGeometry: redactGeometry(annotation),
        startPoint: redactPointFromClientPoint({
          x: event.clientX,
          y: event.clientY,
        }),
      };
      window._PREVENT_DRAG_SCRIPT = true;
    });
  }

  function showRedactResizeHandles(annotation) {
    removeRedactResizeHandles();
    if (!editableRedactAnnotation(annotation)) {
      return;
    }
    ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'].forEach(function (kind) {
      createRedactResizeHandle(annotation, kind);
    });
    syncRedactResizeHandles(annotation);
  }

  function resizedRedactGeometry(state, point) {
    var start = state.startGeometry;
    var dx = point.x - state.startPoint.x;
    var dy = point.y - state.startPoint.y;
    var left = start.x;
    var top = start.y;
    var right = start.x + start.width;
    var bottom = start.y + start.height;
    var handle = state.handle;
    if (handle.indexOf('w') >= 0) {
      left += dx;
    }
    if (handle.indexOf('e') >= 0) {
      right += dx;
    }
    if (handle.indexOf('n') >= 0) {
      top += dy;
    }
    if (handle.indexOf('s') >= 0) {
      bottom += dy;
    }
    var minimum = redactHandleSize();
    if (right - left < minimum.width) {
      if (handle.indexOf('w') >= 0) {
        left = right - minimum.width;
      } else {
        right = left + minimum.width;
      }
    }
    if (bottom - top < minimum.height) {
      if (handle.indexOf('n') >= 0) {
        top = bottom - minimum.height;
      } else {
        bottom = top + minimum.height;
      }
    }
    return {
      x: left,
      y: top,
      width: right - left,
      height: bottom - top,
    };
  }

  function beginRedactMove(annotation, event) {
    if (!editableRedactAnnotation(annotation) || event.button !== 0) {
      return;
    }
    var point = redactPointFromClientPoint({
      x: event.clientX,
      y: event.clientY,
    });
    STATE.redactInteractionState = {
      annotation: annotation,
      action: 'move',
      changed: false,
      startGeometry: redactGeometry(annotation),
      startPoint: point,
    };
    annotation.element.focus();
    event.preventDefault();
    window._PREVENT_DRAG_SCRIPT = true;
  }

  function handleRedactInteractionMove(event) {
    var state = STATE.redactInteractionState;
    if (!state || !editableRedactAnnotation(state.annotation)) {
      return;
    }
    event.preventDefault();
    var point = redactPointFromClientPoint({
      x: event.clientX,
      y: event.clientY,
    });
    var geometry;
    if (state.action === 'move') {
      geometry = {
        x: state.startGeometry.x + point.x - state.startPoint.x,
        y: state.startGeometry.y + point.y - state.startPoint.y,
        width: state.startGeometry.width,
        height: state.startGeometry.height,
      };
    } else {
      geometry = resizedRedactGeometry(state, point);
    }
    applyRedactGeometry(state.annotation, geometry);
    state.changed = true;
    syncRedactResizeHandles(state.annotation);
  }

  function handleRedactInteractionEnd() {
    var state = STATE.redactInteractionState;
    if (!state) {
      return;
    }
    STATE.redactInteractionState = null;
    window._PREVENT_DRAG_SCRIPT = false;
    if (state.action === 'resize' && state.changed) {
      applyRedactAnnotationStyle(state.annotation);
    }
    postAnnotationSelected(state.annotation);
    if (state.changed) {
      STATE.markDocumentChanged(true);
    }
  }

  function removeShapeResizeHandles() {
    Array.prototype.forEach.call(
      document.querySelectorAll('.shape-resize-handle'),
      function (handle) {
        handle.remove();
      }
    );
    STATE.shapeResizeState = null;
  }

  function handlePositionStyle(handle, x, y) {
    handle.style.transform =
      'translate3d(' + Math.round(x - 5) + 'px, ' + Math.round(y - 5) + 'px, 0)';
  }

  function syncShapeResizeHandles(annotation) {
    annotation =
      annotation ||
      (STATE.selectedAnnotationID && STATE.annotations[STATE.selectedAnnotationID]);

    if (!annotation || annotation.type !== 'shape') {
      removeShapeResizeHandles();
      return;
    }

    var geometry = shapeGeometry(annotation);
    var handles = document.querySelectorAll(
      '.shape-resize-handle[data-shape-id="' + annotation.id + '"]'
    );

    Array.prototype.forEach.call(handles, function (handle) {
      var kind = handle.getAttribute('data-handle');

      if (annotation.shapeType === 'line') {
        var startX = geometry.x;
        var startY = geometry.y;
        var endX = geometry.x + geometry.width * Math.cos(geometry.angle);
        var endY = geometry.y + geometry.width * Math.sin(geometry.angle);
        if (kind === 'line-start') {
          handlePositionStyle(handle, startX, startY);
        } else {
          handlePositionStyle(handle, endX, endY);
        }
        return;
      }

      var left = geometry.x;
      var top = geometry.y;
      var centerX = left + geometry.width / 2;
      var centerY = top + geometry.height / 2;
      var right = left + geometry.width;
      var bottom = top + geometry.height;
      var positions = {
        nw: [left, top],
        n: [centerX, top],
        ne: [right, top],
        e: [right, centerY],
        se: [right, bottom],
        s: [centerX, bottom],
        sw: [left, bottom],
        w: [left, centerY],
      };
      handlePositionStyle(handle, positions[kind][0], positions[kind][1]);
    });
  }

  function createShapeHandle(annotation, kind) {
    var handle = document.createElement('div');
    handle.setAttribute('class', 'shape-resize-handle prevent-drag');
    handle.setAttribute('data-shape-id', annotation.id);
    handle.setAttribute('data-handle', kind);
    document.querySelector('#draw-canvas').appendChild(handle);

    handle.addEventListener('mousedown', function (e) {
      e.preventDefault();
      e.stopPropagation();
      STATE.shapeResizeState = {
        annotation: annotation,
        handle: kind,
        startGeometry: shapeGeometry(annotation),
        startMouse: { x: e.clientX, y: e.clientY },
      };
      window._PREVENT_DRAG_SCRIPT = true;
    });
  }

  function showShapeResizeHandles(annotation) {
    removeShapeResizeHandles();

    if (!annotation || annotation.type !== 'shape') {
      return;
    }

    var handles =
      annotation.shapeType === 'line'
        ? ['line-start', 'line-end']
        : ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
    handles.forEach(function (handle) {
      createShapeHandle(annotation, handle);
    });
    syncShapeResizeHandles(annotation);
  }

  function resizeBoxShape(state, e) {
    var start = state.startGeometry;
    var handle = state.handle;
    var dx = e.clientX - state.startMouse.x;
    var dy = e.clientY - state.startMouse.y;
    var left = start.x;
    var top = start.y;
    var right = start.x + start.width;
    var bottom = start.y + start.height;

    if (handle.indexOf('w') >= 0) {
      left += dx;
    }
    if (handle.indexOf('e') >= 0) {
      right += dx;
    }
    if (handle.indexOf('n') >= 0) {
      top += dy;
    }
    if (handle.indexOf('s') >= 0) {
      bottom += dy;
    }

    if (right - left < 12) {
      if (handle.indexOf('w') >= 0) {
        left = right - 12;
      } else {
        right = left + 12;
      }
    }

    if (bottom - top < 12) {
      if (handle.indexOf('n') >= 0) {
        top = bottom - 12;
      } else {
        bottom = top + 12;
      }
    }

    if (e.shiftKey) {
      var width = right - left;
      var height = bottom - top;
      var size = Math.max(12, width, height);

      if (handle.length === 2) {
        if (handle.indexOf('w') >= 0) {
          left = right - size;
        } else {
          right = left + size;
        }
        if (handle.indexOf('n') >= 0) {
          top = bottom - size;
        } else {
          bottom = top + size;
        }
      } else {
        var centerX = start.x + start.width / 2;
        var centerY = start.y + start.height / 2;
        if (handle === 'w' || handle === 'e') {
          size = Math.max(12, width);
          top = centerY - size / 2;
          bottom = centerY + size / 2;
        } else {
          size = Math.max(12, height);
          left = centerX - size / 2;
          right = centerX + size / 2;
        }
      }
    }

    return {
      x: left,
      y: top,
      width: right - left,
      height: bottom - top,
      angle: 0,
    };
  }

  function resizeLineShape(state, e) {
    var start = state.startGeometry;
    var startPoint = { x: start.x, y: start.y };
    var endPoint = {
      x: start.x + start.width * Math.cos(start.angle),
      y: start.y + start.width * Math.sin(start.angle),
    };
    var movingPoint = { x: e.clientX, y: e.clientY };
    var fixedPoint = state.handle === 'line-start' ? endPoint : startPoint;
    var angle =
      state.handle === 'line-start'
        ? Math.atan2(fixedPoint.y - movingPoint.y, fixedPoint.x - movingPoint.x)
        : Math.atan2(movingPoint.y - fixedPoint.y, movingPoint.x - fixedPoint.x);
    var length = Math.sqrt(
      Math.pow(movingPoint.x - fixedPoint.x, 2) +
        Math.pow(movingPoint.y - fixedPoint.y, 2)
    );

    length = Math.max(12, length);

    if (e.shiftKey) {
      angle = (Math.round(((angle * 180) / Math.PI) / 45) * 45 * Math.PI) / 180;
    }

    if (state.handle === 'line-start') {
      return {
        x: fixedPoint.x - length * Math.cos(angle),
        y: fixedPoint.y - length * Math.sin(angle),
        width: length,
        height: 0,
        angle: angle,
      };
    }

    return {
      x: fixedPoint.x,
      y: fixedPoint.y,
      width: length,
      height: 0,
      angle: angle,
    };
  }

  function handleShapeResizeMove(e) {
    var state = STATE.shapeResizeState;
    if (!state || !state.annotation) {
      return;
    }

    e.preventDefault();
    var geometry =
      state.annotation.shapeType === 'line'
        ? resizeLineShape(state, e)
        : resizeBoxShape(state, e);
    applyShapeGeometry(state.annotation, geometry);
    syncShapeResizeHandles(state.annotation);
  }

  function handleShapeResizeEnd() {
    if (!STATE.shapeResizeState) {
      return;
    }
    postAnnotationSelected(STATE.shapeResizeState.annotation);
    STATE.shapeResizeState = null;
    window._PREVENT_DRAG_SCRIPT = false;
  }

  function arrowGeometry(annotation) {
    if (!annotation || annotation.type !== 'arrow') {
      return null;
    }

    var startPoint = annotation.startPoint || {
      x: STATE.xOffset[annotation.id] || 0,
      y: STATE.yOffset[annotation.id] || 0,
    };
    var angle = Number.isFinite(annotation.angle) ? annotation.angle : 0;
    var length = Math.max(ARROW_MIN_LENGTH, annotation.length || 140);
    var endPoint =
      annotation.endPoint || arrowPointFromLengthAngle(startPoint, length, angle);
    var geometry = arrowGeometryFromPoints(startPoint, endPoint);

    annotation.startPoint = geometry.startPoint;
    annotation.endPoint = geometry.endPoint;
    annotation.length = geometry.length;
    annotation.angle = geometry.angle;
    annotation.arrowStyle = normalizeArrowStyle(annotation.arrowStyle);
    annotation.arrowWeight = normalizeArrowWeight(annotation.arrowWeight);

    return geometry;
  }

  function syncArrowGeometryToOffsets(annotation) {
    var geometry = arrowGeometry(annotation);
    if (!geometry) {
      return;
    }
    STATE.xOffset[annotation.id] = geometry.startPoint.x;
    STATE.yOffset[annotation.id] = geometry.startPoint.y;
  }

  STATE.moveArrowAnnotationBody = function (annotation, x, y) {
    var geometry = arrowGeometry(annotation);
    if (!geometry) {
      return;
    }
    annotation.startPoint = { x: x, y: y };
    annotation.endPoint = arrowPointFromLengthAngle(
      annotation.startPoint,
      geometry.length,
      geometry.angle
    );
    annotation.length = geometry.length;
    annotation.angle = geometry.angle;
    syncArrowResizeHandles(annotation);
  };

  function removeArrowResizeHandles() {
    Array.prototype.forEach.call(
      document.querySelectorAll('.arrow-resize-handle'),
      function (handle) {
        handle.remove();
      }
    );
    STATE.arrowResizeState = null;
  }

  function syncArrowResizeHandles(annotation) {
    annotation =
      annotation ||
      (STATE.selectedAnnotationID && STATE.annotations[STATE.selectedAnnotationID]);

    if (!annotation || annotation.type !== 'arrow') {
      removeArrowResizeHandles();
      return;
    }

    var geometry = arrowGeometry(annotation);
    var handles = document.querySelectorAll(
      '.arrow-resize-handle[data-arrow-id="' + annotation.id + '"]'
    );

    Array.prototype.forEach.call(handles, function (handle) {
      var kind = handle.getAttribute('data-handle');
      var point = kind === 'arrow-start' ? geometry.startPoint : geometry.endPoint;
      handlePositionStyle(handle, point.x, point.y);
    });
  }

  function createArrowHandle(annotation, kind) {
    var handle = document.createElement('div');
    handle.setAttribute('class', 'arrow-resize-handle prevent-drag');
    handle.setAttribute('data-arrow-id', annotation.id);
    handle.setAttribute('data-handle', kind);
    document.querySelector('#draw-canvas').appendChild(handle);

    handle.addEventListener('mousedown', function (e) {
      e.preventDefault();
      e.stopPropagation();
      STATE.arrowResizeState = {
        annotation: annotation,
        handle: kind,
        startGeometry: arrowGeometry(annotation),
      };
      window._PREVENT_DRAG_SCRIPT = true;
    });
  }

  function showArrowResizeHandles(annotation) {
    removeArrowResizeHandles();

    if (!annotation || annotation.type !== 'arrow') {
      return;
    }

    createArrowHandle(annotation, 'arrow-start');
    createArrowHandle(annotation, 'arrow-end');
    syncArrowResizeHandles(annotation);
  }

  function resizeArrow(state, e) {
    var start = state.startGeometry;
    var movingPoint = { x: e.clientX, y: e.clientY };

    if (state.handle === 'arrow-start') {
      var head = start.endPoint;
      var tailGeometry = arrowGeometryFromPoints(movingPoint, head, {
        shiftKey: e.shiftKey,
      });
      return {
        startPoint: {
          x: head.x - tailGeometry.length * Math.cos(tailGeometry.angle),
          y: head.y - tailGeometry.length * Math.sin(tailGeometry.angle),
        },
        endPoint: head,
        length: tailGeometry.length,
        angle: tailGeometry.angle,
      };
    }

    return arrowGeometryFromPoints(start.startPoint, movingPoint, {
      shiftKey: e.shiftKey,
    });
  }

  function handleArrowResizeMove(e) {
    var state = STATE.arrowResizeState;
    if (!state || !state.annotation) {
      return;
    }

    e.preventDefault();
    var geometry = resizeArrow(state, e);
    applyArrowGeometry(state.annotation, geometry);
    syncArrowGeometryToOffsets(state.annotation);
    syncArrowResizeHandles(state.annotation);
  }

  function handleArrowResizeEnd() {
    if (!STATE.arrowResizeState) {
      return;
    }
    postAnnotationSelected(STATE.arrowResizeState.annotation);
    STATE.arrowResizeState = null;
    window._PREVENT_DRAG_SCRIPT = false;
  }

  STATE.syncSelectedShapeResizeHandles = syncShapeResizeHandles;
  STATE.syncSelectedArrowResizeHandles = syncArrowResizeHandles;
  document.addEventListener('mousemove', handleShapeResizeMove);
  document.addEventListener('mouseup', handleShapeResizeEnd);
  document.addEventListener('mousemove', handleArrowResizeMove);
  document.addEventListener('mouseup', handleArrowResizeEnd);
  document.addEventListener('mousemove', handleRedactInteractionMove);
  document.addEventListener('mouseup', handleRedactInteractionEnd);

  function setSelectedAnnotation(id) {
    if (STATE.selectedAnnotationID === id) {
      if (id && STATE.annotations[id] && STATE.annotations[id].type === 'shape') {
        showShapeResizeHandles(STATE.annotations[id]);
      }
      if (id && STATE.annotations[id] && STATE.annotations[id].type === 'arrow') {
        showArrowResizeHandles(STATE.annotations[id]);
      }
      if (id && editableRedactAnnotation(STATE.annotations[id])) {
        syncRedactResizeHandles(STATE.annotations[id]);
      }
      return;
    }

    if (STATE.selectedAnnotationID && STATE.annotations[STATE.selectedAnnotationID]) {
      STATE.annotations[STATE.selectedAnnotationID].element.classList.remove(
        'annotation-selected'
      );
      removeShapeResizeHandles();
      removeArrowResizeHandles();
      removeRedactResizeHandles();
    }

    STATE.selectedAnnotationID = id;

    if (!id || !STATE.annotations[id]) {
      removeShapeResizeHandles();
      removeArrowResizeHandles();
      removeRedactResizeHandles();
      postEvent('ANNOTATION_DESELECTED', {});
      return;
    }

    var annotation = STATE.annotations[id];
    annotation.element.classList.add('annotation-selected');
    if (annotation.type === 'shape') {
      showShapeResizeHandles(annotation);
    }
    if (annotation.type === 'arrow') {
      showArrowResizeHandles(annotation);
    }
    if (editableRedactAnnotation(annotation)) {
      showRedactResizeHandles(annotation);
    }
    postAnnotationSelected(annotation);
  }

  STATE.selectAnnotation = setSelectedAnnotation;

  function annotationIDForElement(element) {
    return element && element.getAttribute('data-annotation-id');
  }

  function selectAnnotationText(element) {
    setTimeout(function () {
      if (!element || !element.isConnected) {
        return;
      }
      var selection = window.getSelection();
      var range = document.createRange();
      range.selectNodeContents(element);
      selection.removeAllRanges();
      selection.addRange(range);
    }, 0);
  }

  function installAnnotationInteractions(annotation) {
    var element = annotation && annotation.element;
    if (!element || element._xnapperInteractionInstalled) {
      return;
    }
    element._xnapperInteractionInstalled = true;

    element.addEventListener('mousedown', function (event) {
      if (
        STATE.mode !== 'free' ||
        element.classList.contains('preview')
      ) {
        return;
      }
      if (STATE.maxZ !== parseInt(element.style.zIndex || '0', 10)) {
        STATE.maxZ += 1;
        element.style.zIndex = STATE.maxZ;
      }
      if (
        annotation.type === 'text' &&
        event.detail > 1 &&
        !element.getAttribute('contenteditable')
      ) {
        event.preventDefault();
        element.setAttribute('contenteditable', 'plaintext-only');
        selectAnnotationText(element);
      }
      if (editableRedactAnnotation(annotation)) {
        beginRedactMove(annotation, event);
      }
    });

    element.addEventListener('keydown', function (event) {
      if (element.classList.contains('preview')) {
        return;
      }
      if (event.keyCode === 27) {
        STATE.skipNextCancelOperation = true;
        element.blur();
        return;
      }
      if (
        annotation.type === 'text' &&
        event.keyCode === 13 &&
        (event.metaKey || event.ctrlKey)
      ) {
        event.preventDefault();
        element.blur();
        return;
      }
      if (
        annotation.type === 'text' &&
        event.keyCode === 13 &&
        element === document.activeElement &&
        !element.getAttribute('contenteditable')
      ) {
        event.preventDefault();
        element.setAttribute('contenteditable', 'plaintext-only');
        selectAnnotationText(element);
        return;
      }
      if (
        (event.keyCode === 8 || event.keyCode === 46) &&
        element === document.activeElement &&
        !element.getAttribute('contenteditable')
      ) {
        event.preventDefault();
        STATE.removeAnnotation(annotation.id);
      }
    });

    if (annotation.type === 'text') {
      element.addEventListener('blur', function () {
        if (element.classList.contains('preview')) {
          return;
        }
        var wasEditing = !!element.getAttribute('contenteditable');
        element.removeAttribute('contenteditable');
        if ((element.innerText || '').trim() === '') {
          STATE.removeAnnotation(annotation.id);
        } else if (wasEditing) {
          STATE.markDocumentChanged(true);
        }
        if (window.getSelection) {
          window.getSelection().removeAllRanges();
        }
      });
    }
    if (
      annotation.type === 'redact' &&
      !element.classList.contains('text-rects')
    ) {
      element.addEventListener('click', function () {
        if (STATE.mode === 'redact') {
          STATE.removeAnnotation(annotation.id);
        }
      });
    }
  }

  STATE.registerAnnotation = function (annotation) {
    STATE.annotations[annotation.id] = annotation;
    annotation.element.setAttribute('data-annotation-id', annotation.id);
    annotation.element.setAttribute('data-annotation-type', annotation.type);
    if (annotation.type === 'arrow') {
      annotation.arrowStyle = normalizeArrowStyle(
        annotation.arrowStyle || STATE.arrowDefaults.style
      );
      annotation.arrowWeight = normalizeArrowWeight(
        annotation.arrowWeight || STATE.arrowDefaults.weight
      );
      if (!annotation.startPoint || !annotation.endPoint) {
        var startPoint = {
          x: STATE.xOffset[annotation.id] || 0,
          y: STATE.yOffset[annotation.id] || 0,
        };
        applyArrowGeometry(
          annotation,
          arrowGeometryFromPoints(
            startPoint,
            arrowPointFromLengthAngle(
              startPoint,
              annotation.length || 140,
              annotation.angle || 0
            )
          )
        );
      }
    }
    applyAnnotationColor(annotation);
    applyTextAnnotationSize(annotation);
    applyRedactAnnotationStyle(annotation);

    annotation.element.addEventListener('mousedown', function (e) {
      if (STATE.mode !== 'free') {
        return;
      }
      setSelectedAnnotation(annotation.id);
    });
    installAnnotationInteractions(annotation);
  };

  STATE.createTextAnnotation = function (options) {
    options = options || {};
    var element = document.createElement('div');
    element.setAttribute(
      'class',
      'draggable draw-item-text' + (options.preview ? ' preview' : '')
    );
    element.setAttribute('data-item-id', options.id);
    element.setAttribute('spellcheck', 'false');
    element.setAttribute('auto-complete', 'off');
    element.setAttribute('role', 'textbox');
    element.setAttribute('tabindex', '0');
    if (options.preview) {
      element.setAttribute('contenteditable', 'plaintext-only');
    }
    if (options.size) {
      element.classList.add('text-' + options.size);
    }
    element.innerText = options.text || '';
    element.style.zIndex = finiteNumber(options.zIndex, STATE.maxZ);
    var annotation = {
      id: String(options.id),
      type: 'text',
      color: options.color,
      fontSize: finiteNumber(
        options.fontSize,
        STATE.textAnnotationDefaults.size
      ),
      element: element,
    };
    STATE.registerAnnotation(annotation);
    return annotation;
  };

  STATE.createShapeAnnotation = function (options) {
    options = options || {};
    var element = document.createElement('div');
    element.setAttribute(
      'class',
      (options.preview ? '' : 'draggable ') +
        'draw-item-shape' +
        (options.preview ? ' preview' : '') +
        ' shape-' +
        options.shapeType
    );
    element.setAttribute('data-item-id', options.id);
    element.setAttribute('tabindex', '0');
    element.style.zIndex = finiteNumber(options.zIndex, STATE.maxZ);
    var annotation = {
      id: String(options.id),
      type: 'shape',
      color: options.color,
      shapeType: options.shapeType,
      width: finiteNumber(options.width, 50),
      height: finiteNumber(options.height, 50),
      angle: finiteNumber(options.angle, 0),
      element: element,
    };
    STATE.registerAnnotation(annotation);
    return annotation;
  };

  STATE.createArrowAnnotation = function (options) {
    options = options || {};
    var element = document.createElement('div');
    element.setAttribute(
      'class',
      (options.preview ? '' : 'draggable ') +
        'draw-item-arrow' +
        (options.preview ? ' preview' : '')
    );
    element.setAttribute('data-item-id', options.id);
    element.setAttribute('tabindex', '0');
    element.style.zIndex = finiteNumber(options.zIndex, STATE.maxZ);
    var annotation = {
      id: String(options.id),
      type: 'arrow',
      color: options.color,
      element: element,
      arrowStyle: options.arrowStyle,
      arrowWeight: options.arrowWeight,
    };
    if (options.startPoint && options.endPoint) {
      applyArrowGeometry(
        annotation,
        arrowGeometryFromPoints(options.startPoint, options.endPoint)
      );
    }
    STATE.registerAnnotation(annotation);
    return annotation;
  };

  STATE.createRedactAnnotation = function (options) {
    options = options || {};
    var element = document.createElementNS(
      'http://www.w3.org/2000/svg',
      'rect'
    );
    element.setAttribute(
      'class',
      (options.textRect ? 'text-rects ' : '') + 'redacted'
    );
    element.setAttribute('data-redact-provenance', 'user');
    element.setAttribute('tabindex', '0');
    element.setAttribute('x', finiteNumber(options.x, 0));
    element.setAttribute('y', finiteNumber(options.y, 0));
    element.setAttribute('width', finiteNumber(options.width, 0));
    element.setAttribute('height', finiteNumber(options.height, 0));
    var annotation = {
      id: String(options.id),
      type: 'redact',
      color: options.color,
      redactMode: options.redactMode,
      provenance: 'user',
      element: element,
    };
    STATE.ensureRedactLayer().appendChild(element);
    STATE.registerAnnotation(annotation);
    return annotation;
  };

  STATE.unregisterAnnotation = function (id) {
    if (!id) {
      return;
    }

    var annotation = STATE.annotations[id];
    if (annotation && annotation.element) {
      annotation.element.classList.remove('annotation-selected');
      annotation.element.removeAttribute('data-annotation-id');
      annotation.element.removeAttribute('data-annotation-type');
      annotation.element.removeAttribute('data-annotation-color');
      if (annotation.type === 'shape') {
        removeShapeResizeHandles();
      }
      if (annotation.type === 'arrow') {
        removeArrowResizeHandles();
      }
      if (
        annotation.type === 'redact' &&
        (STATE.selectedAnnotationID === id ||
          (STATE.redactInteractionState &&
            STATE.redactInteractionState.annotation.id === id))
      ) {
        removeRedactResizeHandles();
      }
    }

    delete STATE.annotations[id];

    if (STATE.selectedAnnotationID === id) {
      STATE.selectedAnnotationID = null;
      postEvent('ANNOTATION_DESELECTED', {});
    }
  };

  STATE.removeAnnotation = function (id) {
    var annotation = STATE.annotations[id];
    STATE.unregisterAnnotation(id);
    if (annotation && annotation.element && annotation.element.parentNode) {
      annotation.element.parentNode.removeChild(annotation.element);
      STATE.markDocumentChanged(true);
    }
  };

  STATE.clearSelectedAnnotation = function () {
    setSelectedAnnotation(null);
  };

  STATE.updateAnnotationColor = function (id, color, options) {
    var annotation = STATE.annotations[id];
    if (!annotation) {
      return;
    }
    annotation.color = color;
    applyAnnotationColor(annotation);
    if (options && options.silent) {
      return;
    }
    postAnnotationSelected(annotation);
  };

  STATE.updateTextAnnotationSize = function (id, fontSize, options) {
    var annotation = STATE.annotations[id];
    if (!annotation || annotation.type !== 'text') {
      return;
    }

    annotation.fontSize = Math.max(8, Math.min(256, parseInt(fontSize, 10) || 32));
    applyTextAnnotationSize(annotation);

    if (options && options.silent) {
      return;
    }

    postAnnotationSelected(annotation);
  };

  STATE.setTextAnnotationDefaultSize = function (fontSize, options) {
    STATE.textAnnotationDefaults.size = Math.max(
      8,
      Math.min(256, parseInt(fontSize, 10) || 32)
    );

    Object.keys(STATE.annotations).forEach(function (id) {
      var annotation = STATE.annotations[id];
      if (
        annotation &&
        annotation.type === 'text' &&
        annotation.element &&
        annotation.element.classList.contains('preview')
      ) {
        STATE.updateTextAnnotationSize(id, STATE.textAnnotationDefaults.size, {
          silent: true,
        });
      }
    });

    if (options && options.silent) {
      return;
    }

    postEvent('TEXT_ANNOTATION_DEFAULT_SIZE_CHANGED', {
      fontSize: STATE.textAnnotationDefaults.size,
    });
  };

  STATE.updateRedactAnnotationMode = function (id, mode, options) {
    var annotation = STATE.annotations[id];
    if (!annotation || annotation.type !== 'redact') {
      return;
    }

    annotation.redactMode = normalizeRedactMode(mode);
    applyRedactAnnotationStyle(annotation);

    if (options && options.silent) {
      return;
    }

    postAnnotationSelected(annotation);
  };

  STATE.setRedactDefaultMode = function (mode, options) {
    STATE.redactDefaults.mode = normalizeRedactMode(mode);

    Object.keys(STATE.annotations).forEach(function (id) {
      var annotation = STATE.annotations[id];
      if (
        annotation &&
        annotation.type === 'redact' &&
        annotation.element &&
        annotation.element.classList.contains('preview')
      ) {
        STATE.updateRedactAnnotationMode(id, STATE.redactDefaults.mode, {
          silent: true,
        });
      }
    });

    if (options && options.silent) {
      return;
    }

    postEvent('REDACT_DEFAULT_MODE_CHANGED', {
      redactMode: STATE.redactDefaults.mode,
    });
  };

  STATE.updateArrowAnnotationStyle = function (id, style, options) {
    var annotation = STATE.annotations[id];
    if (!annotation || annotation.type !== 'arrow') {
      return;
    }

    annotation.arrowStyle = normalizeArrowStyle(style);
    refreshArrowRendering(annotation);
    syncArrowResizeHandles(annotation);

    if (options && options.silent) {
      return;
    }

    postAnnotationSelected(annotation);
  };

  STATE.updateArrowAnnotationWeight = function (id, weight, options) {
    var annotation = STATE.annotations[id];
    if (!annotation || annotation.type !== 'arrow') {
      return;
    }

    annotation.arrowWeight = normalizeArrowWeight(weight);
    refreshArrowRendering(annotation);
    syncArrowResizeHandles(annotation);

    if (options && options.silent) {
      return;
    }

    postAnnotationSelected(annotation);
  };

  STATE.setArrowDefaultStyle = function (style, options) {
    STATE.arrowDefaults.style = normalizeArrowStyle(style);
    STATE.currentArrowStyle = STATE.arrowDefaults.style;
    STATE.currentArrowSize = STATE.arrowDefaults.style;

    Object.keys(STATE.annotations).forEach(function (id) {
      var annotation = STATE.annotations[id];
      if (
        annotation &&
        annotation.type === 'arrow' &&
        annotation.element &&
        annotation.element.classList.contains('preview')
      ) {
        STATE.updateArrowAnnotationStyle(id, STATE.arrowDefaults.style, {
          silent: true,
        });
      }
    });

    if (options && options.silent) {
      return;
    }

    postEvent('ARROW_DEFAULT_STYLE_CHANGED', {
      arrowStyle: STATE.arrowDefaults.style,
    });
  };

  STATE.setArrowDefaultWeight = function (weight, options) {
    STATE.arrowDefaults.weight = normalizeArrowWeight(weight);

    Object.keys(STATE.annotations).forEach(function (id) {
      var annotation = STATE.annotations[id];
      if (
        annotation &&
        annotation.type === 'arrow' &&
        annotation.element &&
        annotation.element.classList.contains('preview')
      ) {
        STATE.updateArrowAnnotationWeight(id, STATE.arrowDefaults.weight, {
          silent: true,
        });
      }
    });

    if (options && options.silent) {
      return;
    }

    postEvent('ARROW_DEFAULT_WEIGHT_CHANGED', {
      arrowWeight: STATE.arrowDefaults.weight,
    });
  };

  STATE.setAnnotationDefaultColor = function (type, color) {
    STATE.annotationDefaults[type] = color;

    Object.keys(STATE.annotations).forEach(function (id) {
      var annotation = STATE.annotations[id];
      if (
        annotation &&
        annotation.type === type &&
        annotation.element &&
        annotation.element.classList.contains('preview')
      ) {
        STATE.updateAnnotationColor(id, color, { silent: true });
      }
    });
  };

  STATE.updateAnnotationColorsByType = function (type, color) {
    STATE.setAnnotationDefaultColor(type, color);

    Object.keys(STATE.annotations).forEach(function (id) {
      var annotation = STATE.annotations[id];
      if (annotation && annotation.type === type) {
        STATE.updateAnnotationColor(id, color, { silent: true });
        if (type === 'redact') {
          applyRedactAnnotationStyle(annotation);
        }
      }
    });
  };

  STATE.setTextRectRedacted = function (element, redacted, options) {
    if (!element) {
      return;
    }

    var id = annotationIDForElement(element);

    if (redacted) {
      var provenance = (options && options.provenance) || 'user';
      element.setAttribute('data-redact-provenance', provenance);
      if (provenance === 'user') {
        element.classList.remove('ocr-text-rect');
      }
      if (!id || !STATE.annotations[id]) {
        id = 'redact-' + Math.random();
        STATE.registerAnnotation({
          id: id,
          type: 'redact',
          color:
            STATE.annotationDefaults.redact || DEFAULT_ANNOTATION_COLORS.redact,
          redactMode: STATE.redactDefaults.mode,
          provenance: provenance,
          element: element,
        });
      } else {
        STATE.annotations[id].provenance = provenance;
      }
      element.classList.add('redacted');
      STATE.updateRedactAnnotationMode(id, STATE.redactDefaults.mode, {
        silent: true,
      });
      STATE.updateAnnotationColor(id, STATE.annotationDefaults.redact, {
        silent: true,
      });
      if (!(options && options.silent)) {
        STATE.markDocumentChanged(true);
      }
      return;
    }

    var previousProvenance =
      element.getAttribute('data-redact-provenance') || 'derived';
    if (previousProvenance === 'user') {
      if (id && STATE.annotations[id]) {
        STATE.removeAnnotation(id);
      } else {
        element.remove();
        STATE.markDocumentChanged(true);
      }
      return;
    }

    element.classList.remove('redacted');
    element.setAttribute('data-redact-provenance', 'derived');
    element.classList.add('ocr-text-rect');
    element.removeAttribute('data-redact-mode');
    element.setAttribute('fill', 'black');
    element.setAttribute('fill-opacity', '0');
    if (id) {
      STATE.unregisterAnnotation(id);
    }
    if (!(options && options.silent)) {
      STATE.markDocumentChanged(true);
    }
  };

  STATE.hydrateTextRects = function () {
    Array.prototype.forEach.call(
      document.querySelectorAll('rect.text-rects'),
      function (element) {
        var provenance =
          element.getAttribute('data-redact-provenance') || 'derived';
        if (
          provenance !== 'user' &&
          element.classList.contains('redacted') &&
          !annotationIDForElement(element)
        ) {
          STATE.setTextRectRedacted(element, true, {
            provenance: provenance,
            silent: true,
          });
        }
      }
    );
  };

  function handleTextRectClick(event) {
    if (STATE.mode !== 'redact') {
      return;
    }
    var element =
      event.target &&
      event.target.closest &&
      event.target.closest('rect.text-rects');
    if (!element) {
      return;
    }
    event.preventDefault();
    event.stopPropagation();
    if (element._skipRedactClick) {
      element._skipRedactClick = false;
      return;
    }
    var shouldRedact = !element.classList.contains('redacted');
    STATE.setTextRectRedacted(element, shouldRedact);
    if (shouldRedact && element.isConnected) {
      STATE.selectAnnotation(annotationIDForElement(element));
    }
  }

  function exportedAnnotation(annotation, canvasRect) {
    if (
      !annotation ||
      !annotation.element ||
      annotation.element.classList.contains('preview')
    ) {
      return null;
    }
    if (
      annotation.type === 'redact' &&
      (annotation.provenance ||
        annotation.element.getAttribute('data-redact-provenance')) !== 'user'
    ) {
      return null;
    }

    var id = String(annotation.id);
    var result = {
      id: id,
      type: annotation.type,
      color: annotation.color || STATE.annotationDefaults[annotation.type],
      zIndex: parseInt(annotation.element.style.zIndex || '0', 10) || 0,
    };
    if (annotation.type === 'arrow') {
      var geometry = arrowGeometry(annotation);
      result.startX = geometry.startPoint.x - canvasRect.x;
      result.startY = geometry.startPoint.y - canvasRect.y;
      result.endX = geometry.endPoint.x - canvasRect.x;
      result.endY = geometry.endPoint.y - canvasRect.y;
      result.arrowStyle = annotation.arrowStyle;
      result.arrowWeight = annotation.arrowWeight;
      return result;
    }
    if (annotation.type === 'text') {
      result.x = finiteNumber(STATE.xOffset[id], 0) - canvasRect.x;
      result.y = finiteNumber(STATE.yOffset[id], 0) - canvasRect.y;
      result.text = annotation.element.innerText || '';
      result.fontSize = finiteNumber(annotation.fontSize, 32);
      return result;
    }
    if (annotation.type === 'shape') {
      var shape = shapeGeometry(annotation);
      result.x = shape.x - canvasRect.x;
      result.y = shape.y - canvasRect.y;
      result.width = shape.width;
      result.height = shape.height;
      result.angle = shape.angle;
      result.shapeType = annotation.shapeType;
      return result;
    }
    if (annotation.type === 'redact') {
      var rect = svgRectForElement(annotation.element);
      result.x = rect.x;
      result.y = rect.y;
      result.width = rect.width;
      result.height = rect.height;
      result.redactMode = annotation.redactMode;
      return result;
    }
    return null;
  }

  STATE.exportDocumentState = function () {
    var canvas = document.getElementById('draw-canvas');
    var image = document.querySelector('#target .image img');
    var canvasRect = rectPayload(canvas && canvas.getBoundingClientRect());
    var imageRect = rectPayload(image && image.getBoundingClientRect());
    var annotations = Object.keys(STATE.annotations)
      .map(function (id) {
        return exportedAnnotation(STATE.annotations[id], canvasRect);
      })
      .filter(Boolean)
      .sort(function (a, b) {
        return a.zIndex - b.zIndex;
      });
    return {
      version: 1,
      documentSerial: STATE.documentSerial,
      canvasRect: canvasRect,
      imageRect: imageRect,
      imageSize: {
        width: finiteNumber(STATE.imageSize.width, 0),
        height: finiteNumber(STATE.imageSize.height, 0),
      },
      annotations: annotations,
    };
  };

  STATE.publishDocumentSnapshot = function () {
    if (STATE.suppressDocumentChanges || !STATE.captureGenerationID) {
      return;
    }
    postEvent('documentStateSnapshot', {
      generationID: STATE.captureGenerationID,
      serial: STATE.documentSerial,
      state: STATE.exportDocumentState(),
    });
  };

  function validDocumentState(state) {
    if (!state || state.version !== 1 || !Array.isArray(state.annotations)) {
      return false;
    }
    if (state.annotations.length > 1000 || JSON.stringify(state).length > 5 * 1024 * 1024) {
      return false;
    }
    var ids = {};
    return state.annotations.every(function (annotation) {
      if (
        !annotation ||
        typeof annotation.id !== 'string' ||
        annotation.id.length === 0 ||
        ids[annotation.id] ||
        ['arrow', 'text', 'shape', 'redact'].indexOf(annotation.type) < 0
      ) {
        return false;
      }
      ids[annotation.id] = true;
      if (annotation.type === 'text') {
        return (
          typeof annotation.text === 'string' &&
          new Blob([annotation.text]).size <= 64 * 1024
        );
      }
      return true;
    });
  }

  function removeEditableAnnotations() {
    setSelectedAnnotation(null);
    Object.keys(STATE.annotations).forEach(function (id) {
      var annotation = STATE.annotations[id];
      var provenance =
        annotation &&
        annotation.element &&
        annotation.element.getAttribute('data-redact-provenance');
      if (annotation && annotation.type === 'redact' && provenance !== 'user') {
        return;
      }
      STATE.removeAnnotation(id);
    });
    document
      .querySelectorAll('.shape-resize-handle, .arrow-resize-handle')
      .forEach(function (element) {
        element.remove();
      });
    STATE.xOffset = {};
    STATE.yOffset = {};
    STATE.maxZ = 1;
  }

  function canvasMapping(savedRect) {
    var canvas = document.getElementById('draw-canvas');
    var current = rectPayload(canvas && canvas.getBoundingClientRect());
    var saved = rectPayload(savedRect);
    return {
      x: current.x,
      y: current.y,
      scaleX: saved.width > 0 ? current.width / saved.width : 1,
      scaleY: saved.height > 0 ? current.height / saved.height : 1,
    };
  }

  function restoreAnnotation(record, mapping) {
    var element;
    var annotation;
    var id = record.id;
    var zIndex = finiteNumber(record.zIndex, 1);
    if (record.type === 'text') {
      annotation = STATE.createTextAnnotation({
        id: id,
        color: record.color,
        fontSize: finiteNumber(record.fontSize, 32),
        text: record.text,
        zIndex: zIndex,
      });
      element = annotation.element;
      var textX = mapping.x + finiteNumber(record.x, 0) * mapping.scaleX;
      var textY = mapping.y + finiteNumber(record.y, 0) * mapping.scaleY;
      STATE.xOffset[id] = textX;
      STATE.yOffset[id] = textY;
      element.style.transform =
        'translate3d(' + textX + 'px, ' + textY + 'px, 0)';
      document.getElementById('draw-canvas').appendChild(element);
    } else if (record.type === 'shape') {
      annotation = STATE.createShapeAnnotation({
        id: id,
        color: record.color,
        shapeType: record.shapeType,
        width: finiteNumber(record.width, 50) * mapping.scaleX,
        height: finiteNumber(record.height, 50) * mapping.scaleY,
        angle: finiteNumber(record.angle, 0),
        zIndex: zIndex,
      });
      element = annotation.element;
      applyShapeGeometry(annotation, {
        x: mapping.x + finiteNumber(record.x, 0) * mapping.scaleX,
        y: mapping.y + finiteNumber(record.y, 0) * mapping.scaleY,
        width: finiteNumber(record.width, 50) * mapping.scaleX,
        height: finiteNumber(record.height, 50) * mapping.scaleY,
        angle: finiteNumber(record.angle, 0),
      });
      document.getElementById('draw-canvas').appendChild(element);
    } else if (record.type === 'arrow') {
      annotation = STATE.createArrowAnnotation({
        id: id,
        color: record.color,
        arrowStyle: record.arrowStyle,
        arrowWeight: record.arrowWeight,
        startPoint: {
          x: mapping.x + finiteNumber(record.startX, 0) * mapping.scaleX,
          y: mapping.y + finiteNumber(record.startY, 0) * mapping.scaleY,
        },
        endPoint: {
          x: mapping.x + finiteNumber(record.endX, 0) * mapping.scaleX,
          y: mapping.y + finiteNumber(record.endY, 0) * mapping.scaleY,
        },
        zIndex: zIndex,
      });
      element = annotation.element;
      STATE.xOffset[id] = annotation.startPoint.x;
      STATE.yOffset[id] = annotation.startPoint.y;
      document.getElementById('draw-canvas').appendChild(element);
    } else {
      annotation = STATE.createRedactAnnotation({
        id: id,
        color: record.color,
        redactMode: record.redactMode,
        textRect: true,
        x: finiteNumber(record.x, 0),
        y: finiteNumber(record.y, 0),
        width: finiteNumber(record.width, 0),
        height: finiteNumber(record.height, 0),
      });
      element = annotation.element;
    }
    STATE.maxZ = Math.max(STATE.maxZ, zIndex);
  }

  STATE.restoreDocumentState = function (state) {
    if (!validDocumentState(state)) {
      throw new Error('Unsupported or invalid editor document state');
    }
    STATE.suppressDocumentChanges = true;
    try {
      removeEditableAnnotations();
      var mapping = canvasMapping(state.canvasRect);
      state.annotations.forEach(function (record) {
        restoreAnnotation(record, mapping);
      });
      STATE.documentSerial = finiteNumber(state.documentSerial, 0);
      setSelectedAnnotation(null);
      return STATE.documentSerial;
    } catch (error) {
      removeEditableAnnotations();
      STATE.documentSerial = 0;
      throw error;
    } finally {
      STATE.suppressDocumentChanges = false;
    }
  };

  function bindUserEvents() {
    document.addEventListener('keydown', function (e) {
      if (document.activeElement.getAttribute('contenteditable')) {
        return;
      }

      activateCommand({
        addTextActivated: e.keyCode === 49, // key 1
        addArrowActivated: e.keyCode === 50, // key 2
        redactActivated: e.keyCode === 51, // key 3
        addShapeActivated: e.keyCode === 52, // key 4
      });
    });
  }

  function activateCommand(options) {
    var addTextActivated = options.addTextActivated;
    var addArrowActivated = options.addArrowActivated;
    var redactActivated = options.redactActivated;
    var addShapeActivated = options.addShapeActivated;
    var didCycleArrowStyle = false;

    if (
      addTextActivated ||
      addArrowActivated ||
      redactActivated ||
      addShapeActivated
    ) {
      if (STATE.currentController && STATE.mode !== 'free') {
        if (addTextActivated && STATE.mode === 'add-text') {
          STATE.currentTextSize =
            TEXT_SIZES[
              (TEXT_SIZES.indexOf(STATE.currentTextSize) + 1) %
                TEXT_SIZES.length
            ];
          STATE.setTextAnnotationDefaultSize(
            TEXT_SIZE_PRESETS[STATE.currentTextSize] || 32
          );
        }
        if (addArrowActivated && STATE.mode === 'add-arrow') {
          STATE.currentArrowStyle =
            ARROW_STYLES[
              (ARROW_STYLES.indexOf(STATE.currentArrowStyle) + 1) %
                ARROW_STYLES.length
            ];
          didCycleArrowStyle = true;
          STATE.setArrowDefaultStyle(STATE.currentArrowStyle);
        }

        if (addShapeActivated && STATE.mode === 'add-shape') {
          STATE.currentShapeType =
            SHAPE_TYPES[
              (SHAPE_TYPES.indexOf(STATE.currentShapeType) + 1) %
                SHAPE_TYPES.length
            ];
        }

        STATE.currentController.cancel();
      }
    }

    // blur any active focused element
    if (document.activeElement) {
      document.activeElement.blur();
    }

    if (
      addTextActivated ||
      addArrowActivated ||
      redactActivated ||
      addShapeActivated
    ) {
      STATE.clearSelectedAnnotation();
    }

    if (addTextActivated) {
      STATE.currentController = startAddText(STATE);
    }

    if (addArrowActivated) {
      if (!didCycleArrowStyle && STATE.mode === 'free') {
        STATE.setArrowDefaultStyle('normal');
      }
      STATE.currentController = startAddArrow(STATE);
    }

    if (redactActivated) {
      STATE.currentController = startRedactMode(STATE);
    }

    if (addShapeActivated) {
      STATE.currentController = startAddShape(STATE);
    }
  }

  function bindGlobalEvents() {
    function onMouseUpdate(e) {
      STATE.mouse.clientX = e.clientX;
      STATE.mouse.clientY = e.clientY;
    }

    document.addEventListener('mousemove', onMouseUpdate, false);
    document.addEventListener(
      'mouseup',
      function () {
        if (!STATE.suppressDocumentChanges) {
          publishCommittedDocumentSnapshot();
        }
      },
      false
    );
    document.addEventListener(
      'mousedown',
      function (e) {
        if (STATE.mode !== 'free') {
          return;
        }

        var selectedElement = anyParentContainsClass(
          e.target,
          'draggable'
        );
        var selectedRedact =
          e.target.tagName &&
          e.target.tagName.toUpperCase() === 'RECT' &&
          e.target.classList.contains('redacted');

        if (!selectedElement && !selectedRedact) {
          var selectedShape = shapeAnnotationAtPoint(e.clientX, e.clientY);
          if (selectedShape) {
            setSelectedAnnotation(selectedShape.id);
            return;
          }

          setSelectedAnnotation(null);
        }
      },
      false
    );
    document.addEventListener('click', handleTextRectClick, false);
  }

  STATE.redactDefaults.mode = normalizeRedactMode(STATE.redactDefaults.mode);
  STATE.arrowDefaults.style = normalizeArrowStyle(STATE.arrowDefaults.style);
  STATE.arrowDefaults.weight = normalizeArrowWeight(STATE.arrowDefaults.weight);
  STATE.currentArrowStyle = STATE.arrowDefaults.style;
  STATE.currentArrowSize = STATE.arrowDefaults.style;
  setUpDrag(document.body);
  STATE.hydrateTextRects();
  bindGlobalEvents();
  console.log('draw lib init');

  var isLocalEnv =
    window.location.pathname.split('/').slice(-1)[0] === 'markup.html';
  if (isLocalEnv) {
    bindUserEvents();
  }

  window.ACTIVATE_COMMAND = activateCommand;
  window.UPDATE_ANNOTATION_COLOR = function (id, color) {
    STATE.updateAnnotationColor(id, color);
  };
  window.SET_ANNOTATION_DEFAULT_COLOR = function (type, color) {
    STATE.setAnnotationDefaultColor(type, color);
  };
  window.UPDATE_TEXT_ANNOTATION_SIZE = function (id, fontSize) {
    STATE.updateTextAnnotationSize(id, fontSize);
  };
  window.SET_TEXT_ANNOTATION_DEFAULT_SIZE = function (fontSize) {
    STATE.setTextAnnotationDefaultSize(fontSize, { silent: true });
  };
  window.UPDATE_REDACT_ANNOTATION_MODE = function (id, mode) {
    STATE.updateRedactAnnotationMode(id, mode);
  };
  window.SET_REDACT_DEFAULT_MODE = function (mode) {
    STATE.setRedactDefaultMode(mode, { silent: true });
  };
  window.UPDATE_ARROW_ANNOTATION_STYLE = function (id, style) {
    STATE.updateArrowAnnotationStyle(id, style);
  };
  window.UPDATE_ARROW_ANNOTATION_WEIGHT = function (id, weight) {
    STATE.updateArrowAnnotationWeight(id, weight);
  };
  window.SET_ARROW_DEFAULT_STYLE = function (style) {
    STATE.setArrowDefaultStyle(style, { silent: true });
  };
  window.SET_ARROW_DEFAULT_WEIGHT = function (weight) {
    STATE.setArrowDefaultWeight(weight, { silent: true });
  };
  window.UPDATE_ANNOTATION_COLORS_BY_TYPE = function (type, color) {
    STATE.updateAnnotationColorsByType(type, color);
  };
  window.CLEAR_SELECTED_ANNOTATION = function () {
    STATE.clearSelectedAnnotation();
  };
  window.XNAPPER_EXPORT_DOCUMENT_STATE = function () {
    return STATE.exportDocumentState();
  };
  window.XNAPPER_RESTORE_DOCUMENT_STATE = function (state) {
    return STATE.restoreDocumentState(state);
  };

  window.HANDLE_CANCEL_OPERATION = function () {
    if (STATE.skipNextCancelOperation) {
      STATE.skipNextCancelOperation = false;
      return true;
    }
  };
})();
