import { debounce, bind, reduce, find, forEach, isDefined, every, assign, isNumber } from "../../../min-dash/dist/index.esm.mjs";
import { assignStyle as assign$1 } from "../../../min-dom/dist/index.esm.mjs";
import { add, remove as remove$1 } from "../util/Collections.mjs";
import { getType, getBBox } from "../util/Elements.mjs";
import { asTRBL } from "../layout/LayoutUtil.mjs";
import { create, attr, append, remove, classes, transform, createMatrix } from "../../../tiny-svg/dist/index.esm.mjs";
function round(number, resolution) {
  return Math.round(number * resolution) / resolution;
}
function ensurePx(number) {
  return isNumber(number) ? number + "px" : number;
}
function findRoot(element) {
  while (element.parent) {
    element = element.parent;
  }
  return element;
}
function createContainer(options) {
  options = assign({}, { width: "100%", height: "100%" }, options);
  const container = options.container || document.body;
  const parent = document.createElement("div");
  parent.setAttribute("class", "djs-container");
  assign$1(parent, {
    position: "relative",
    overflow: "hidden",
    width: ensurePx(options.width),
    height: ensurePx(options.height)
  });
  container.appendChild(parent);
  return parent;
}
function createGroup(parent, cls, childIndex) {
  const group = create("g");
  classes(group).add(cls);
  const index = childIndex !== void 0 ? childIndex : parent.childNodes.length - 1;
  parent.insertBefore(group, parent.childNodes[index] || null);
  return group;
}
const BASE_LAYER = "base";
const PLANE_LAYER_INDEX = 0;
const UTILITY_LAYER_INDEX = 1;
const REQUIRED_MODEL_ATTRS = {
  shape: ["x", "y", "width", "height"],
  connection: ["waypoints"]
};
function Canvas(config, eventBus, graphicsFactory, elementRegistry) {
  this._eventBus = eventBus;
  this._elementRegistry = elementRegistry;
  this._graphicsFactory = graphicsFactory;
  this._rootsIdx = 0;
  this._layers = {};
  this._planes = [];
  this._rootElement = null;
  this._init(config || {});
}
Canvas.$inject = [
  "config.canvas",
  "eventBus",
  "graphicsFactory",
  "elementRegistry"
];
Canvas.prototype._init = function(config) {
  const eventBus = this._eventBus;
  const container = this._container = createContainer(config);
  const svg = this._svg = create("svg");
  attr(svg, { width: "100%", height: "100%" });
  append(container, svg);
  const viewport = this._viewport = createGroup(svg, "viewport");
  if (config.deferUpdate !== false) {
    this._viewboxChanged = debounce(bind(this._viewboxChanged, this), 300);
  }
  eventBus.on("diagram.init", function() {
    eventBus.fire("canvas.init", {
      svg,
      viewport
    });
  }, this);
  eventBus.on([
    "shape.added",
    "connection.added",
    "shape.removed",
    "connection.removed",
    "elements.changed",
    "root.set"
  ], function() {
    delete this._cachedViewbox;
  }, this);
  eventBus.on("diagram.destroy", 500, this._destroy, this);
  eventBus.on("diagram.clear", 500, this._clear, this);
};
Canvas.prototype._destroy = function(emit) {
  this._eventBus.fire("canvas.destroy", {
    svg: this._svg,
    viewport: this._viewport
  });
  const parent = this._container.parentNode;
  if (parent) {
    parent.removeChild(this._container);
  }
  delete this._svg;
  delete this._container;
  delete this._layers;
  delete this._planes;
  delete this._rootElement;
  delete this._viewport;
};
Canvas.prototype._clear = function() {
  const allElements = this._elementRegistry.getAll();
  allElements.forEach((element) => {
    const type = getType(element);
    if (type === "root") {
      this.removeRootElement(element);
    } else {
      this._removeElement(element, type);
    }
  });
  this._planes = [];
  this._rootElement = null;
  delete this._cachedViewbox;
};
Canvas.prototype.getDefaultLayer = function() {
  return this.getLayer(BASE_LAYER, PLANE_LAYER_INDEX);
};
Canvas.prototype.getLayer = function(name, index) {
  if (!name) {
    throw new Error("must specify a name");
  }
  let layer = this._layers[name];
  if (!layer) {
    layer = this._layers[name] = this._createLayer(name, index);
  }
  if (typeof index !== "undefined" && layer.index !== index) {
    throw new Error("layer <" + name + "> already created at index <" + index + ">");
  }
  return layer.group;
};
Canvas.prototype._getChildIndex = function(index) {
  return reduce(this._layers, function(childIndex, layer) {
    if (layer.visible && index >= layer.index) {
      childIndex++;
    }
    return childIndex;
  }, 0);
};
Canvas.prototype._createLayer = function(name, index) {
  if (typeof index === "undefined") {
    index = UTILITY_LAYER_INDEX;
  }
  const childIndex = this._getChildIndex(index);
  return {
    group: createGroup(this._viewport, "layer-" + name, childIndex),
    index,
    visible: true
  };
};
Canvas.prototype.showLayer = function(name) {
  if (!name) {
    throw new Error("must specify a name");
  }
  const layer = this._layers[name];
  if (!layer) {
    throw new Error("layer <" + name + "> does not exist");
  }
  const viewport = this._viewport;
  const group = layer.group;
  const index = layer.index;
  if (layer.visible) {
    return group;
  }
  const childIndex = this._getChildIndex(index);
  viewport.insertBefore(group, viewport.childNodes[childIndex] || null);
  layer.visible = true;
  return group;
};
Canvas.prototype.hideLayer = function(name) {
  if (!name) {
    throw new Error("must specify a name");
  }
  const layer = this._layers[name];
  if (!layer) {
    throw new Error("layer <" + name + "> does not exist");
  }
  const group = layer.group;
  if (!layer.visible) {
    return group;
  }
  remove(group);
  layer.visible = false;
  return group;
};
Canvas.prototype._removeLayer = function(name) {
  const layer = this._layers[name];
  if (layer) {
    delete this._layers[name];
    remove(layer.group);
  }
};
Canvas.prototype.getActiveLayer = function() {
  const plane = this._findPlaneForRoot(this.getRootElement());
  if (!plane) {
    return null;
  }
  return plane.layer;
};
Canvas.prototype.findRoot = function(element) {
  if (typeof element === "string") {
    element = this._elementRegistry.get(element);
  }
  if (!element) {
    return;
  }
  const plane = this._findPlaneForRoot(
    findRoot(element)
  ) || {};
  return plane.rootElement;
};
Canvas.prototype.getRootElements = function() {
  return this._planes.map(function(plane) {
    return plane.rootElement;
  });
};
Canvas.prototype._findPlaneForRoot = function(rootElement) {
  return find(this._planes, function(plane) {
    return plane.rootElement === rootElement;
  });
};
Canvas.prototype.getContainer = function() {
  return this._container;
};
Canvas.prototype._updateMarker = function(element, marker, add2) {
  let container;
  if (!element.id) {
    element = this._elementRegistry.get(element);
  }
  container = this._elementRegistry._elements[element.id];
  if (!container) {
    return;
  }
  forEach([container.gfx, container.secondaryGfx], function(gfx) {
    if (gfx) {
      if (add2) {
        classes(gfx).add(marker);
      } else {
        classes(gfx).remove(marker);
      }
    }
  });
  this._eventBus.fire("element.marker.update", { element, gfx: container.gfx, marker, add: !!add2 });
};
Canvas.prototype.addMarker = function(element, marker) {
  this._updateMarker(element, marker, true);
};
Canvas.prototype.removeMarker = function(element, marker) {
  this._updateMarker(element, marker, false);
};
Canvas.prototype.hasMarker = function(element, marker) {
  if (!element.id) {
    element = this._elementRegistry.get(element);
  }
  const gfx = this.getGraphics(element);
  return classes(gfx).has(marker);
};
Canvas.prototype.toggleMarker = function(element, marker) {
  if (this.hasMarker(element, marker)) {
    this.removeMarker(element, marker);
  } else {
    this.addMarker(element, marker);
  }
};
Canvas.prototype.getRootElement = function() {
  const rootElement = this._rootElement;
  if (rootElement || this._planes.length) {
    return rootElement;
  }
  return this.setRootElement(this.addRootElement(null));
};
Canvas.prototype.addRootElement = function(rootElement) {
  const idx = this._rootsIdx++;
  if (!rootElement) {
    rootElement = {
      id: "__implicitroot_" + idx,
      children: [],
      isImplicit: true
    };
  }
  const layerName = rootElement.layer = "root-" + idx;
  this._ensureValid("root", rootElement);
  const layer = this.getLayer(layerName, PLANE_LAYER_INDEX);
  this.hideLayer(layerName);
  this._addRoot(rootElement, layer);
  this._planes.push({
    rootElement,
    layer
  });
  return rootElement;
};
Canvas.prototype.removeRootElement = function(rootElement) {
  if (typeof rootElement === "string") {
    rootElement = this._elementRegistry.get(rootElement);
  }
  const plane = this._findPlaneForRoot(rootElement);
  if (!plane) {
    return;
  }
  this._removeRoot(rootElement);
  this._removeLayer(rootElement.layer);
  this._planes = this._planes.filter(function(plane2) {
    return plane2.rootElement !== rootElement;
  });
  if (this._rootElement === rootElement) {
    this._rootElement = null;
  }
  return rootElement;
};
Canvas.prototype.setRootElement = function(rootElement, override) {
  if (isDefined(override)) {
    throw new Error("override not supported");
  }
  if (rootElement === this._rootElement) {
    return;
  }
  let plane;
  if (!rootElement) {
    throw new Error("rootElement required");
  }
  plane = this._findPlaneForRoot(rootElement);
  if (!plane) {
    rootElement = this.addRootElement(rootElement);
  }
  this._setRoot(rootElement);
  return rootElement;
};
Canvas.prototype._removeRoot = function(element) {
  const elementRegistry = this._elementRegistry, eventBus = this._eventBus;
  eventBus.fire("root.remove", { element });
  eventBus.fire("root.removed", { element });
  elementRegistry.remove(element);
};
Canvas.prototype._addRoot = function(element, gfx) {
  const elementRegistry = this._elementRegistry, eventBus = this._eventBus;
  eventBus.fire("root.add", { element });
  elementRegistry.add(element, gfx);
  eventBus.fire("root.added", { element, gfx });
};
Canvas.prototype._setRoot = function(rootElement, layer) {
  const currentRoot = this._rootElement;
  if (currentRoot) {
    this._elementRegistry.updateGraphics(currentRoot, null, true);
    this.hideLayer(currentRoot.layer);
  }
  if (rootElement) {
    if (!layer) {
      layer = this._findPlaneForRoot(rootElement).layer;
    }
    this._elementRegistry.updateGraphics(rootElement, this._svg, true);
    this.showLayer(rootElement.layer);
  }
  this._rootElement = rootElement;
  this._eventBus.fire("root.set", { element: rootElement });
};
Canvas.prototype._ensureValid = function(type, element) {
  if (!element.id) {
    throw new Error("element must have an id");
  }
  if (this._elementRegistry.get(element.id)) {
    throw new Error("element <" + element.id + "> already exists");
  }
  const requiredAttrs = REQUIRED_MODEL_ATTRS[type];
  const valid = every(requiredAttrs, function(attr2) {
    return typeof element[attr2] !== "undefined";
  });
  if (!valid) {
    throw new Error(
      "must supply { " + requiredAttrs.join(", ") + " } with " + type
    );
  }
};
Canvas.prototype._setParent = function(element, parent, parentIndex) {
  add(parent.children, element, parentIndex);
  element.parent = parent;
};
Canvas.prototype._addElement = function(type, element, parent, parentIndex) {
  parent = parent || this.getRootElement();
  const eventBus = this._eventBus, graphicsFactory = this._graphicsFactory;
  this._ensureValid(type, element);
  eventBus.fire(type + ".add", { element, parent });
  this._setParent(element, parent, parentIndex);
  const gfx = graphicsFactory.create(type, element, parentIndex);
  this._elementRegistry.add(element, gfx);
  graphicsFactory.update(type, element, gfx);
  eventBus.fire(type + ".added", { element, gfx });
  return element;
};
Canvas.prototype.addShape = function(shape, parent, parentIndex) {
  return this._addElement("shape", shape, parent, parentIndex);
};
Canvas.prototype.addConnection = function(connection, parent, parentIndex) {
  return this._addElement("connection", connection, parent, parentIndex);
};
Canvas.prototype._removeElement = function(element, type) {
  const elementRegistry = this._elementRegistry, graphicsFactory = this._graphicsFactory, eventBus = this._eventBus;
  element = elementRegistry.get(element.id || element);
  if (!element) {
    return;
  }
  eventBus.fire(type + ".remove", { element });
  graphicsFactory.remove(element);
  remove$1(element.parent && element.parent.children, element);
  element.parent = null;
  eventBus.fire(type + ".removed", { element });
  elementRegistry.remove(element);
  return element;
};
Canvas.prototype.removeShape = function(shape) {
  return this._removeElement(shape, "shape");
};
Canvas.prototype.removeConnection = function(connection) {
  return this._removeElement(connection, "connection");
};
Canvas.prototype.getGraphics = function(element, secondary) {
  return this._elementRegistry.getGraphics(element, secondary);
};
Canvas.prototype._changeViewbox = function(changeFn) {
  this._eventBus.fire("canvas.viewbox.changing");
  changeFn.apply(this);
  this._cachedViewbox = null;
  this._viewboxChanged();
};
Canvas.prototype._viewboxChanged = function() {
  this._eventBus.fire("canvas.viewbox.changed", { viewbox: this.viewbox() });
};
Canvas.prototype.viewbox = function(box) {
  if (box === void 0 && this._cachedViewbox) {
    return this._cachedViewbox;
  }
  const viewport = this._viewport, outerBox = this.getSize();
  let innerBox, matrix, activeLayer, transform$1, scale, x, y;
  if (!box) {
    activeLayer = this._rootElement ? this.getActiveLayer() : null;
    innerBox = activeLayer && activeLayer.getBBox() || {};
    transform$1 = transform(viewport);
    matrix = transform$1 ? transform$1.matrix : createMatrix();
    scale = round(matrix.a, 1e3);
    x = round(-matrix.e || 0, 1e3);
    y = round(-matrix.f || 0, 1e3);
    box = this._cachedViewbox = {
      x: x ? x / scale : 0,
      y: y ? y / scale : 0,
      width: outerBox.width / scale,
      height: outerBox.height / scale,
      scale,
      inner: {
        width: innerBox.width || 0,
        height: innerBox.height || 0,
        x: innerBox.x || 0,
        y: innerBox.y || 0
      },
      outer: outerBox
    };
    return box;
  } else {
    this._changeViewbox(function() {
      scale = Math.min(outerBox.width / box.width, outerBox.height / box.height);
      const matrix2 = this._svg.createSVGMatrix().scale(scale).translate(-box.x, -box.y);
      transform(viewport, matrix2);
    });
  }
  return box;
};
Canvas.prototype.scroll = function(delta) {
  const node = this._viewport;
  let matrix = node.getCTM();
  if (delta) {
    this._changeViewbox(function() {
      delta = assign({ dx: 0, dy: 0 }, delta || {});
      matrix = this._svg.createSVGMatrix().translate(delta.dx, delta.dy).multiply(matrix);
      setCTM(node, matrix);
    });
  }
  return { x: matrix.e, y: matrix.f };
};
Canvas.prototype.scrollToElement = function(element, padding) {
  let defaultPadding = 100;
  if (typeof element === "string") {
    element = this._elementRegistry.get(element);
  }
  const rootElement = this.findRoot(element);
  if (rootElement !== this.getRootElement()) {
    this.setRootElement(rootElement);
  }
  if (!padding) {
    padding = {};
  }
  if (typeof padding === "number") {
    defaultPadding = padding;
  }
  padding = {
    top: padding.top || defaultPadding,
    right: padding.right || defaultPadding,
    bottom: padding.bottom || defaultPadding,
    left: padding.left || defaultPadding
  };
  const elementBounds = getBBox(element), elementTrbl = asTRBL(elementBounds), viewboxBounds = this.viewbox(), zoom = this.zoom();
  let dx, dy;
  viewboxBounds.y += padding.top / zoom;
  viewboxBounds.x += padding.left / zoom;
  viewboxBounds.width -= (padding.right + padding.left) / zoom;
  viewboxBounds.height -= (padding.bottom + padding.top) / zoom;
  const viewboxTrbl = asTRBL(viewboxBounds);
  const canFit = elementBounds.width < viewboxBounds.width && elementBounds.height < viewboxBounds.height;
  if (!canFit) {
    dx = elementBounds.x - viewboxBounds.x;
    dy = elementBounds.y - viewboxBounds.y;
  } else {
    const dRight = Math.max(0, elementTrbl.right - viewboxTrbl.right), dLeft = Math.min(0, elementTrbl.left - viewboxTrbl.left), dBottom = Math.max(0, elementTrbl.bottom - viewboxTrbl.bottom), dTop = Math.min(0, elementTrbl.top - viewboxTrbl.top);
    dx = dRight || dLeft;
    dy = dBottom || dTop;
  }
  this.scroll({ dx: -dx * zoom, dy: -dy * zoom });
};
Canvas.prototype.zoom = function(newScale, center) {
  if (!newScale) {
    return this.viewbox(newScale).scale;
  }
  if (newScale === "fit-viewport") {
    return this._fitViewport(center);
  }
  let outer, matrix;
  this._changeViewbox(function() {
    if (typeof center !== "object") {
      outer = this.viewbox().outer;
      center = {
        x: outer.width / 2,
        y: outer.height / 2
      };
    }
    matrix = this._setZoom(newScale, center);
  });
  return round(matrix.a, 1e3);
};
function setCTM(node, m) {
  const mstr = "matrix(" + m.a + "," + m.b + "," + m.c + "," + m.d + "," + m.e + "," + m.f + ")";
  node.setAttribute("transform", mstr);
}
Canvas.prototype._fitViewport = function(center) {
  const vbox = this.viewbox(), outer = vbox.outer, inner = vbox.inner;
  let newScale, newViewbox;
  if (inner.x >= 0 && inner.y >= 0 && inner.x + inner.width <= outer.width && inner.y + inner.height <= outer.height && !center) {
    newViewbox = {
      x: 0,
      y: 0,
      width: Math.max(inner.width + inner.x, outer.width),
      height: Math.max(inner.height + inner.y, outer.height)
    };
  } else {
    newScale = Math.min(1, outer.width / inner.width, outer.height / inner.height);
    newViewbox = {
      x: inner.x + (center ? inner.width / 2 - outer.width / newScale / 2 : 0),
      y: inner.y + (center ? inner.height / 2 - outer.height / newScale / 2 : 0),
      width: outer.width / newScale,
      height: outer.height / newScale
    };
  }
  this.viewbox(newViewbox);
  return this.viewbox(false).scale;
};
Canvas.prototype._setZoom = function(scale, center) {
  const svg = this._svg, viewport = this._viewport;
  const matrix = svg.createSVGMatrix();
  const point = svg.createSVGPoint();
  let centerPoint, originalPoint, currentMatrix, scaleMatrix, newMatrix;
  currentMatrix = viewport.getCTM();
  const currentScale = currentMatrix.a;
  if (center) {
    centerPoint = assign(point, center);
    originalPoint = centerPoint.matrixTransform(currentMatrix.inverse());
    scaleMatrix = matrix.translate(originalPoint.x, originalPoint.y).scale(1 / currentScale * scale).translate(-originalPoint.x, -originalPoint.y);
    newMatrix = currentMatrix.multiply(scaleMatrix);
  } else {
    newMatrix = matrix.scale(scale);
  }
  setCTM(this._viewport, newMatrix);
  return newMatrix;
};
Canvas.prototype.getSize = function() {
  return {
    width: this._container.clientWidth,
    height: this._container.clientHeight
  };
};
Canvas.prototype.getAbsoluteBBox = function(element) {
  const vbox = this.viewbox();
  let bbox;
  if (element.waypoints) {
    const gfx = this.getGraphics(element);
    bbox = gfx.getBBox();
  } else {
    bbox = element;
  }
  const x = bbox.x * vbox.scale - vbox.x * vbox.scale;
  const y = bbox.y * vbox.scale - vbox.y * vbox.scale;
  const width = bbox.width * vbox.scale;
  const height = bbox.height * vbox.scale;
  return {
    x,
    y,
    width,
    height
  };
};
Canvas.prototype.resized = function() {
  delete this._cachedViewbox;
  this._eventBus.fire("canvas.resized");
};
export {
  Canvas as default
};
