text: import pathToCurve from 'svg-path-commander/src/convert/pathToCurve.js';
  import pathToString from 'svg-path-commander/src/convert/pathToString.js';
  import normalizePath from 'svg-path-commander/src/process/normalizePath.js';
  import splitPath from 'svg-path-commander/src/process/splitPath.js';
  import roundPath from 'svg-path-commander/src/process/roundPath.js';
  import invalidPathValue from 'svg-path-commander/src/util/invalidPathValue.js';
  import getPathLength from 'svg-path-commander/src/util/getPathLength.js';
  import getPointAtLength from 'svg-path-commander/src/util/getPointAtLength.js';
  import getDrawDirection from 'svg-path-commander/src/util/getDrawDirection.js';
  import epsilon from 'svg-path-commander/src/math/epsilon.js';
  import midPoint from 'svg-path-commander/src/math/midPoint.js';
  import distanceSquareRoot from 'svg-path-commander/src/math/distanceSquareRoot.js';
  import { onStartSVGMorph } from './svgMorphBase.js';
  import coords from '../interpolation/coords.js';
  import defaultOptions from '../objects/defaultOptions.js';
  import selector from '../util/selector.js';
  
  /* SVGMorph = {
    property: 'path',
    defaultValue: [],
    interpolators: {numbers,coords},
    functions = { prepareStart, prepareProperty, onStart, crossCheck }
  } */
  
  // Component Interpolation
  // function function(array1, array2, length, progress)
  
  // Component Util
  // original script flubber
  // https://github.com/veltman/flubber
  
  function polygonLength(ring) {
    return ring.reduce((length, point, i) => (i
      ? length + distanceSquareRoot(ring[i - 1], point)
      : 0), 0);
  }
  
  function exactRing(pathArray) {
    const ring = [];
    const pathlen = pathArray.length;
    let segment = [];
    let pathCommand = '';
    let pathLength = 0;
  
    if (!pathArray.length || pathArray[0][0] !== 'M') {
      return false;
    }
  
    for (let i = 0; i < pathlen; i += 1) {
      segment = pathArray[i];
      [pathCommand] = segment;
  
      if ((pathCommand === 'M' && i) || pathCommand === 'Z') {
        break; // !!
      } else if ('ML'.indexOf(pathCommand) > -1) {
        ring.push([segment[1], segment[2]]);
      } else {
        return false;
      }
    }
  
    pathLength = polygonLength(ring);
  
    return pathlen ? { ring, pathLength } : false;
  }
  
  function approximateRing(parsed, maxSegmentLength) {
    const ringPath = splitPath(pathToString(parsed))[0];
    const curvePath = pathToCurve(ringPath, 4);
    const pathLength = getPathLength(curvePath);
    const ring = [];
    let numPoints = 3;
    let point;
  
    if (maxSegmentLength && !Number.isNaN(maxSegmentLength) && +maxSegmentLength > 0) {
      numPoints = Math.max(numPoints, Math.ceil(pathLength / maxSegmentLength));
    }
  
    for (let i = 0; i < numPoints; i += 1) {
      point = getPointAtLength(curvePath, (pathLength * i) / numPoints);
      ring.push([point.x, point.y]);
    }
  
    // Make all rings clockwise
    if (!getDrawDirection(curvePath)) {
      ring.reverse();
    }
  
    return {
      pathLength,
      ring,
      skipBisect: true,
    };
  }
  
  function pathStringToRing(str, maxSegmentLength) {
    const parsed = normalizePath(str, 0);
    return exactRing(parsed) || approximateRing(parsed, maxSegmentLength);
  }
  
  function rotateRing(ring, vs) {
    const len = ring.length;
    let min = Infinity;
    let bestOffset;
    let sumOfSquares = 0;
    let spliced;
    let d;
    let p;
  
    for (let offset = 0; offset < len; offset += 1) {
      sumOfSquares = 0;
  
      // vs.forEach((p, i) => {
      //   const d = distanceSquareRoot(ring[(offset + i) % len], p);
      //   sumOfSquares += d * d;
      // });
      for (let i = 0; i < vs.length; i += 1) {
        p = vs[i];
        d = distanceSquareRoot(ring[(offset + i) % len], p);
        sumOfSquares += d * d;
      }
  
      if (sumOfSquares < min) {
        min = sumOfSquares;
        bestOffset = offset;
      }
    }
  
    if (bestOffset) {
      spliced = ring.splice(0, bestOffset);
      ring.splice(ring.length, 0, ...spliced);
    }
  }
  
  function addPoints(ring, numPoints) {
    const desiredLength = ring.length + numPoints;
    // const step = ring.pathLength / numPoints;
    const step = polygonLength(ring) / numPoints;
  
    let i = 0;
    let cursor = 0;
    let insertAt = step / 2;
    let a;
    let b;
    let segment;
  
    while (ring.length < desiredLength) {
      a = ring[i];
      b = ring[(i + 1) % ring.length];
  
      segment = distanceSquareRoot(a, b);
  
      if (insertAt <= cursor + segment) {
        ring.splice(i + 1, 0, segment
          ? midPoint(a, b, (insertAt - cursor) / segment)
          : a.slice(0));
        insertAt += step;
      } else {
        cursor += segment;
        i += 1;
      }
    }
  }
  
  function bisect(ring, maxSegmentLength = Infinity) {
    let a = [];
    let b = [];
  
    for (let i = 0; i < ring.length; i += 1) {
      a = ring[i];
      b = i === ring.length - 1 ? ring[0] : ring[i + 1];
  
      // Could splice the whole set for a segment instead, but a bit messy
      while (distanceSquareRoot(a, b) > maxSegmentLength) {
        b = midPoint(a, b, 0.5);
        ring.splice(i + 1, 0, b);
      }
    }
  }
  
  function validRing(ring) {
    return Array.isArray(ring)
      && ring.every((point) => Array.isArray(point)
        && point.length === 2
        && !Number.isNaN(point[0])
        && !Number.isNaN(point[1]));
  }
  
  function normalizeRing(input, maxSegmentLength) {
    let skipBisect;
    let pathLength;
    let ring = input;
  
    if (typeof (ring) === 'string') {
      const converted = pathStringToRing(ring, maxSegmentLength);
      ring = converted.ring;
      skipBisect = converted.skipBisect;
      pathLength = converted.pathLength;
    } else if (!Array.isArray(ring)) {
      throw Error(`${invalidPathValue}: ${ring}`);
    }
  
    const points = ring.slice(0);
    points.pathLength = pathLength;
  
    if (!validRing(points)) {
      throw Error(`${invalidPathValue}: ${points}`);
    }
  
    // TODO skip this test to avoid scale issues?
    // Chosen epsilon (1e-6) is problematic for small coordinate range, we now use 1e-9
    if (points.length > 1 && distanceSquareRoot(points[0], points[points.length - 1]) < epsilon) {
      points.pop();
    }
  
    if (!skipBisect && maxSegmentLength
      && !Number.isNaN(maxSegmentLength) && (+maxSegmentLength) > 0) {
      bisect(points, maxSegmentLength);
    }
  
    return points;
  }
  
  function getInterpolationPoints(pathArray1, pathArray2, precision) {
    const morphPrecision = precision || defaultOptions.morphPrecision;
    const fromRing = normalizeRing(pathArray1, morphPrecision);
    const toRing = normalizeRing(pathArray2, morphPrecision);
    const diff = fromRing.length - toRing.length;
  
    addPoints(fromRing, diff < 0 ? diff * -1 : 0);
    addPoints(toRing, diff > 0 ? diff : 0);
  
    rotateRing(fromRing, toRing);
  
    return [roundPath(fromRing), roundPath(toRing)];
  }
  
  // Component functions
  function getSVGMorph(/* tweenProp */) {
    return this.element.getAttribute('d');
  }
  
  function prepareSVGMorph(tweenProp, value) {
    const pathObject = {};
    // remove newlines, they brake JSON strings sometimes
    const pathReg = new RegExp('\\n', 'ig');
    let elem = null;
  
    if (value instanceof SVGElement) {
      elem = value;
    } else if (/^\.|^#/.test(value)) {
      elem = selector(value);
    }
  
    // first make sure we return pre-processed values
    if (typeof (value) === 'object' && value.pathArray) {
      return value;
    } if (elem && ['path', 'glyph'].includes(elem.tagName)) {
      pathObject.original = elem.getAttribute('d').replace(pathReg, '');
    // maybe it's a string path already
    } else if (!elem && typeof (value) === 'string') {
      pathObject.original = value.replace(pathReg, '');
    }
  
    return pathObject;
  }
  function crossCheckSVGMorph(prop) {
    if (this.valuesEnd[prop]) {
      const pathArray1 = this.valuesStart[prop].pathArray;
      const pathArray2 = this.valuesEnd[prop].pathArray;
      // skip already processed paths
      // allow the component to work with pre-processed values
      if (!pathArray1 || !pathArray2
        || (pathArray1 && pathArray2 && pathArray1.length !== pathArray2.length)) {
        const p1 = this.valuesStart[prop].original;
        const p2 = this.valuesEnd[prop].original;
        // process morphPrecision
        const morphPrecision = this._morphPrecision
          ? parseInt(this._morphPrecision, 10)
          : defaultOptions.morphPrecision;
  
        const [path1, path2] = getInterpolationPoints(p1, p2, morphPrecision);
        this.valuesStart[prop].pathArray = path1;
        this.valuesEnd[prop].pathArray = path2;
      }
    }
  }
  
  // All Component Functions
  const svgMorphFunctions = {
    prepareStart: getSVGMorph,
    prepareProperty: prepareSVGMorph,
    onStart: onStartSVGMorph,
    crossCheck: crossCheckSVGMorph,
  };
  
  // Component Full
  const svgMorph = {
    component: 'svgMorph',
    property: 'path',
    defaultValue: [],
    Interpolate: coords,
    defaultOptions: { morphPrecision: 10, morphIndex: 0 },
    functions: svgMorphFunctions,
    // Export utils to global for faster execution
    Util: {
      addPoints,
      bisect,
      normalizeRing,
      validRing, // component
      getInterpolationPoints,
      pathStringToRing,
      distanceSquareRoot,
      midPoint,
      approximateRing,
      rotateRing,
      pathToString,
      pathToCurve, // svg-path-commander
      getPathLength,
      getPointAtLength,
      getDrawDirection,
      roundPath,
    },
  };
  
  export default svgMorph;
  