"use client";
import { useCallback, useEffect, useMemo, useState } from "react";
import { isTerminalPhase } from "../execution/execution-phases";
import { useStigmer } from "../hooks";
import { useConversationStoreRef } from "../internal/store";
import { useCreateAgentExecution } from "../execution/useCreateAgentExecution";
import { useExecutionStream } from "../execution/useExecutionStream";
import { useSubmitApproval } from "../execution/useSubmitApproval";
import { useSession } from "./useSession";
import { useSessionExecutions } from "./useSessionExecutions";
import { useUpdateSession } from "./useUpdateSession";
/**
 * Behavior hook that encapsulates the full conversation lifecycle for
 * a session: loading data, streaming the active execution, submitting
 * follow-up messages, and updating session-level workspace entries.
 *
 * Composes {@link useSession}, {@link useSessionExecutions},
 * {@link useCreateAgentExecution}, {@link useExecutionStream}, and
 * {@link useUpdateSession} into a single return value that drives
 * both {@link MessageThread} and {@link SessionComposer}.
 *
 * Platform builders get the complete conversation loop without
 * reimplementing orchestration logic.
 *
 * @param sessionId - Session to display and converse in. Pass `null` to skip.
 * @param org - Organization slug for creating follow-up executions.
 *
 * @example
 * ```tsx
 * function Chat({ sessionId, org }: { sessionId: string; org: string }) {
 *   const conv = useSessionConversation(sessionId, org);
 *
 *   if (conv.isLoading) return <Spinner />;
 *
 *   return (
 *     <>
 *       <MessageThread
 *         executions={conv.completedExecutions}
 *         activeStreamExecution={conv.activeStreamExecution}
 *         pendingUserMessage={conv.pendingUserMessage}
 *         onApprovalSubmit={conv.submitApproval}
 *         submittingApprovalIds={conv.submittingApprovalIds}
 *       />
 *       <SessionComposer
 *         onSubmit={(msg, model) => conv.sendFollowUp(msg, { modelName: model })}
 *         disabled={!conv.canSendFollowUp}
 *         isSubmitting={conv.isSending}
 *       />
 *     </>
 *   );
 * }
 * ```
 */
export function useSessionConversation(sessionId, org) {
    const stigmer = useStigmer();
    const { session, isLoading: sessionLoading, error: sessionError, refetch: refetchSession, } = useSession(sessionId);
    const { executions, isLoading: executionsLoading, error: executionsError, refetch, } = useSessionExecutions(sessionId);
    const { create, isCreating, error: createError, clearError: clearCreateError, } = useCreateAgentExecution();
    const { update: updateSession } = useUpdateSession();
    const { submitApproval: rawSubmitApproval, submittingToolCallIds, error: approvalError, clearError: clearApprovalError, } = useSubmitApproval();
    const [pendingExecutionId, setPendingExecutionId] = useState(null);
    const [pendingUserMessage, setPendingUserMessage] = useState(null);
    const listActiveId = useMemo(() => {
        for (let i = executions.length - 1; i >= 0; i--) {
            const phase = executions[i].status?.phase;
            if (phase === undefined || !isTerminalPhase(phase)) {
                return executions[i].metadata?.id ?? null;
            }
        }
        return null;
    }, [executions]);
    const activeExecutionId = pendingExecutionId ?? listActiveId;
    // The conversation store is shared between useExecutionStream (which
    // ingests snapshots with structural sharing + rAF coalescing) and the
    // rendering tree. This eliminates the duplicate structuralShare that
    // was previously done in this hook.
    const conversationStore = useConversationStoreRef();
    const stream = useExecutionStream(activeExecutionId, {
        store: conversationStore,
    });
    // Clear pendingExecutionId once the execution appears in the fetched list
    useEffect(() => {
        if (pendingExecutionId &&
            executions.some((e) => (e.metadata?.id ?? "") === pendingExecutionId)) {
            setPendingExecutionId(null);
        }
    }, [pendingExecutionId, executions]);
    // Clear optimistic message once the stream delivers its first snapshot
    useEffect(() => {
        if (pendingUserMessage && stream.execution) {
            setPendingUserMessage(null);
        }
    }, [pendingUserMessage, stream.execution]);
    // Refetch executions when stream reaches a terminal phase so the
    // fetched list reflects the completed status and listActiveId clears.
    useEffect(() => {
        if (activeExecutionId && isTerminalPhase(stream.phase)) {
            refetch();
        }
    }, [activeExecutionId, stream.phase, refetch]);
    const completedExecutions = useMemo(() => {
        if (!activeExecutionId)
            return executions;
        return executions.filter((e) => (e.metadata?.id ?? "") !== activeExecutionId);
    }, [executions, activeExecutionId]);
    const fetchedActiveExecution = useMemo(() => {
        if (!activeExecutionId)
            return null;
        return (executions.find((e) => (e.metadata?.id ?? "") === activeExecutionId) ?? null);
    }, [executions, activeExecutionId]);
    const activeStreamExecution = stream.execution ?? fetchedActiveExecution;
    const activePhase = useMemo(() => {
        if (!activeExecutionId)
            return null;
        return stream.phase;
    }, [activeExecutionId, stream.phase]);
    const canSendFollowUp = !isCreating && activeExecutionId === null;
    const workspaceEntries = useMemo(() => session?.spec?.workspaceEntries ?? [], [session]);
    const mcpServerUsages = useMemo(() => session?.spec?.mcpServerUsages ?? [], [session]);
    const skillRefs = useMemo(() => session?.spec?.skillRefs ?? [], [session]);
    const sendFollowUp = useCallback(async (message, options) => {
        if (!sessionId || !session)
            return;
        setPendingUserMessage(message);
        try {
            const needsSessionUpdate = options?.agentInstanceId !== undefined ||
                options?.workspaceEntries !== undefined ||
                options?.mcpServerUsages !== undefined ||
                options?.skillRefs !== undefined;
            if (needsSessionUpdate) {
                // Fetch the latest session to avoid overwriting fields that were
                // modified server-side (e.g., LLM-generated subject, sandbox_id)
                // since the React state was last loaded.
                const freshSession = await stigmer.session.get(sessionId);
                await updateSession(buildUpdateInput(freshSession, {
                    agentInstanceId: options?.agentInstanceId,
                    workspaceEntries: options?.workspaceEntries,
                    mcpServerUsages: options?.mcpServerUsages,
                    skillRefs: options?.skillRefs,
                }));
                refetchSession();
            }
            const result = await create({
                org,
                sessionId,
                message,
                modelName: options?.modelName,
                runtimeEnv: options?.runtimeEnv,
                attachments: options?.attachments,
            });
            setPendingExecutionId(result.executionId);
            refetch();
        }
        catch (err) {
            setPendingUserMessage(null);
            if (process.env.NODE_ENV !== "production") {
                console.error("[useSessionConversation] sendFollowUp failed:", err);
            }
        }
    }, [sessionId, session, org, stigmer, create, updateSession, refetch, refetchSession]);
    const pendingApprovals = useMemo(() => activeStreamExecution?.status?.pendingApprovals ?? [], [activeStreamExecution]);
    const submitApproval = useCallback(async (toolCallId, action, comment) => {
        if (!activeExecutionId)
            return;
        await rawSubmitApproval(activeExecutionId, toolCallId, action, comment);
    }, [activeExecutionId, rawSubmitApproval]);
    const isLoading = sessionLoading || executionsLoading;
    const loadError = sessionError || executionsError;
    return {
        session,
        completedExecutions,
        activeStreamExecution,
        activePhase,
        isStreaming: stream.isStreaming,
        isConnecting: stream.isConnecting,
        sendFollowUp,
        canSendFollowUp,
        isSending: isCreating,
        sendError: createError,
        clearSendError: clearCreateError,
        pendingUserMessage,
        workspaceEntries,
        mcpServerUsages,
        skillRefs,
        pendingApprovals,
        submitApproval,
        submittingApprovalIds: submittingToolCallIds,
        approvalError,
        clearApprovalError,
        isLoading,
        loadError,
        streamError: stream.error,
        reconnectStream: stream.reconnect,
    };
}
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
/**
 * Converts the existing Session proto into a SessionInput suitable for
 * the update RPC (which uses replace semantics — the full spec is sent).
 *
 * For each session-level collection (workspace, MCP servers, skills):
 * if an override is provided, it replaces the existing value; otherwise
 * the current session value is preserved by converting it back to input
 * format.
 */
