import { describe, expect, it } from 'vitest';
import { dist, elev, hrZone, percentOf, powerZone, speed, toMass, fromMass } from './format';

/**
 * The display layer is where a rider's preference is applied and nowhere else,
 * so these tests exist to pin two properties: the conversions are exact, and a
 * zone is never invented from a reference the rider has not given.
 */
describe('units', () => {
  it('leaves metric alone and converts imperial exactly', () => {
    expect(dist(42195, 'metric', 2)).toBe('42.20');
    expect(dist(1609.344, 'imperial', 3)).toBe('1.000');
    expect(elev(1000, 'metric')).toBe('1000');
    expect(elev(304.8, 'imperial')).toBe('1000');
    expect(speed(32.18688, 'imperial')).toBe('20.0');
  });

  it('round-trips a mass through the form and back', () => {
    const kg = 72.5;
    expect(fromMass(toMass(kg, 'imperial'), 'imperial')).toBeCloseTo(kg, 6);
    expect(toMass(kg, 'metric')).toBe(kg);
  });

  it('says nothing rather than NaN when there is no value', () => {
    expect(dist(null, 'metric')).toBe('—');
    expect(elev(undefined, 'imperial')).toBe('—');
  });
});

describe('effort zones', () => {
  it('places power on the Coggan boundaries', () => {
    const ftp = 250;
    expect(powerZone(100, ftp)?.short).toBe('Z1'); // 40%
    expect(powerZone(175, ftp)?.short).toBe('Z2'); // 70%
    expect(powerZone(220, ftp)?.short).toBe('Z3'); // 88%
    expect(powerZone(250, ftp)?.short).toBe('Z4'); // 100%
    expect(powerZone(290, ftp)?.short).toBe('Z5'); // 116%
    expect(powerZone(350, ftp)?.short).toBe('Z6'); // 140%
    expect(powerZone(500, ftp)?.short).toBe('Z7'); // 200%
  });

  it('puts a boundary value in the lower zone', () => {
    // 55% of FTP is the top of zone 1, not the bottom of zone 2.
    expect(powerZone(137.5, 250)?.short).toBe('Z1');
  });

  /** The whole contract of `ftpW: 0` — no reference, no colour. */
  it('refuses to invent a zone without a reference', () => {
    expect(powerZone(300, 0)).toBeNull();
    expect(hrZone(150, 0)).toBeNull();
    expect(percentOf(300, 0)).toBeNull();
  });

  it('does not colour a rider who is not pedalling', () => {
    expect(powerZone(0, 250)).toBeNull();
    expect(powerZone(null, 250)).toBeNull();
  });

  it('places heart rate as a fraction of maximum', () => {
    expect(hrZone(100, 190)?.short).toBe('Z1'); // 53%
    expect(hrZone(125, 190)?.short).toBe('Z2'); // 66%
    expect(hrZone(150, 190)?.short).toBe('Z3'); // 79%
    expect(hrZone(170, 190)?.short).toBe('Z4'); // 89%
    expect(hrZone(185, 190)?.short).toBe('Z5'); // 97%
  });

  it('reports the percentage the zone came from', () => {
    expect(percentOf(275, 250)).toBe('110% of FTP');
  });
});
