import { rotateVector } from '../util/rotate-vector';

/**
 * Converts A (arc-to) segments to C (cubic-bezier-to).
 *
 * For more information of where this math came from visit:
 * http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
 */
export function arcToCubic(
  X1: number,
  Y1: number,
  RX: number,
  RY: number,
  angle: number,
  LAF: number,
  SF: number,
  X2: number,
  Y2: number,
  recursive: number[],
) {
  let x1 = X1;
  let y1 = Y1;
  let rx = RX;
  let ry = RY;
  let x2 = X2;
  let y2 = Y2;
  // for more information of where this Math came from visit:
  // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
  const d120 = (Math.PI * 120) / 180;

  const rad = (Math.PI / 180) * (+angle || 0);
  /** @type {number[]} */
  let res = [];
  let xy;
  let f1;
  let f2;
  let cx;
  let cy;

  if (!recursive) {
    xy = rotateVector(x1, y1, -rad);
    x1 = xy.x;
    y1 = xy.y;
    xy = rotateVector(x2, y2, -rad);
    x2 = xy.x;
    y2 = xy.y;

    const x = (x1 - x2) / 2;
    const y = (y1 - y2) / 2;
    let h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
    if (h > 1) {
      h = Math.sqrt(h);
      rx *= h;
      ry *= h;
    }
    const rx2 = rx * rx;
    const ry2 = ry * ry;

    const k =
      (LAF === SF ? -1 : 1) *
      Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));

    cx = (k * rx * y) / ry + (x1 + x2) / 2;
    cy = (k * -ry * x) / rx + (y1 + y2) / 2;
    // eslint-disable-next-line no-bitwise -- Impossible to satisfy no-bitwise
    f1 = Math.asin(((((y1 - cy) / ry) * 10 ** 9) >> 0) / 10 ** 9);
    // eslint-disable-next-line no-bitwise -- Impossible to satisfy no-bitwise
    f2 = Math.asin(((((y2 - cy) / ry) * 10 ** 9) >> 0) / 10 ** 9);

    f1 = x1 < cx ? Math.PI - f1 : f1;
    f2 = x2 < cx ? Math.PI - f2 : f2;
    if (f1 < 0) f1 = Math.PI * 2 + f1;
    if (f2 < 0) f2 = Math.PI * 2 + f2;
    if (SF && f1 > f2) {
      f1 -= Math.PI * 2;
    }
    if (!SF && f2 > f1) {
      f2 -= Math.PI * 2;
    }
  } else {
    [f1, f2, cx, cy] = recursive;
  }
  let df = f2 - f1;
  if (Math.abs(df) > d120) {
    const f2old = f2;
    const x2old = x2;
    const y2old = y2;
    f2 = f1 + d120 * (SF && f2 > f1 ? 1 : -1);
    x2 = cx + rx * Math.cos(f2);
    y2 = cy + ry * Math.sin(f2);
    res = arcToCubic(x2, y2, rx, ry, angle, 0, SF, x2old, y2old, [f2, f2old, cx, cy]);
  }
  df = f2 - f1;
  const c1 = Math.cos(f1);
  const s1 = Math.sin(f1);
  const c2 = Math.cos(f2);
  const s2 = Math.sin(f2);
  const t = Math.tan(df / 4);
  const hx = (4 / 3) * rx * t;
  const hy = (4 / 3) * ry * t;
  const m1 = [x1, y1];
  const m2 = [x1 + hx * s1, y1 - hy * c1];
  const m3 = [x2 + hx * s2, y2 - hy * c2];
  const m4 = [x2, y2];
  m2[0] = 2 * m1[0] - m2[0];
  m2[1] = 2 * m1[1] - m2[1];
  if (recursive) {
    return m2.concat(m3, m4, res);
    // return [...m2, ...m3, ...m4, ...res];
  }

  res = m2.concat(m3, m4, res);
  // res = [...m2, ...m3, ...m4, ...res];
  const newres = [];
  for (let i = 0, ii = res.length; i < ii; i += 1) {
    newres[i] = i % 2 ? rotateVector(res[i - 1], res[i], rad).y : rotateVector(res[i], res[i + 1], rad).x;
  }
  return newres;
}

// const TAU = Math.PI * 2;

// const mapToEllipse = (
//   { x, y }: { x: number; y: number },
//   rx: number,
//   ry: number,
//   cosphi: number,
//   sinphi: number,
//   centerx: number,
//   centery: number,
// ) => {
//   x *= rx;
//   y *= ry;

//   const xp = cosphi * x - sinphi * y;
//   const yp = sinphi * x + cosphi * y;

//   return {
//     x: xp + centerx,
//     y: yp + centery,
//   };
// };

// const approxUnitArc = (ang1: number, ang2: number) => {
//   // If 90 degree circular arc, use a constant
//   // as derived from http://spencermortensen.com/articles/bezier-circle
//   const a =
//     ang2 === 1.5707963267948966
//       ? 0.551915024494
//       : ang2 === -1.5707963267948966
//       ? -0.551915024494
//       : (4 / 3) * Math.tan(ang2 / 4);

//   const x1 = Math.cos(ang1);
//   const y1 = Math.sin(ang1);
//   const x2 = Math.cos(ang1 + ang2);
//   const y2 = Math.sin(ang1 + ang2);

//   return [
//     {
//       x: x1 - y1 * a,
//       y: y1 + x1 * a,
//     },
//     {
//       x: x2 + y2 * a,
//       y: y2 - x2 * a,
//     },
//     {
//       x: x2,
//       y: y2,
//     },
//   ];
// };

