import { describe, it, expect } from 'vitest';
import { topologicalSortPhases, type PhaseForSort, type DependencyEdge } from '@codewalk-district/shared';

function mkPhase(id: string, createdAt: string | Date): PhaseForSort {
  return { id, createdAt };
}

describe('topologicalSortPhases', () => {
  it('should return empty array for empty input', () => {
    expect(topologicalSortPhases([], [])).toEqual([]);
  });

  it('should return phases in createdAt order when no edges', () => {
    const phases = [
      mkPhase('c', '2026-01-03'),
      mkPhase('a', '2026-01-01'),
      mkPhase('b', '2026-01-02'),
    ];
    const result = topologicalSortPhases(phases, []);
    expect(result.map((p) => p.id)).toEqual(['a', 'b', 'c']);
  });

  it('should sort linear chain correctly', () => {
    // A -> B -> C (B depends on A, C depends on B)
    const phases = [
      mkPhase('a', '2026-01-01'),
      mkPhase('b', '2026-01-02'),
      mkPhase('c', '2026-01-03'),
    ];
    const edges: DependencyEdge[] = [
      { phaseId: 'b', dependsOnPhaseId: 'a' },
      { phaseId: 'c', dependsOnPhaseId: 'b' },
    ];
    const result = topologicalSortPhases(phases, edges);
    expect(result.map((p) => p.id)).toEqual(['a', 'b', 'c']);
  });

  it('should handle diamond dependency', () => {
    //   A
    //  / \
    // B   C
    //  \ /
    //   D
    const phases = [
      mkPhase('a', '2026-01-01'),
      mkPhase('b', '2026-01-02'),
      mkPhase('c', '2026-01-03'),
      mkPhase('d', '2026-01-04'),
    ];
    const edges: DependencyEdge[] = [
      { phaseId: 'b', dependsOnPhaseId: 'a' },
      { phaseId: 'c', dependsOnPhaseId: 'a' },
      { phaseId: 'd', dependsOnPhaseId: 'b' },
      { phaseId: 'd', dependsOnPhaseId: 'c' },
    ];
    const result = topologicalSortPhases(phases, edges);
    // A must come first, D must come last, B before C by createdAt
    expect(result[0].id).toBe('a');
    expect(result[3].id).toBe('d');
    expect(result.map((p) => p.id)).toEqual(['a', 'b', 'c', 'd']);
  });

  it('should use createdAt as deterministic tiebreaker', () => {
    // Three independent phases — should sort by createdAt
    const phases = [
      mkPhase('z', '2026-01-03'),
      mkPhase('y', '2026-01-01'),
      mkPhase('x', '2026-01-02'),
    ];
    const result = topologicalSortPhases(phases, []);
    expect(result.map((p) => p.id)).toEqual(['y', 'x', 'z']);
  });

  it('should handle cycle gracefully by appending cycled nodes', () => {
    // A -> B -> A (cycle), C is independent
    const phases = [
      mkPhase('a', '2026-01-01'),
      mkPhase('b', '2026-01-02'),
      mkPhase('c', '2026-01-03'),
    ];
    const edges: DependencyEdge[] = [
      { phaseId: 'b', dependsOnPhaseId: 'a' },
      { phaseId: 'a', dependsOnPhaseId: 'b' },
    ];
    const result = topologicalSortPhases(phases, edges);
    // C has no deps so it comes first, then A and B appended (cycle)
    expect(result[0].id).toBe('c');
    expect(result.length).toBe(3);
    // A and B are appended in createdAt order
    expect(result[1].id).toBe('a');
    expect(result[2].id).toBe('b');
  });

  it('should ignore edges referencing non-existent phases', () => {
    const phases = [
      mkPhase('a', '2026-01-01'),
      mkPhase('b', '2026-01-02'),
    ];
    const edges: DependencyEdge[] = [
      { phaseId: 'b', dependsOnPhaseId: 'nonexistent' },
    ];
    const result = topologicalSortPhases(phases, edges);
    // Edge is ignored, both treated as independent
    expect(result.map((p) => p.id)).toEqual(['a', 'b']);
  });

  it('should handle single phase with no edges', () => {
    const phases = [mkPhase('only', '2026-01-01')];
    const result = topologicalSortPhases(phases, []);
    expect(result.map((p) => p.id)).toEqual(['only']);
  });

  it('should work with Date objects', () => {
    const phases = [
      mkPhase('b', new Date('2026-01-02')),
      mkPhase('a', new Date('2026-01-01')),
    ];
    const edges: DependencyEdge[] = [
      { phaseId: 'b', dependsOnPhaseId: 'a' },
    ];
    const result = topologicalSortPhases(phases, edges);
    expect(result.map((p) => p.id)).toEqual(['a', 'b']);
  });

  it('should preserve extra properties on phase objects', () => {
    const phases = [
      { id: 'a', createdAt: '2026-01-01', name: 'Alpha', status: 'pending' },
      { id: 'b', createdAt: '2026-01-02', name: 'Beta', status: 'active' },
    ];
    const result = topologicalSortPhases(phases, []);
    expect(result[0].name).toBe('Alpha');
    expect(result[1].name).toBe('Beta');
  });
});
