import { describe, expect, it } from "vitest";
import {
  compactConversationMemoryLayer,
  compactRecentPromptLayerLines,
  compactSystemPromptLayers,
} from "./prompt-layers.js";

describe("prompt layer compaction", () => {
  it("reports the original and final token estimates when no compaction is needed", () => {
    const layers = [
      {
        name: "active-profile",
        value: "abcd".repeat(20),
        kind: "profile",
        priority: 0,
        compactStrategy: "preserve" as const,
      },
    ];

    expect(compactSystemPromptLayers(layers, 100)).toMatchObject({
      budgetTokens: 100,
      finalEstimatedTokens: 20,
      originalEstimatedTokens: 20,
      overBudgetTokens: 0,
      tokensSaved: 0,
    });
  });

  it("reports residual overflow after all compactable layers are exhausted", () => {
    const result = compactSystemPromptLayers(
      [
        {
          name: "active-profile",
          value: "abcd".repeat(40),
          kind: "profile",
          priority: 0,
          compactStrategy: "preserve",
        },
        {
          name: "skills",
          value: "abcd".repeat(20),
          kind: "skills",
          priority: 20,
          compactStrategy: "expire",
        },
      ],
      30,
    );

    expect(result).toMatchObject({
      budgetTokens: 30,
      finalEstimatedTokens: 40,
      originalEstimatedTokens: 60,
      overBudgetTokens: 10,
      tokensSaved: 20,
    });
    expect(result.layers[1]?.value).toBeUndefined();
  });

  it("keeps both head and tail when truncating a layer", () => {
    const result = compactSystemPromptLayers(
      [
        {
          name: "repair",
          value: `first evidence\n${"middle noise ".repeat(40)}latest failure`,
          kind: "repair",
          priority: 10,
          compactStrategy: "truncate",
          maxTokens: 20,
        },
      ],
      20,
    );

    expect(result.layers[0]?.value).toContain("first evidence");
    expect(result.layers[0]?.value).toContain("latest failure");
    expect(result.layers[0]?.value).toContain("[...middle compacted...]");
  });

  it("shrinks truncate layers below their max when the whole prompt is over budget", () => {
    const result = compactSystemPromptLayers(
      [
        {
          name: "contract",
          value: "abcd".repeat(20),
          kind: "contract",
          priority: 0,
          compactStrategy: "preserve",
        },
        {
          name: "memory",
          value: "memory ".repeat(80),
          kind: "memory",
          priority: 10,
          compactStrategy: "truncate",
          maxTokens: 200,
        },
      ],
      40,
    );

    expect(result.finalEstimatedTokens).toBeLessThanOrEqual(40);
    expect(result.layers[1]?.value?.length).toBeLessThan("memory ".repeat(80).length);
  });

  it("does not exceed compact char budget for tiny token targets", () => {
    const result = compactSystemPromptLayers(
      [
        {
          name: "repair",
          value: "a".repeat(1000),
          kind: "repair",
          priority: 10,
          compactStrategy: "truncate",
          maxTokens: 2,
        },
      ],
      2,
    );

    expect(result.layers[0]?.value?.length).toBeLessThanOrEqual(8);
    expect(result.finalEstimatedTokens).toBeLessThanOrEqual(2);
  });

  it("uses layer semantic compactors when present", () => {
    const result = compactSystemPromptLayers(
      [
        {
          name: "evidence",
          value: ["old evidence", "middle evidence", "latest failure code=E_TEST"].join("\n"),
          kind: "evidence",
          priority: 10,
          compactStrategy: "truncate",
          compact: compactRecentPromptLayerLines,
          maxTokens: 10,
        },
      ],
      10,
    );

    expect(result.layers[0]?.value).toContain("latest failure code=E_TEST");
    expect(result.layers[0]?.value).not.toContain("old evidence");
  });

  it("clamps custom compactor output to the target budget", () => {
    const result = compactSystemPromptLayers(
      [
        {
          name: "evidence",
          value: "a".repeat(1000),
          kind: "evidence",
          priority: 10,
          compactStrategy: "truncate",
          compact: () => "b".repeat(1000),
          maxTokens: 5,
        },
      ],
      5,
    );

    expect(result.layers[0]?.value?.length).toBeLessThanOrEqual(20);
    expect(result.finalEstimatedTokens).toBeLessThanOrEqual(5);
  });

  it("preserves high-value conversation memory lines while keeping recent context", () => {
    const value = [
      "Thread working memory:",
      "Current objective: preserve the checkpoint replay contract",
      "Open questions: whether budget fallback can finalize",
      ...Array.from({ length: 20 }, (_, index) => `older detail ${index}`),
      "latest tool outcome: replay repair selected read-file",
    ].join("\n");

    const result = compactConversationMemoryLayer(value, 60);

    expect(result.length).toBeLessThanOrEqual(240);
    expect(result).toContain("Current objective: preserve the checkpoint replay contract");
    expect(result).toContain("Open questions: whether budget fallback can finalize");
    expect(result).toContain("latest tool outcome: replay repair selected read-file");
  });
});