function buildUpdateInput(session, overrides) {
    const meta = session.metadata;
    const spec = session.spec;
    const workspaceEntries = overrides.workspaceEntries ?? specWorkspaceToInput(spec);
    const mcpServerUsages = overrides.mcpServerUsages ?? specMcpUsagesToInput(spec);
    const skillRefs = overrides.skillRefs ?? specSkillRefsToInput(spec);
    return {
        name: meta.name,
        org: meta.org,
        agentInstanceId: overrides.agentInstanceId ?? (spec?.agentInstanceId || undefined),
        subject: spec?.subject || undefined,
        threadId: spec?.threadId || undefined,
        sandboxId: spec?.sandboxId || undefined,
        runnerId: spec?.runnerId || undefined,
        harness: spec?.harness,
        metadata: spec?.metadata && Object.keys(spec.metadata).length > 0
            ? { ...spec.metadata }
            : undefined,
        workspaceEntries: workspaceEntries?.length ? workspaceEntries : undefined,
        mcpServerUsages: mcpServerUsages?.length ? mcpServerUsages : undefined,
        skillRefs: skillRefs?.length ? skillRefs : undefined,
    };
}
/** Convert proto workspace entries back to SDK input format. */
function specWorkspaceToInput(spec) {
    return spec?.workspaceEntries?.map((e) => {
        if (e.source?.source.case === "gitRepo") {
            const v = e.source.source.value;
            return {
                name: e.name || undefined,
                source: {
                    gitRepo: {
                        url: v.url,
                        branch: v.branch || undefined,
                        commit: v.commit || undefined,
                        depth: v.depth || undefined,
                    },
                },
            };
        }
        if (e.source?.source.case === "localPath") {
            return {
                name: e.name || undefined,
                source: {
                    localPath: { path: e.source.source.value.path || undefined },
                },
            };
        }
        return { name: e.name || undefined, source: {} };
    });
}
/** Convert proto MCP server usages back to SDK input format. */
function specMcpUsagesToInput(spec) {
    return spec?.mcpServerUsages?.map((u) => ({
        mcpServerRef: {
            org: u.mcpServerRef?.org ?? "",
            slug: u.mcpServerRef?.slug ?? "",
            version: u.mcpServerRef?.version || undefined,
            kind: u.mcpServerRef?.kind,
        },
        enabledTools: u.enabledTools?.length ? [...u.enabledTools] : undefined,
        toolApprovalOverrides: u.toolApprovalOverrides?.length
            ? u.toolApprovalOverrides.map((o) => ({
                toolName: o.toolName || undefined,
                requiresApproval: o.requiresApproval || undefined,
                message: o.message || undefined,
            }))
            : undefined,
    }));
}
/** Convert proto skill references back to SDK input format. */
function specSkillRefsToInput(spec) {
    return spec?.skillRefs?.map((r) => ({
        org: r.org ?? "",
        slug: r.slug ?? "",
        version: r.version || undefined,
        kind: r.kind,
    }));
}
//# sourceMappingURL=useSessionConversation.js.map