import { describe, expect, it } from "vitest";
import { type LabelEntry, SessionManager } from "../../src/core/session-manager.js";

describe("SessionManager labels", () => {
	it("sets and gets labels", () => {
		const session = SessionManager.inMemory();

		const msgId = session.appendMessage({ role: "user", content: "hello", timestamp: 1 });

		// No label initially
		expect(session.getLabel(msgId)).toBeUndefined();

		// Set a label
		const labelId = session.appendLabelChange(msgId, "checkpoint");
		expect(session.getLabel(msgId)).toBe("checkpoint");

		// Label entry should be in entries
		const entries = session.getEntries();
		const labelEntry = entries.find((e) => e.type === "label") as LabelEntry;
		expect(labelEntry).toBeDefined();
		expect(labelEntry.id).toBe(labelId);
		expect(labelEntry.targetId).toBe(msgId);
		expect(labelEntry.label).toBe("checkpoint");
	});

	it("clears labels with undefined", () => {
		const session = SessionManager.inMemory();

		const msgId = session.appendMessage({ role: "user", content: "hello", timestamp: 1 });

		session.appendLabelChange(msgId, "checkpoint");
		expect(session.getLabel(msgId)).toBe("checkpoint");

		// Clear the label
		session.appendLabelChange(msgId, undefined);
		expect(session.getLabel(msgId)).toBeUndefined();
	});

	it("last label wins", () => {
		const session = SessionManager.inMemory();

		const msgId = session.appendMessage({ role: "user", content: "hello", timestamp: 1 });

		session.appendLabelChange(msgId, "first");
		session.appendLabelChange(msgId, "second");
		session.appendLabelChange(msgId, "third");

		expect(session.getLabel(msgId)).toBe("third");
	});

	it("labels are included in tree nodes", () => {
		const session = SessionManager.inMemory();

		const msg1Id = session.appendMessage({ role: "user", content: "hello", timestamp: 1 });
		const msg2Id = session.appendMessage({
			role: "assistant",
			content: [{ type: "text", text: "hi" }],
			api: "anthropic-messages",
			provider: "anthropic",
			model: "test",
			usage: {
				input: 1,
				output: 1,
				cacheRead: 0,
				cacheWrite: 0,
				totalTokens: 2,
				cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
			},
			stopReason: "stop",
			timestamp: 2,
		});

		session.appendLabelChange(msg1Id, "start");
		session.appendLabelChange(msg2Id, "response");

		const tree = session.getTree();

		// Find the message nodes (skip label entries)
		const msg1Node = tree.find((n) => n.entry.id === msg1Id);
		expect(msg1Node?.label).toBe("start");

		// msg2 is a child of msg1
		const msg2Node = msg1Node?.children.find((n) => n.entry.id === msg2Id);
		expect(msg2Node?.label).toBe("response");
	});

	it("labels are preserved in createBranchedSession", () => {
		const session = SessionManager.inMemory();

		const msg1Id = session.appendMessage({ role: "user", content: "hello", timestamp: 1 });
		const msg2Id = session.appendMessage({
			role: "assistant",
			content: [{ type: "text", text: "hi" }],
			api: "anthropic-messages",
			provider: "anthropic",
			model: "test",
			usage: {
				input: 1,
				output: 1,
				cacheRead: 0,
				cacheWrite: 0,
				totalTokens: 2,
				cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
			},
			stopReason: "stop",
			timestamp: 2,
		});

		session.appendLabelChange(msg1Id, "important");
		session.appendLabelChange(msg2Id, "also-important");

		// Branch from msg2 (in-memory mode returns null, but updates internal state)
		session.createBranchedSession(msg2Id);

		// Labels should be preserved
		expect(session.getLabel(msg1Id)).toBe("important");
		expect(session.getLabel(msg2Id)).toBe("also-important");

		// New label entries should exist
		const entries = session.getEntries();
		const labelEntries = entries.filter((e) => e.type === "label") as LabelEntry[];
		expect(labelEntries).toHaveLength(2);
	});

	it("labels not on path are not preserved in createBranchedSession", () => {
		const session = SessionManager.inMemory();

		const msg1Id = session.appendMessage({ role: "user", content: "hello", timestamp: 1 });
		const msg2Id = session.appendMessage({
			role: "assistant",
			content: [{ type: "text", text: "hi" }],
			api: "anthropic-messages",
			provider: "anthropic",
			model: "test",
			usage: {
				input: 1,
				output: 1,
				cacheRead: 0,
				cacheWrite: 0,
				totalTokens: 2,
				cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
			},
			stopReason: "stop",
			timestamp: 2,
		});
		const msg3Id = session.appendMessage({ role: "user", content: "followup", timestamp: 3 });

		// Label all messages
		session.appendLabelChange(msg1Id, "first");
		session.appendLabelChange(msg2Id, "second");
		session.appendLabelChange(msg3Id, "third");

		// Branch from msg2 (excludes msg3)
		session.createBranchedSession(msg2Id);

		// Only labels for msg1 and msg2 should be preserved
		expect(session.getLabel(msg1Id)).toBe("first");
		expect(session.getLabel(msg2Id)).toBe("second");
		expect(session.getLabel(msg3Id)).toBeUndefined();
	});

	it("labels are not included in buildSessionContext", () => {
		const session = SessionManager.inMemory();

		const msgId = session.appendMessage({ role: "user", content: "hello", timestamp: 1 });
		session.appendLabelChange(msgId, "checkpoint");

		const ctx = session.buildSessionContext();
		expect(ctx.messages).toHaveLength(1);
		expect(ctx.messages[0].role).toBe("user");
	});

	it("throws when labeling non-existent entry", () => {
		const session = SessionManager.inMemory();

		expect(() => session.appendLabelChange("non-existent", "label")).toThrow("Entry non-existent not found");
	});
});
