/*
 *  Implements the .roundRect() method of the CanvasPath mixin
 *  as introduced by https://github.com/whatwg/html/pull/6765
 * 
 * From https://github.com/Kaiido/roundRect
 */
export default function roundRect(x, y, w, h, radii) {
  if (!([x, y, w, h].every((input) => Number.isFinite(input)))) {
    return;
  }

  radii = parseRadiiArgument(radii);

  let upperLeft, upperRight, lowerRight, lowerLeft;

  if (radii.length === 4) {

    upperLeft = toCornerPoint(radii[0]);
    upperRight = toCornerPoint(radii[1]);
    lowerRight = toCornerPoint(radii[2]);
    lowerLeft = toCornerPoint(radii[3]);

  } else if (radii.length === 3) {

    upperLeft = toCornerPoint(radii[0]);
    upperRight = toCornerPoint(radii[1]);
    lowerLeft = toCornerPoint(radii[1]);
    lowerRight = toCornerPoint(radii[2]);

  } else if (radii.length === 2) {

    upperLeft = toCornerPoint(radii[0]);
    lowerRight = toCornerPoint(radii[0]);
    upperRight = toCornerPoint(radii[1]);
    lowerLeft = toCornerPoint(radii[1]);

  } else if (radii.length === 1) {

    upperLeft = toCornerPoint(radii[0]);
    upperRight = toCornerPoint(radii[0]);
    lowerRight = toCornerPoint(radii[0]);
    lowerLeft = toCornerPoint(radii[0]);

  } else {

    throw new RangeError(`${getErrorMessageHeader(this)} ${radii.length} is not a valid size for radii sequence.`);

  }

  const corners = [upperLeft, upperRight, lowerRight, lowerLeft];
  const negativeCorner = corners.find(({ x, y }) => x < 0 || y < 0);
  const negativeValue = negativeCorner?.x < 0 ? negativeCorner.x : negativeCorner?.y

  if (corners.some(({ x, y }) => !Number.isFinite(x) || !Number.isFinite(y))) {

    return;

  }

  if (negativeCorner) {

    throw new RangeError(`${getErrorMessageHeader(this)} Radius value ${negativeCorner} is negative.`);

  }

  fixOverlappingCorners(corners);

  if (w < 0 && h < 0) {

    this.moveTo(x - upperLeft.x, y);
    this.ellipse(x + w + upperRight.x, y - upperRight.y, upperRight.x, upperRight.y, 0, -Math.PI * 1.5, -Math.PI);
    this.ellipse(x + w + lowerRight.x, y + h + lowerRight.y, lowerRight.x, lowerRight.y, 0, -Math.PI, -Math.PI / 2);
    this.ellipse(x - lowerLeft.x, y + h + lowerLeft.y, lowerLeft.x, lowerLeft.y, 0, -Math.PI / 2, 0);
    this.ellipse(x - upperLeft.x, y - upperLeft.y, upperLeft.x, upperLeft.y, 0, 0, -Math.PI / 2);

  } else if (w < 0) {

    this.moveTo(x - upperLeft.x, y);
    this.ellipse(x + w + upperRight.x, y + upperRight.y, upperRight.x, upperRight.y, 0, -Math.PI / 2, -Math.PI, 1);
    this.ellipse(x + w + lowerRight.x, y + h - lowerRight.y, lowerRight.x, lowerRight.y, 0, -Math.PI, -Math.PI * 1.5, 1);
    this.ellipse(x - lowerLeft.x, y + h - lowerLeft.y, lowerLeft.x, lowerLeft.y, 0, Math.PI / 2, 0, 1);
    this.ellipse(x - upperLeft.x, y + upperLeft.y, upperLeft.x, upperLeft.y, 0, 0, -Math.PI / 2, 1);

  } else if (h < 0) {

    this.moveTo(x + upperLeft.x, y);
    this.ellipse(x + w - upperRight.x, y - upperRight.y, upperRight.x, upperRight.y, 0, Math.PI / 2, 0, 1);
    this.ellipse(x + w - lowerRight.x, y + h + lowerRight.y, lowerRight.x, lowerRight.y, 0, 0, -Math.PI / 2, 1);
    this.ellipse(x + lowerLeft.x, y + h + lowerLeft.y, lowerLeft.x, lowerLeft.y, 0, -Math.PI / 2, -Math.PI, 1);
    this.ellipse(x + upperLeft.x, y - upperLeft.y, upperLeft.x, upperLeft.y, 0, -Math.PI, -Math.PI * 1.5, 1);

  } else {

    this.moveTo(x + upperLeft.x, y);
    this.ellipse(x + w - upperRight.x, y + upperRight.y, upperRight.x, upperRight.y, 0, -Math.PI / 2, 0);
    this.ellipse(x + w - lowerRight.x, y + h - lowerRight.y, lowerRight.x, lowerRight.y, 0, 0, Math.PI / 2);
    this.ellipse(x + lowerLeft.x, y + h - lowerLeft.y, lowerLeft.x, lowerLeft.y, 0, Math.PI / 2, Math.PI);
    this.ellipse(x + upperLeft.x, y + upperLeft.y, upperLeft.x, upperLeft.y, 0, Math.PI, Math.PI * 1.5);

  }

  this.closePath();
  this.moveTo(x, y);

  function toDOMPointInit(value) {

    const { x, y, z, w } = value;
    return { x, y, z, w };

  }

  function parseRadiiArgument(value) {

    // https://webidl.spec.whatwg.org/#es-union
    // with 'optional (unrestricted double or DOMPointInit
    //   or sequence<(unrestricted double or DOMPointInit)>) radii = 0'
    const type = typeof value;

    if (type === "undefined" || value === null) {

      return [0];

    }
    if (type === "function") {

      return [NaN];

    }
    if (type === "object") {

      if (typeof value[Symbol.iterator] === "function") {

        return [...value].map((elem) => {
          // https://webidl.spec.whatwg.org/#es-union
          // with '(unrestricted double or DOMPointInit)'
          const elemType = typeof elem;
          if (elemType === "undefined" || elem === null) {
            return 0;
          }
          if (elemType === "function") {
            return NaN;
          }
          if (elemType === "object") {
            return toDOMPointInit(elem);
          }
          return toUnrestrictedNumber(elem);
        });

      }

      return [toDOMPointInit(value)];

    }

    return [toUnrestrictedNumber(value)];

  }

  function toUnrestrictedNumber(value) {

    return +value;

  }

  function toCornerPoint(value) {

    const asNumber = toUnrestrictedNumber(value);
    if (Number.isFinite(asNumber)) {

      return {
        x: asNumber,
        y: asNumber
      };

    }
    if (Object(value) === value) {

      return {
        x: toUnrestrictedNumber(value.x ?? 0),
        y: toUnrestrictedNumber(value.y ?? 0)
      };

    }

    return {
      x: NaN,
      y: NaN
    };

  }

  function fixOverlappingCorners(corners) {

    const [upperLeft, upperRight, lowerRight, lowerLeft] = corners;
    const factors = [
      Math.abs(w) / (upperLeft.x + upperRight.x),
      Math.abs(h) / (upperRight.y + lowerRight.y),
      Math.abs(w) / (lowerRight.x + lowerLeft.x),
      Math.abs(h) / (upperLeft.y + lowerLeft.y)
    ];
    const minFactor = Math.min(...factors);
    if (minFactor <= 1) {

      for (const radii of corners) {

        radii.x *= minFactor;
        radii.y *= minFactor;

      }

    }

  }

}

function getErrorMessageHeader(instance) {

  return `Failed to execute 'roundRect' on '${getConstructorName(instance)}':`;

}

function getConstructorName(instance) {

  return Object(instance) === instance &&
    instance instanceof Path2D ? "Path2D" :
    instance instanceof globalThis?.CanvasRenderingContext2D ? "CanvasRenderingContext2D" :
      instance instanceof globalThis?.OffscreenCanvasRenderingContext2D ? "OffscreenCanvasRenderingContext2D" :
        instance?.constructor.name ||
        instance;

}