import { getDomainLengths } from "barycentric";

/**
 * Computes the domain ranges for each axis given a transform object from d3.zoom.
 * The transform object contains scale (k) and translation (x,y) values.
 *
 * This function ensures the domains stay within valid bounds and maintains
 * equal domain lengths across all axes.
 *
 * @param transform - Object containing zoom transform parameters:
 *   - k: scale factor (zoom level)
 *   - x: x-translation
 *   - y: y-translation
 * @returns Tuple of three [start,end] domain ranges for axes A, B, and C
 * @throws Error if zoom level or translation would create invalid domains
 */
export function domainsFromTransform(transform: {
  k: number;
  x: number;
  y: number;
}): [[number, number], [number, number], [number, number]] {
  const { k, x, y } = transform;

  const domainLength = 1 / k;

  if (domainLength > 1) {
    throw new Error(
      `Invalid zoom level: ${k}. Cannot zoom out beyond the original triangle.`,
    );
  }

  const untranslatedDomainStart = (k - 1) / (k * 3);

  const radian = Math.PI / 180;
  const [vA, vB, vC] = [-90, 150, 30].map((d) => [
    Math.cos(d * radian),
    Math.sin(d * radian),
  ]);

  // Solve system of equations to find shifts for each axis
  const det =
    vA[0] * (vB[1] - vC[1]) + vB[0] * (vC[1] - vA[1]) + vC[0] * (vA[1] - vB[1]);

  const shiftA = ((x / k) * (vB[1] - vC[1]) + (y / k) * (vC[0] - vB[0])) / det;
  const shiftB = ((x / k) * (vC[1] - vA[1]) + (y / k) * (vA[0] - vC[0])) / det;
  const shiftC = ((x / k) * (vA[1] - vB[1]) + (y / k) * (vB[0] - vA[0])) / det;

  // Calculate initial domain starts
  const domainAStart = untranslatedDomainStart - shiftA;
  const domainBStart = untranslatedDomainStart - shiftB;
  const domainCStart = untranslatedDomainStart - shiftC;

  const domainAEnd = domainAStart + domainLength;
  const domainBEnd = domainBStart + domainLength;
  const domainCEnd = domainCStart + domainLength;

  // Check if any domain goes outside [0,1] range with some tolerance
  const epsilon = 10e-6;
  const min = 0 - epsilon;
  const max = 1 + epsilon;
  if (domainAStart < min || domainAEnd > max) {
    throw new Error(
      `New domain A exceeds bounds ${[domainAStart, domainAEnd]}`,
    );
  }
  if (domainBStart < min || domainBEnd > max) {
    throw new Error(
      `New domain B exceeds bounds ${[domainBStart, domainBEnd]}`,
    );
  }
  if (domainCStart < min || domainCEnd > max) {
    throw new Error(
      `New domain C exceeds bounds ${[domainCStart, domainCEnd]}`,
    );
  }
  return [
    [domainAStart, domainAEnd],
    [domainBStart, domainBEnd],
    [domainCStart, domainCEnd],
  ];
}

/**
 * Computes the d3.zoom transform parameters that would create the given domain ranges.
 * This is the inverse of domainsFromTransform().
 *
 * Used to sync the zoom and pan state to match specified domain ranges.
 * The returned transform can be passed to d3.zoom.transform() to update the view.
 *
 * @param domains - Array of [start,end] domain ranges for axes A, B, and C
 * @returns Object with zoom transform parameters:
 *   - k: scale factor (zoom level)
 *   - x: x-translation (unscaled by radius)
 *   - y: y-translation (unscaled by radius)
 */
export function transformFromDomains(
  domains: [[number, number], [number, number], [number, number]],
) {
  const [domainA, domainB, domainC] = domains;

  const domainLengths = getDomainLengths(domains);
  const domainLength = [...domainLengths][0];

  const k = 1 / domainLength;

  const untranslatedDomainStart = (k - 1) / (k * 3); // find start value of centered, untranslated domain for this scale

  const shiftA = untranslatedDomainStart - domainA[0];
  const shiftB = untranslatedDomainStart - domainB[0];
  const shiftC = untranslatedDomainStart - domainC[0];

  const radian = Math.PI / 180;
  const [vA, vB, vC] = [-90, 150, 30].map((d) => [
    Math.cos(d * radian),
    Math.sin(d * radian),
  ]);

  const tx = (vA[0] * shiftA + vB[0] * shiftB + vC[0] * shiftC) * k;
  const ty = (vA[1] * shiftA + vB[1] * shiftB + vC[1] * shiftC) * k;

  return { k, x: tx, y: ty };
}
