import { describe, it, expect } from "vitest";
import {
  applyBulkDocType,
  selectAllUnclassified,
  getClassifiedCount,
  getUnclassifiedIds,
  uploadedCountForType,
} from "./classifyPhase";
import type { ClassifyEntry } from "./classifyPhase";

function makeEntry(overrides: Partial<ClassifyEntry> = {}): ClassifyEntry {
  return {
    id: "1",
    docType: "",
    status: "done",
    ...overrides,
  };
}

// ── applyBulkDocType ─────────────────────────────────────────────────────────

describe("applyBulkDocType", () => {
  it("applies docType to selected entries", () => {
    const entries = [makeEntry({ id: "a" }), makeEntry({ id: "b" })];
    const result = applyBulkDocType(entries, new Set(["a"]), "pre_photo");
    expect(result.find((e) => e.id === "a")?.docType).toBe("pre_photo");
  });

  it("leaves unselected entries unchanged", () => {
    const entries = [makeEntry({ id: "a" }), makeEntry({ id: "b", docType: "post_photo" })];
    const result = applyBulkDocType(entries, new Set(["a"]), "pre_photo");
    expect(result.find((e) => e.id === "b")?.docType).toBe("post_photo");
  });

  it("overwrites an existing docType on selected entry", () => {
    const entries = [makeEntry({ id: "a", docType: "mid_photo" })];
    const result = applyBulkDocType(entries, new Set(["a"]), "pre_photo");
    expect(result.find((e) => e.id === "a")?.docType).toBe("pre_photo");
  });

  it("skips non-done entries even if selected", () => {
    const entries = [makeEntry({ id: "a", status: "error" })];
    const result = applyBulkDocType(entries, new Set(["a"]), "pre_photo");
    expect(result.find((e) => e.id === "a")?.docType).toBe("");
  });

  it("returns same length array", () => {
    const entries = [makeEntry({ id: "a" }), makeEntry({ id: "b" })];
    const result = applyBulkDocType(entries, new Set(["a", "b"]), "pre_photo");
    expect(result).toHaveLength(2);
  });
});

// ── selectAllUnclassified ────────────────────────────────────────────────────

describe("selectAllUnclassified", () => {
  it("returns IDs of done entries with no docType", () => {
    const entries = [makeEntry({ id: "a" }), makeEntry({ id: "b" })];
    const ids = selectAllUnclassified(entries);
    expect(ids).toEqual(new Set(["a", "b"]));
  });

  it("excludes already-classified entries", () => {
    const entries = [
      makeEntry({ id: "a", docType: "pre_photo" }),
      makeEntry({ id: "b" }),
    ];
    const ids = selectAllUnclassified(entries);
    expect(ids).toEqual(new Set(["b"]));
  });

  it("excludes non-done entries", () => {
    const entries = [
      makeEntry({ id: "a", status: "error" }),
      makeEntry({ id: "b" }),
    ];
    const ids = selectAllUnclassified(entries);
    expect(ids).toEqual(new Set(["b"]));
  });

  it("returns empty set when all classified", () => {
    const entries = [makeEntry({ id: "a", docType: "pre_photo" })];
    expect(selectAllUnclassified(entries)).toEqual(new Set());
  });
});

// ── getClassifiedCount ───────────────────────────────────────────────────────

describe("getClassifiedCount", () => {
  it("counts done entries with a docType", () => {
    const entries = [
      makeEntry({ id: "a", docType: "pre_photo" }),
      makeEntry({ id: "b" }),
    ];
    expect(getClassifiedCount(entries)).toBe(1);
  });

  it("returns 0 when none classified", () => {
    expect(getClassifiedCount([makeEntry(), makeEntry({ id: "2" })])).toBe(0);
  });

  it("excludes non-done entries from count", () => {
    const entries = [makeEntry({ id: "a", status: "error", docType: "pre_photo" })];
    expect(getClassifiedCount(entries)).toBe(0);
  });
});

// ── getUnclassifiedIds ───────────────────────────────────────────────────────

describe("getUnclassifiedIds", () => {
  it("returns IDs of done entries with empty docType", () => {
    const entries = [
      makeEntry({ id: "a" }),
      makeEntry({ id: "b", docType: "pre_photo" }),
    ];
    expect(getUnclassifiedIds(entries)).toEqual(["a"]);
  });

  it("returns empty array when all classified", () => {
    const entries = [makeEntry({ id: "a", docType: "pre_photo" })];
    expect(getUnclassifiedIds(entries)).toEqual([]);
  });
});

// ── uploadedCountForType ─────────────────────────────────────────────────────

describe("uploadedCountForType", () => {
  it("returns 0 when docType not in uploaded list", () => {
    expect(uploadedCountForType(["post_photo"], "pre_photo")).toBe(0);
  });

  it("counts occurrences of docType", () => {
    expect(uploadedCountForType(["pre_photo", "pre_photo", "post_photo"], "pre_photo")).toBe(2);
  });

  it("returns 0 for empty list", () => {
    expect(uploadedCountForType([], "pre_photo")).toBe(0);
  });

  it("exact match only — partial strings don't count", () => {
    expect(uploadedCountForType(["pre_photo_extra"], "pre_photo")).toBe(0);
  });
});
