import { describe, it, expect } from "vitest";
import { gcd, lcm, Fraction } from "./fractions";

const f = (n: number, d: number) => new Fraction(n, d);

describe("gcd / lcm", () => {
  it("computes gcd", () => {
    expect(gcd(12, 8)).toBe(4);
    expect(gcd(7, 3)).toBe(1);
    expect(gcd(0, 5)).toBe(5);
    expect(gcd(-12, 8)).toBe(4);
  });

  it("computes lcm", () => {
    expect(lcm(4, 6)).toBe(12);
    expect(lcm(3, 5)).toBe(15);
  });
});

describe("Fraction sign + simplify", () => {
  it("keeps the sign on the numerator", () => {
    expect([f(2, -4).n, f(2, -4).d]).toEqual([-2, 4]);
    expect([f(-2, -4).n, f(-2, -4).d]).toEqual([2, 4]);
  });

  it("reduces to lowest terms", () => {
    const check = (n: number, d: number, en: number, ed: number) => {
      const s = f(n, d).simplified();
      expect([s.n, s.d]).toEqual([en, ed]);
    };
    check(5, 10, 1, 2);
    check(8, 12, 2, 3);
    check(3, 1, 3, 1);
    check(2, -4, -1, 2);
    check(-2, -4, 1, 2);
    check(0, 5, 0, 1);
  });

  it("reports whether it is already simplified", () => {
    expect(f(1, 2).isSimplified).toBe(true);
    expect(f(5, 10).isSimplified).toBe(false);
  });
});

describe("Fraction arithmetic (raw, then .simplified())", () => {
  const s = (fr: Fraction) => [fr.simplified().n, fr.simplified().d];

  it("adds", () => {
    expect(s(f(1, 2).plus(f(1, 2)))).toEqual([1, 1]);
    expect(s(f(2, 10).plus(f(3, 10)))).toEqual([1, 2]);
    expect(s(f(1, 3).plus(f(1, 4)))).toEqual([7, 12]);
  });

  it("subtracts", () => {
    expect(s(f(7, 8).minus(f(6, 8)))).toEqual([1, 8]);
    expect(s(f(1, 2).minus(f(1, 2)))).toEqual([0, 1]);
  });

  it("multiplies", () => {
    expect(s(f(2, 3).times(f(3, 4)))).toEqual([1, 2]);
    expect(s(f(1, 2).times(f(1, 2)))).toEqual([1, 4]);
  });

  it("divides (invert and multiply)", () => {
    expect(s(f(1, 2).dividedBy(f(1, 4)))).toEqual([2, 1]);
    expect(s(f(3, 4).dividedBy(f(1, 2)))).toEqual([3, 2]);
  });

  it("handles negative fractions with the sign rules", () => {
    expect(s(f(-1, 2).plus(f(3, 4)))).toEqual([1, 4]); // -1/2 + 3/4
    expect(s(f(1, 2).minus(f(-3, 4)))).toEqual([5, 4]); // subtracting a negative adds
    expect(s(f(-2, 3).times(f(1, 4)))).toEqual([-1, 6]); // different signs -> negative
    expect(s(f(-2, 3).times(f(-3, 5)))).toEqual([2, 5]); // same signs -> positive
    expect(s(f(-1, 2).dividedBy(f(1, 4)))).toEqual([-2, 1]); // -1/2 ÷ 1/4
  });
});

describe("Fraction presentation + equality", () => {
  it("renders KaTeX, collapsing whole numbers", () => {
    expect(f(1, 2).toKatex()).toBe("\\frac{1}{2}");
    expect(f(3, 1).toKatex()).toBe("3");
  });

  it("renders signed KaTeX with the sign in front", () => {
    expect(f(-3, 4).toSignedKatex()).toBe("-\\frac{3}{4}");
    expect(f(3, 4).toSignedKatex()).toBe("\\frac{3}{4}");
    expect(f(-5, 1).toSignedKatex()).toBe("-5");
  });

  it("compares by value, not written form", () => {
    expect(f(5, 10).equals(f(1, 2))).toBe(true);
    expect(f(1, 2).equals(f(1, 3))).toBe(false);
  });
});
