import { describe, expect, it } from "vitest";

import { extractUcfsForReact, extractUcfsForReactStreaming } from "../react-adapter.js";
import type { UniversalComponentFact } from "../types.js";

const sourceCommit = "0123456789abcdef0123456789abcdef01234567";

// Enough files to cross the default yield boundary (25) several times, so the
// streaming path actually exercises its cooperative yielding.
function manyComponents(count: number) {
  return Array.from({ length: count }, (_, i) => ({
    filePath: `/repo/Comp${i}.tsx`,
    source: `export function Comp${i}(props: { label: string }) {
      return <div className="x">{props.label}</div>;
    }`,
  }));
}

// Stable projection that ignores per-run fields (capturedAt) so two runs can
// be compared for extraction equivalence.
function shape(facts: UniversalComponentFact[]) {
  return facts
    .map((fact) => ({
      componentName: fact.componentName,
      filePath: fact.filePath,
      definitionKind: fact.definitionKind,
      propNames: fact.props.map((prop) => prop.name).sort(),
    }))
    .sort((a, b) => a.componentName.localeCompare(b.componentName));
}

describe("extractUcfsForReactStreaming", () => {
  it("produces identical facts to the synchronous extractor", async () => {
    const inlineSources = manyComponents(60);

    const sync = extractUcfsForReact({ inlineSources, sourceCommit, cwd: "/repo" });
    const streamed = await extractUcfsForReactStreaming({
      inlineSources,
      sourceCommit,
      cwd: "/repo",
    });

    expect(streamed).toHaveLength(sync.length);
    expect(shape(streamed)).toEqual(shape(sync));
  });

  it("reports progress and yields across the file set", async () => {
    const inlineSources = manyComponents(60);
    const ticks: Array<{ filesProcessed: number; totalFiles: number }> = [];

    await extractUcfsForReactStreaming({
      inlineSources,
      sourceCommit,
      cwd: "/repo",
      yieldEveryNFiles: 10,
      onProgress: ({ filesProcessed, totalFiles }) => ticks.push({ filesProcessed, totalFiles }),
    });

    // Multiple ticks => the loop yielded between batches rather than running
    // to completion in one synchronous block.
    expect(ticks.length).toBeGreaterThan(1);
    const last = ticks.at(-1)!;
    expect(last.filesProcessed).toBe(last.totalFiles);
    // Progress is monotonic and never overshoots the total.
    for (const tick of ticks) {
      expect(tick.filesProcessed).toBeLessThanOrEqual(tick.totalFiles);
    }
  });

  it("returns an empty array for no sources without invoking progress", async () => {
    let called = false;
    const result = await extractUcfsForReactStreaming({
      inlineSources: [],
      sourceCommit,
      cwd: "/repo",
      onProgress: () => {
        called = true;
      },
    });
    expect(result).toEqual([]);
    expect(called).toBe(false);
  });
});