// const vectorAngle = (ux: number, uy: number, vx: number, vy: number) => {
//   const sign = ux * vy - uy * vx < 0 ? -1 : 1;

//   let dot = ux * vx + uy * vy;

//   if (dot > 1) {
//     dot = 1;
//   }

//   if (dot < -1) {
//     dot = -1;
//   }

//   return sign * Math.acos(dot);
// };

// const getArcCenter = (
//   px: any,
//   py: any,
//   cx: any,
//   cy: any,
//   rx: number,
//   ry: number,
//   largeArcFlag: number,
//   sweepFlag: number,
//   sinphi: number,
//   cosphi: number,
//   pxp: number,
//   pyp: number,
// ) => {
//   const rxsq = Math.pow(rx, 2);
//   const rysq = Math.pow(ry, 2);
//   const pxpsq = Math.pow(pxp, 2);
//   const pypsq = Math.pow(pyp, 2);

//   let radicant = rxsq * rysq - rxsq * pypsq - rysq * pxpsq;

//   if (radicant < 0) {
//     radicant = 0;
//   }

//   radicant /= rxsq * pypsq + rysq * pxpsq;
//   radicant = Math.sqrt(radicant) * (largeArcFlag === sweepFlag ? -1 : 1);

//   const centerxp = ((radicant * rx) / ry) * pyp;
//   const centeryp = ((radicant * -ry) / rx) * pxp;

//   const centerx = cosphi * centerxp - sinphi * centeryp + (px + cx) / 2;
//   const centery = sinphi * centerxp + cosphi * centeryp + (py + cy) / 2;

//   const vx1 = (pxp - centerxp) / rx;
//   const vy1 = (pyp - centeryp) / ry;
//   const vx2 = (-pxp - centerxp) / rx;
//   const vy2 = (-pyp - centeryp) / ry;

//   const ang1 = vectorAngle(1, 0, vx1, vy1);
//   let ang2 = vectorAngle(vx1, vy1, vx2, vy2);

//   if (sweepFlag === 0 && ang2 > 0) {
//     ang2 -= TAU;
//   }

//   if (sweepFlag === 1 && ang2 < 0) {
//     ang2 += TAU;
//   }

//   return [centerx, centery, ang1, ang2];
// };

// const arcToBezier = ({ px, py, cx, cy, rx, ry, xAxisRotation = 0, largeArcFlag = 0, sweepFlag = 0 }) => {
//   const curves = [];

//   if (rx === 0 || ry === 0) {
//     return [{ x1: 0, y1: 0, x2: 0, y2: 0, x: cx, y: cy }];
//   }

//   const sinphi = Math.sin((xAxisRotation * TAU) / 360);
//   const cosphi = Math.cos((xAxisRotation * TAU) / 360);

//   const pxp = (cosphi * (px - cx)) / 2 + (sinphi * (py - cy)) / 2;
//   const pyp = (-sinphi * (px - cx)) / 2 + (cosphi * (py - cy)) / 2;

//   if (pxp === 0 && pyp === 0) {
//     return [{ x1: 0, y1: 0, x2: 0, y2: 0, x: cx, y: cy }];
//   }

//   rx = Math.abs(rx);
//   ry = Math.abs(ry);

//   const lambda = Math.pow(pxp, 2) / Math.pow(rx, 2) + Math.pow(pyp, 2) / Math.pow(ry, 2);

//   if (lambda > 1) {
//     rx *= Math.sqrt(lambda);
//     ry *= Math.sqrt(lambda);
//   }

//   let [centerx, centery, ang1, ang2] = getArcCenter(
//     px,
//     py,
//     cx,
//     cy,
//     rx,
//     ry,
//     largeArcFlag,
//     sweepFlag,
//     sinphi,
//     cosphi,
//     pxp,
//     pyp,
//   );

//   // If 'ang2' == 90.0000000001, then `ratio` will evaluate to
//   // 1.0000000001. This causes `segments` to be greater than one, which is an
//   // unecessary split, and adds extra points to the bezier curve. To alleviate
//   // this issue, we round to 1.0 when the ratio is close to 1.0.
//   let ratio = Math.abs(ang2) / (TAU / 4);
//   if (Math.abs(1.0 - ratio) < 0.0000001) {
//     ratio = 1.0;
//   }

//   const segments = Math.max(Math.ceil(ratio), 1);

//   ang2 /= segments;

//   for (let i = 0; i < segments; i++) {
//     curves.push(approxUnitArc(ang1, ang2));
//     ang1 += ang2;
//   }

//   return curves.map((curve) => {
//     const { x: x1, y: y1 } = mapToEllipse(curve[0], rx, ry, cosphi, sinphi, centerx, centery);
//     const { x: x2, y: y2 } = mapToEllipse(curve[1], rx, ry, cosphi, sinphi, centerx, centery);
//     const { x, y } = mapToEllipse(curve[2], rx, ry, cosphi, sinphi, centerx, centery);

//     return { x1, y1, x2, y2, x, y };
//   });
// };

// export function arcToCubic(
//   x1: number,
//   y1: number,
//   rx: number,
//   ry: number,
//   angle: number,
//   LAF: number,
//   SF: number,
//   x2: number,
//   y2: number,
// ) {
//   const curves = arcToBezier({
//     px: x1,
//     py: y1,
//     cx: x2,
//     cy: y2,
//     rx,
//     ry,
//     xAxisRotation: angle,
//     largeArcFlag: LAF,
//     sweepFlag: SF,
//   });

//   return curves.reduce((prev, cur) => {
//     const { x1, y1, x2, y2, x, y } = cur;
//     prev.push(x1, y1, x2, y2, x, y);
//     return prev;
//   }, [] as number[]);
// }
