import { describe, expect, it } from "vitest";
import { DEFAULT_REGEX_LIMITS } from "../execution/request-limits";
import {
  corpusTotalBytes,
  createPastedCorpusDocument,
  readCorpusFiles,
} from "./corpus-input";

describe("corpus input", () => {
  it("adds bounded pasted UTF-8 text with unique names", () => {
    const first = createPastedCorpusDocument("notes.txt", "Grüße", []);
    const second = createPastedCorpusDocument("notes.txt", "again", [first]);

    expect(first).toMatchObject({
      name: "notes.txt",
      bytes: new TextEncoder().encode("Grüße").byteLength,
      source: "pasted-text",
    });
    expect(second.name).toBe("notes (2).txt");
    expect(corpusTotalBytes([first, second])).toBe(first.bytes + second.bytes);
  });

  it("rejects binary, invalid UTF-8 and oversized documents before use", async () => {
    await expect(
      readCorpusFiles(
        [
          new File([new Uint8Array([0, 1, 2])], "sample.bin", {
            type: "application/octet-stream",
          }),
        ],
        [],
      ),
    ).rejects.toThrow(/not a recognized text file/iu);

    await expect(
      readCorpusFiles(
        [
          new File([new Uint8Array([0xc3, 0x28])], "broken.txt", {
            type: "text/plain",
          }),
        ],
        [],
      ),
    ).rejects.toThrow(/not valid UTF-8/iu);

    expect(() =>
      createPastedCorpusDocument(
        "huge.txt",
        "a".repeat(DEFAULT_REGEX_LIMITS.maximumCorpusDocumentBytes + 1),
        [],
      ),
    ).toThrow(/per-document limit/iu);
  });

  it("honours import cancellation and reports file progress", async () => {
    const controller = new AbortController();
    controller.abort();
    await expect(
      readCorpusFiles(
        [new File(["first"], "first.txt", { type: "text/plain" })],
        [],
        controller.signal,
      ),
    ).rejects.toMatchObject({ name: "AbortError" });

    const progress: string[] = [];
    const documents = await readCorpusFiles(
      [
        new File(["one"], "one.txt", { type: "text/plain" }),
        new File(["two"], "two.txt", { type: "text/plain" }),
      ],
      [],
      undefined,
      (completed, total, name) =>
        progress.push(`${completed}/${total}:${name}`),
    );
    expect(documents.map((document) => document.text)).toEqual(["one", "two"]);
    expect(progress).toEqual(["1/2:one.txt", "2/2:two.txt"]);
  });

  it("preserves a UTF-8 BOM so no-op apply can round-trip the file", async () => {
    const [document] = await readCorpusFiles(
      [
        new File([new Uint8Array([0xef, 0xbb, 0xbf]), "content"], "bom.txt", {
          type: "text/plain",
        }),
      ],
      [],
    );
    expect(document?.text).toBe("\ufeffcontent");
    expect(new TextEncoder().encode(document?.text).byteLength).toBe(10);
  });

  it("preflights aggregate bytes and document count without reading input", () => {
    const atAggregateLimit = [
      {
        id: "existing",
        name: "existing.txt",
        text: "",
        bytes: DEFAULT_REGEX_LIMITS.corpusHardBytes,
        source: "file" as const,
      },
    ];
    expect(() =>
      createPastedCorpusDocument("one-more.txt", "x", atAggregateLimit),
    ).toThrow(/aggregate limit/iu);

    const atDocumentLimit = Array.from(
      { length: DEFAULT_REGEX_LIMITS.maximumCorpusDocuments },
      (_, index) => ({
        id: `existing-${index}`,
        name: `${index}.txt`,
        text: "",
        bytes: 0,
        source: "file" as const,
      }),
    );
    expect(() =>
      createPastedCorpusDocument("one-more.txt", "", atDocumentLimit),
    ).toThrow(/at most 256 documents/iu);
  });
});
