export function gcd(a: number, b: number): number {
  a = Math.abs(a);
  b = Math.abs(b);
  while (b) {
    [a, b] = [b, a % b];
  }
  return a;
}

export function lcm(a: number, b: number): number {
  return Math.abs(a * b) / gcd(a, b);
}

/**
 * An immutable fraction. Sign is kept on the numerator; arithmetic returns a new
 * Fraction and does NOT auto-simplify — call `.simplified()` when you want lowest
 * terms (this lets teaching views show the un-reduced working). Prefer this over
 * passing `(num, den)` pairs around: positional pairs are easy to mis-order.
 */
export class Fraction {
  readonly n: number;
  readonly d: number;

  constructor(n: number, d: number) {
    // Canonical sign: keep it on the numerator. A zero denominator is tolerated
    // (kept as-is) so divide-by-zero in a lesson renders rather than throwing.
    if (d < 0) {
      n = -n;
      d = -d;
    }
    this.n = n;
    this.d = d;
  }

  static of(n: number, d: number): Fraction {
    return new Fraction(n, d);
  }

  get isZero(): boolean {
    return this.n === 0;
  }

  plus(o: Fraction): Fraction {
    return new Fraction(this.n * o.d + o.n * this.d, this.d * o.d);
  }

  minus(o: Fraction): Fraction {
    return new Fraction(this.n * o.d - o.n * this.d, this.d * o.d);
  }

  times(o: Fraction): Fraction {
    return new Fraction(this.n * o.n, this.d * o.d);
  }

  dividedBy(o: Fraction): Fraction {
    return new Fraction(this.n * o.d, this.d * o.n);
  }

  simplified(): Fraction {
    if (this.d === 0) return this;
    if (this.n === 0) return new Fraction(0, 1);
    const g = gcd(Math.abs(this.n), Math.abs(this.d));
    return new Fraction(this.n / g, this.d / g);
  }

  get isSimplified(): boolean {
    return gcd(Math.abs(this.n), Math.abs(this.d)) === 1;
  }

  /** Value equality (cross-multiplication), independent of the written form. */
  equals(o: Fraction): boolean {
    return this.n * o.d === o.n * this.d;
  }

  toDecimal(): number {
    return this.n / this.d;
  }

  /** [whole, remainderNumerator, denominator] */
  toMixed(): [number, number, number] {
    const whole = Math.trunc(this.n / this.d);
    const remainder = Math.abs(this.n % this.d);
    return [whole, remainder, this.d];
  }

  toKatex(): string {
    if (this.d === 1) return `${this.n}`;
    return `\\frac{${this.n}}{${this.d}}`;
  }

  /** Like toKatex, but writes negatives with the sign in front: -\frac{3}{4} rather than \frac{-3}{4}. */
  toSignedKatex(): string {
    if (this.d === 1) return `${this.n}`;
    const sign = this.n < 0 ? "-" : "";
    return `${sign}\\frac{${Math.abs(this.n)}}{${this.d}}`;
  }

  toString(): string {
    return this.d === 1 ? `${this.n}` : `${this.n}/${this.d}`;
  }
}

/**
 * Convenience KaTeX formatter for raw (num, den) pairs. Handy in the step-by-step
 * lesson views, which build up strings from loose numerators/denominators rather
 * than Fraction objects. For arithmetic, use the Fraction class instead.
 */
export function toKatex(n: number, d: number): string {
  return new Fraction(n, d).toKatex();
}
