function startRedactMode(STATE) {
  STATE.setMode('redact');
  document.body.style.cursor = 'crosshair';
  document.body.classList.add('redact-mode');
  document.body.classList.add('show-crosshair');

  var instructionElement = document.createElement('div');
  instructionElement.setAttribute('class', 'redact-instruction');
  instructionElement.innerHTML = 'Draw to redact<br/>ESC to exit redact mode';

  document.body.appendChild(instructionElement);

  var svgRectsElement = STATE.ensureRedactLayer();
  if (!svgRectsElement) {
    stopRedactMode();
    return { cancel: function () {} };
  }

  var isDown = false;
  var isDrawing = false;
  var startPoint = null;
  var currentPoint = null;
  var rectElement = null;

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

    if (
      element.tagName &&
      element.tagName.toUpperCase() === 'RECT' &&
      element.classList.contains('text-rects')
    ) {
      return element;
    }

    if (element.closest) {
      return element.closest('rect.text-rects');
    }

    return null;
  }

  function isRedactedRect(element) {
    var rect = redactRectFromEventTarget(element);
    return (
      rect &&
      rect.classList.contains('redacted')
    );
  }

  function removeRedactedRect(element) {
    var rect = redactRectFromEventTarget(element);
    if (!rect || !rect.classList.contains('redacted')) {
      return false;
    }

    rect._skipRedactClick = true;

    if (rect.classList.contains('ocr-text-rect')) {
      STATE.setTextRectRedacted(rect, false);
    } else {
      STATE.removeAnnotation(annotationIDForRect(rect));
    }

    return true;
  }

  function handleKeydown(e) {
    // Key escape pressed
    if (e.keyCode === 27) {
      if (isDrawing) {
        stopDrawRectangle(e, true);
      }
      STATE.skipNextCancelOperation = true;
      stopRedactMode();
    }
  }

  function handleMouseDown(e) {
    e.preventDefault();

    if (removeRedactedRect(e.target)) {
      e.stopPropagation();
      isDown = false;
      startPoint = null;
      currentPoint = null;
      return;
    }

    isDown = true;
    startPoint = {
      x: e.clientX,
      y: e.clientY,
    };

    console.log(e.target);
  }

  function handleClick(e) {
    if (removeRedactedRect(e.target)) {
      e.preventDefault();
      e.stopPropagation();
    }
  }

  function shouldStartDrawing() {
    return (
      Math.abs(currentPoint.x - startPoint.x) > 5 &&
      Math.abs(currentPoint.y - startPoint.y) > 5
    );
  }

  function svgPointFromClientPoint(point) {
    if (!point) {
      return { x: 0, y: 0 };
    }

    if (svgRectsElement.createSVGPoint && svgRectsElement.getScreenCTM()) {
      var svgPoint = svgRectsElement.createSVGPoint();
      svgPoint.x = point.x;
      svgPoint.y = point.y;
      return svgPoint.matrixTransform(svgRectsElement.getScreenCTM().inverse());
    }

    var rect = svgRectsElement.getBoundingClientRect();
    var viewBox = svgRectsElement.viewBox && svgRectsElement.viewBox.baseVal;
    var width = (viewBox && viewBox.width) || rect.width || 1;
    var height = (viewBox && viewBox.height) || rect.height || 1;
    return {
      x: ((point.x - rect.left) / (rect.width || 1)) * width,
      y: ((point.y - rect.top) / (rect.height || 1)) * height,
    };
  }

  function handleMouseMove(e) {
    // make instructionElement follow the cursor
    instructionElement.style.left = e.clientX + 10 + 'px';
    instructionElement.style.top = e.clientY + 10 + 'px';

    var targetRect = redactRectFromEventTarget(e.target);
    var isRedactRect = !!targetRect;

    if (isDown) {
      currentPoint = {
        x: e.clientX,
        y: e.clientY,
      };

      if (shouldStartDrawing() && !isRedactRect) {
        instructionElement.innerHTML = 'ESC to cancel';

        startDrawRectangle(e);
      }
    } else {
      if (isRedactRect) {
        if (targetRect.classList.contains('redacted')) {
          instructionElement.innerHTML = 'Click to remove';
        } else {
          instructionElement.innerHTML = 'Click to redact text';
        }
      } else {
        instructionElement.innerHTML =
          'Draw to redact<br/>ESC to exit redact mode';
      }
    }

    if (isDrawing) {
      draw(e);
    }
  }

  function draw() {
    if (!rectElement) {
      return;
    }

    var startSvgPoint = svgPointFromClientPoint(startPoint);
    var currentSvgPoint = svgPointFromClientPoint(currentPoint);
    var x = Math.min(startSvgPoint.x, currentSvgPoint.x);
    var y = Math.min(startSvgPoint.y, currentSvgPoint.y);

    var width = Math.abs(startSvgPoint.x - currentSvgPoint.x);
    var height = Math.abs(startSvgPoint.y - currentSvgPoint.y);

    rectElement.classList.add('text-rects');
    rectElement.classList.add('redacted');

    rectElement.setAttribute('x', x);
    rectElement.setAttribute('y', y);
    rectElement.setAttribute('width', width);
    rectElement.setAttribute('height', height);

    if (STATE.redactDefaults.mode === 'pixelate') {
      STATE.updateRedactAnnotationMode(
        annotationIDForRect(rectElement),
        STATE.redactDefaults.mode,
        { silent: true }
      );
    } else {
      rectElement.setAttribute('fill', STATE.annotationDefaults.redact);
      rectElement.setAttribute('fill-opacity', '0.5');
    }
  }

  function handleMouseUp(e) {
    isDown = false;
    if (stopDrawRectangle(e)) {
      stopRedactMode();
    }
  }

  function startDrawRectangle(e) {
    if (!isDrawing) {
      isDrawing = true;
      var itemID = 'redact-' + Math.random();
      rectElement = STATE.createRedactAnnotation({
        id: itemID,
        color: STATE.annotationDefaults.redact,
        redactMode: STATE.redactDefaults.mode,
      }).element;
      console.log('startDrawRectangle');
    }
  }

  function stopDrawRectangle(e, cancel) {
    if (!isDrawing) {
      return false;
    }

    isDrawing = false;

    if (cancel) {
      // remove `rectElement` out of its parent
      STATE.removeAnnotation(annotationIDForRect(rectElement));
    } else {
      var annotationID = annotationIDForRect(rectElement);
      STATE.updateRedactAnnotationMode(
        annotationID,
        STATE.redactDefaults.mode,
        { silent: true }
      );
      STATE.selectAnnotation(annotationID);
      STATE.markDocumentChanged(true);
    }

    rectElement = null;
    console.log('stopDrawRectangle');
    return !cancel;
  }

  function stopRedactMode() {
    STATE.setMode('free');
    document.body.classList.remove('redact-mode');
    document.body.style.cursor = 'auto';
    document.body.classList.remove('show-crosshair');
    instructionElement.remove();
    STATE.currentController = null;

    document.removeEventListener('keydown', handleKeydown);
    document.removeEventListener('mousedown', handleMouseDown);
    document.removeEventListener('mousemove', handleMouseMove);
    document.removeEventListener('mouseup', handleMouseUp);
    document.removeEventListener('click', handleClick, true);
  }

  document.addEventListener('keydown', handleKeydown);
  document.addEventListener('mousedown', handleMouseDown);
  document.addEventListener('mousemove', handleMouseMove);
  document.addEventListener('mouseup', handleMouseUp);
  document.addEventListener('click', handleClick, true);

  return { cancel: stopRedactMode };
}

function annotationIDForRect(rectElement) {
  return rectElement && rectElement.getAttribute('data-annotation-id');
}
