import { jsx as _jsx } from "react/jsx-runtime";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, act, waitFor } from "@testing-library/react";
import { create } from "@bufbuild/protobuf";
import { AgentExecutionSchema, AgentExecutionStatusSchema, } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
import { SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
import { StigmerContext } from "../../context";
import { useSessionConversation } from "../useSessionConversation";
function makeSession(id) {
    const session = create(SessionSchema);
    const metadata = create(ApiResourceMetadataSchema);
    metadata.id = id;
    session.metadata = metadata;
    return session;
}
function makeExecution(id, phase) {
    const exec = create(AgentExecutionSchema);
    const metadata = create(ApiResourceMetadataSchema);
    metadata.id = id;
    exec.metadata = metadata;
    const status = create(AgentExecutionStatusSchema);
    status.phase = phase;
    exec.status = status;
    return exec;
}
/**
 * Creates a controllable async generator for streaming.
 */
function createControllableStream() {
    let resolve = null;
    let reject = null;
    const generator = {
        next() {
            return new Promise((res, rej) => {
                resolve = res;
                reject = rej;
            });
        },
        return(value) {
            return Promise.resolve({ done: true, value: value });
        },
        throw(err) {
            return Promise.reject(err);
        },
        [Symbol.asyncIterator]() {
            return generator;
        },
    };
    return {
        generator,
        push(value) {
            resolve?.({ done: false, value });
        },
        finish() {
            resolve?.({ done: true, value: undefined });
        },
        fail(err) {
            reject?.(err);
        },
    };
}
function createMockStigmer(methods) {
    return {
        session: {
            get: methods.sessionGet,
        },
        agentExecution: {
            listBySession: methods.listBySession,
            create: methods.executionCreate,
            subscribe: methods.subscribe,
            submitApproval: methods.submitApproval,
        },
    };
}
function createWrapper(client) {
    return function Wrapper({ children }) {
        return (_jsx(StigmerContext.Provider, { value: client, children: children }));
    };
}
describe("useSessionConversation", () => {
    let methods;
    let mockStigmer;
    let stream;
    beforeEach(() => {
        stream = createControllableStream();
        methods = {
            sessionGet: vi.fn().mockResolvedValue(makeSession("session-1")),
            listBySession: vi.fn().mockResolvedValue({ entries: [] }),
            executionCreate: vi.fn(),
            subscribe: vi.fn().mockReturnValue(stream.generator),
            submitApproval: vi.fn().mockResolvedValue({}),
        };
        mockStigmer = createMockStigmer(methods);
    });
    it("returns idle state when sessionId is null", () => {
        const { result } = renderHook(() => useSessionConversation(null, "org"), { wrapper: createWrapper(mockStigmer) });
        expect(result.current.session).toBeNull();
        expect(result.current.isLoading).toBe(false);
        expect(result.current.canSendFollowUp).toBe(true);
        expect(methods.sessionGet).not.toHaveBeenCalled();
        expect(methods.listBySession).not.toHaveBeenCalled();
    });
    it("isLoading is true while session and executions are loading", () => {
        methods.sessionGet.mockReturnValue(new Promise(() => { }));
        methods.listBySession.mockReturnValue(new Promise(() => { }));
        const { result } = renderHook(() => useSessionConversation("session-1", "org"), { wrapper: createWrapper(mockStigmer) });
        expect(result.current.isLoading).toBe(true);
    });
    it("loads session and executions on mount", async () => {
        const session = makeSession("session-1");
        methods.sessionGet.mockResolvedValue(session);
        methods.listBySession.mockResolvedValue({ entries: [] });
        const { result } = renderHook(() => useSessionConversation("session-1", "org"), { wrapper: createWrapper(mockStigmer) });
        await waitFor(() => {
            expect(result.current.session).toBe(session);
            expect(result.current.isLoading).toBe(false);
        });
    });
    it("surfaces session fetch errors in loadError", async () => {
        methods.sessionGet.mockRejectedValue(new Error("session not found"));
        const { result } = renderHook(() => useSessionConversation("session-1", "org"), { wrapper: createWrapper(mockStigmer) });
        await waitFor(() => {
            expect(result.current.loadError?.message).toBe("session not found");
        });
    });
    it("completedExecutions excludes the active non-terminal execution", async () => {
        const completed = makeExecution("e1", ExecutionPhase.EXECUTION_COMPLETED);
        const active = makeExecution("e2", ExecutionPhase.EXECUTION_IN_PROGRESS);
        methods.listBySession.mockResolvedValue({
            entries: [completed, active],
        });
        const { result } = renderHook(() => useSessionConversation("session-1", "org"), { wrapper: createWrapper(mockStigmer) });
        await waitFor(() => {
            expect(result.current.completedExecutions).toHaveLength(1);
            expect(result.current.completedExecutions[0]).toBe(completed);
        });
    });
    it("canSendFollowUp is true when no active execution", async () => {
        const completed = makeExecution("e1", ExecutionPhase.EXECUTION_COMPLETED);
        methods.listBySession.mockResolvedValue({ entries: [completed] });
        const { result } = renderHook(() => useSessionConversation("session-1", "org"), { wrapper: createWrapper(mockStigmer) });
        await waitFor(() => {
            expect(result.current.canSendFollowUp).toBe(true);
        });
    });
    it("canSendFollowUp is false while an execution is active", async () => {
        const active = makeExecution("e1", ExecutionPhase.EXECUTION_IN_PROGRESS);
        methods.listBySession.mockResolvedValue({ entries: [active] });
        const { result } = renderHook(() => useSessionConversation("session-1", "org"), { wrapper: createWrapper(mockStigmer) });
        await waitFor(() => {
            expect(result.current.canSendFollowUp).toBe(false);
        });
    });
    it("sendFollowUp sets pendingUserMessage optimistically", async () => {
        methods.listBySession.mockResolvedValue({ entries: [] });
        const newExec = makeExecution("new-exec", ExecutionPhase.EXECUTION_PENDING);
        newExec.metadata.id = "new-exec";
        methods.executionCreate.mockResolvedValue(newExec);
        const { result } = renderHook(() => useSessionConversation("session-1", "org"), { wrapper: createWrapper(mockStigmer) });
        await waitFor(() => {
            expect(result.current.isLoading).toBe(false);
        });
        act(() => {
            result.current.sendFollowUp("Hello agent");
        });
        expect(result.current.pendingUserMessage).toBe("Hello agent");
    });
    it("sendFollowUp creates execution via the SDK", async () => {
        methods.listBySession.mockResolvedValue({ entries: [] });
        const newExec = makeExecution("new-exec", ExecutionPhase.EXECUTION_PENDING);
        newExec.metadata.id = "new-exec";
        methods.executionCreate.mockResolvedValue(newExec);
        const { result } = renderHook(() => useSessionConversation("session-1", "org"), { wrapper: createWrapper(mockStigmer) });
        await waitFor(() => {
            expect(result.current.isLoading).toBe(false);
        });
        await act(async () => {
            await result.current.sendFollowUp("Deploy it");
        });
        expect(methods.executionCreate).toHaveBeenCalledWith(expect.objectContaining({
            org: "org",
            sessionId: "session-1",
            message: "Deploy it",
        }));
    });
    it("pendingUserMessage clears when stream delivers first snapshot", async () => {
        methods.listBySession.mockResolvedValue({ entries: [] });
        const newExec = makeExecution("new-exec", ExecutionPhase.EXECUTION_PENDING);
        newExec.metadata.id = "new-exec";
        methods.executionCreate.mockResolvedValue(newExec);
        const newStream = createControllableStream();
        methods.subscribe.mockReturnValue(newStream.generator);
        const { result } = renderHook(() => useSessionConversation("session-1", "org"), { wrapper: createWrapper(mockStigmer) });
        await waitFor(() => expect(result.current.isLoading).toBe(false));
        await act(async () => {
            await result.current.sendFollowUp("Hello");
        });
        expect(result.current.pendingUserMessage).toBe("Hello");
        act(() => {
            newStream.push(makeExecution("new-exec", ExecutionPhase.EXECUTION_IN_PROGRESS));
        });
        await waitFor(() => {
            expect(result.current.pendingUserMessage).toBeNull();
        });
    });
    it("full follow-up lifecycle: active execution completes → canSendFollowUp → sendFollowUp succeeds", async () => {
        // Start with one IN_PROGRESS execution (simulates a Cursor execution running)
        const activeExec = makeExecution("exec-1", ExecutionPhase.EXECUTION_IN_PROGRESS);
        methods.listBySession.mockResolvedValue({ entries: [activeExec] });
        const execStream = createControllableStream();
        methods.subscribe.mockReturnValue(execStream.generator);
        const { result } = renderHook(() => useSessionConversation("session-1", "org"), { wrapper: createWrapper(mockStigmer) });
        await waitFor(() => expect(result.current.isLoading).toBe(false));
        // Initially canSendFollowUp should be false (execution is active)
        expect(result.current.canSendFollowUp).toBe(false);
        // Stream delivers EXECUTION_COMPLETED — simulating Cursor run finishing
        const completedExec = makeExecution("exec-1", ExecutionPhase.EXECUTION_COMPLETED);
        act(() => {
            execStream.push(completedExec);
        });
        // After stream completes, the hook should refetch executions.
        // Mock the refetch to return the completed execution.
        methods.listBySession.mockResolvedValue({ entries: [completedExec] });
        // Wait for canSendFollowUp to become true
        await waitFor(() => {
            expect(result.current.canSendFollowUp).toBe(true);
        });
        // Now send a follow-up message
        const followUpExec = makeExecution("exec-2", ExecutionPhase.EXECUTION_PENDING);
        followUpExec.metadata.id = "exec-2";
        methods.executionCreate.mockResolvedValue(followUpExec);
        await act(async () => {
            await result.current.sendFollowUp("Follow-up message", { modelName: "default" });
        });
        // Verify the create call was made with the correct session and message
        expect(methods.executionCreate).toHaveBeenCalledWith(expect.objectContaining({
            org: "org",
            sessionId: "session-1",
            message: "Follow-up message",
            executionConfig: expect.objectContaining({ modelName: "default" }),
        }));
    });
});
//# sourceMappingURL=useSessionConversation.test.js.map