import { describe, it, expect } from 'vitest';
import {
  barProgress,
  statusIndexFor,
  elapsedTenths,
  BAR_CEILING,
  STATUS_SCHEDULE_SECONDS,
  STATUS_COUNT,
} from './processingProgress';

const s = (seconds: number) => seconds * 1000;

describe('barProgress', () => {
  it('starts at zero', () => {
    expect(barProgress(0)).toBe(0);
  });

  it('increases strictly for every second up to 60s', () => {
    for (let t = 1; t <= 60; t++) {
      expect(barProgress(s(t))).toBeGreaterThan(barProgress(s(t - 1)));
    }
  });

  // Regression test for the competitor defect: their bar pegs at 100% and
  // freezes when generation outruns the animation. Ours must always have
  // somewhere left to go, across every duration that can realistically
  // occur, the worst generation ever measured is 48s.
  it('never reaches the ceiling across any realistic duration', () => {
    for (const t of [10, 35, 48, 120, 300, 514]) {
      expect(barProgress(s(t))).toBeLessThan(BAR_CEILING);
    }
  });

  // Documents a known float64 limit rather than hiding it: past ~514s the
  // exponential underflows and the fill lands exactly on the ceiling. That
  // is an order of magnitude beyond any observed generation, and even there
  // the bar reads 92%, it never presents as complete, which is the
  // property that actually matters to the shopper.
  it('saturates at the ceiling only far beyond any real generation', () => {
    expect(barProgress(s(514))).toBeLessThan(BAR_CEILING);
    expect(barProgress(s(600))).toBe(BAR_CEILING);
  });

  it('is roughly half full at the measured p50 of 11s', () => {
    expect(barProgress(s(11))).toBeCloseTo(0.5, 2);
  });

  it('is still visibly climbing at the measured p90 of 35s', () => {
    expect(barProgress(s(35))).toBeCloseTo(0.844, 2);
  });

  it('is still climbing at the worst observed duration of 48s', () => {
    expect(barProgress(s(48))).toBeCloseTo(0.89, 2);
  });

  it('clamps a negative elapsed to zero', () => {
    expect(barProgress(-5000)).toBe(0);
  });
});

describe('statusIndexFor', () => {
  it('starts on the first string', () => {
    expect(statusIndexFor(0)).toBe(0);
  });

  it('clamps a negative elapsed to the first string', () => {
    expect(statusIndexFor(-5000)).toBe(0);
  });

  it('advances exactly on each scheduled boundary', () => {
    STATUS_SCHEDULE_SECONDS.forEach((boundary, index) => {
      expect(statusIndexFor(s(boundary))).toBe(index);
      if (boundary > 0) {
        expect(statusIndexFor(s(boundary) - 1)).toBe(index - 1);
      }
    });
  });

  // Regression test: a repeating carousel is what exposes the pacing as
  // theatre. The final string must hold indefinitely instead of wrapping.
  it('never loops and never exceeds the last string, out to 300s', () => {
    let previous = 0;
    for (let t = 0; t <= 300; t++) {
      const index = statusIndexFor(s(t));
      expect(index).toBeGreaterThanOrEqual(previous);
      expect(index).toBeLessThanOrEqual(STATUS_COUNT - 1);
      previous = index;
    }
    expect(statusIndexFor(s(300))).toBe(STATUS_COUNT - 1);
  });
});

describe('elapsedTenths', () => {
  it('truncates to one decimal place', () => {
    expect(elapsedTenths(12449)).toBe(12.4);
    expect(elapsedTenths(0)).toBe(0);
  });

  it('clamps a negative elapsed to zero', () => {
    expect(elapsedTenths(-100)).toBe(0);
  });
});
