/**
 * Unit tests for rank.ts — fractional-indexing helpers (D-13).
 *
 * Pure function tests, no DB needed.
 * Run: pnpm --filter @familysync/api exec vitest run tests/lib/rank.test.ts
 */

import { describe, it, expect } from 'vitest';
import { rankForAppend, rankBetween } from '../../src/lib/rank.js';

describe('rankForAppend', () => {
  it('returns "a0" when list is empty (no existing rank)', () => {
    const rank = rankForAppend(null);
    expect(rank).toBe('a0');
  });

  it('returns a rank string that sorts AFTER the given last rank', () => {
    const lastRank = 'a0';
    const newRank = rankForAppend(lastRank);
    expect(newRank > lastRank).toBe(true);
  });

  it('multiple appends produce strictly increasing ranks', () => {
    let last: string | null = null;
    const ranks: string[] = [];
    for (let i = 0; i < 5; i++) {
      const r = rankForAppend(last);
      ranks.push(r);
      last = r;
    }
    // Each successive rank must be greater than the previous
    for (let i = 1; i < ranks.length; i++) {
      expect(ranks[i] > ranks[i - 1]).toBe(true);
    }
  });
});

describe('rankBetween', () => {
  it('returns "a0" when both prev and next are null (empty list)', () => {
    const rank = rankBetween(null, null);
    expect(rank).toBe('a0');
  });

  it('returns a rank that sorts between two existing ranks', () => {
    const first = rankForAppend(null); // 'a0'
    const second = rankForAppend(first); // 'a1'
    const between = rankBetween(first, second);
    expect(between > first).toBe(true);
    expect(between < second).toBe(true);
  });

  it('returns a rank that sorts AFTER prev when next is null', () => {
    const prev = 'a0';
    const rank = rankBetween(prev, null);
    expect(rank > prev).toBe(true);
  });

  it('returns a rank that sorts BEFORE next when prev is null', () => {
    const next = 'a1';
    const rank = rankBetween(null, next);
    expect(rank < next).toBe(true);
  });

  it('produces lexicographically stable ordering across multiple insertions', () => {
    // Simulate inserting between 'a0' and 'a1' repeatedly
    const a = 'a0';
    const b = 'a1';
    const c = rankBetween(a, b);
    const d = rankBetween(a, c);
    const e = rankBetween(c, b);

    // All four ranks should be orderable
    expect(a < d).toBe(true);
    expect(d < c).toBe(true);
    expect(c < e).toBe(true);
    expect(e < b).toBe(true);
  });

  /**
   * Precision regression test — LIST-03, Pitfall 2.
   *
   * Repeated "zipper" inserts (always between the first two items) must produce
   * strictly increasing, unique, non-empty rank strings across many iterations.
   * Float-based approaches would exhaust precision around iteration 52; the
   * fractional-indexing string approach degrades gracefully by growing string
   * length instead.
   */
  it('repeated mid-point inserts produce unique strictly-increasing ranks over 100 iterations (precision — Pitfall 2)', () => {
    const ranks: string[] = [rankBetween(null, null), rankBetween(null, null)];
    // Set up: two items with known ranks
    ranks[0] = rankBetween(null, null); // 'a0'
    ranks[1] = rankBetween(ranks[0], null); // 'a1'

    // Insert 100 times between the first item and the second item
    // This is the worst-case "zipper" pattern — always inserting at the same gap
    for (let i = 0; i < 100; i++) {
      const newRank = rankBetween(ranks[0], ranks[1]);
      // Must be strictly between
      expect(newRank > ranks[0]).toBe(true);
      expect(newRank < ranks[1]).toBe(true);
      // Must be a non-empty string
      expect(newRank.length).toBeGreaterThan(0);
      // Must be unique (not equal to any existing rank)
      expect(ranks).not.toContain(newRank);
      // New item goes at index 1 (after first, before old second) — shift old items
      ranks.splice(1, 0, newRank);
    }

    // Verify the final list is fully sorted ASC
    for (let i = 1; i < ranks.length; i++) {
      expect(ranks[i] > ranks[i - 1]).toBe(true);
    }
  });

  it('rank between two neighbors is strictly between them (D-13 reorder contract)', () => {
    const prev = 'a0';
    const next = 'a3';
    const middle = rankBetween(prev, next);
    expect(middle > prev).toBe(true);
    expect(middle < next).toBe(true);
  });
});
