"use client";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
import { useDefaultAgent } from "../agent/index.js";
import { useStigmer } from "../hooks.js";
import { useWorkspaceEntries } from "../workspace/index.js";
import { useSessionVariables } from "../execution/useSessionVariables.js";
import { fromProtoInteractionMode } from "../composer/index.js";
import { fromProtoHarness } from "../models/harness.js";
import { Harness, ExecutionTarget } from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
import { fromProtoExecutionTarget } from "./execution-target.js";
import { useSessionConversation } from "./useSessionConversation.js";
import { resolveExecutionRuntimeEnv } from "./runtime-env.js";
import { useAgentRefFromSession } from "./useAgentRefFromSession.js";
import { usePersistedModel } from "./usePersistedModel.js";
import { specMcpUsagesToInput, specSkillRefsToInput } from "./session-spec-converters.js";
/**
 * Well-known Daytona sandbox workspace root. Used as the SDK safety-net
 * normalization target for cloud sessions (git-repo workspace entries).
 */
const DAYTONA_WORKSPACE_ROOT = "/home/daytona/workspace";
/**
 * Orchestrates the session page experience.
 *
 * Composes `useSessionConversation` with agent resolution, workspace
 * synchronization, model persistence, and follow-up submission logic.
 * Returns everything a session page needs to render its UI without
 * duplicating domain orchestration.
 *
 * Framework-agnostic — works identically in Next.js, Vite, Tauri, or
 * any React environment. The consumer provides layout, error states,
 * and loading skeletons.
 *
 * @example
 * ```tsx
 * const flow = useSessionPageFlow({ sessionId: "ses_abc", org: "acme" });
 *
 * if (flow.conv.isLoading) return <Spinner />;
 * if (flow.conv.loadError) return <ErrorView error={flow.conv.loadError} />;
 *
 * return (
 *   <>
 *     <MessageThread
 *       executions={flow.conv.completedExecutions}
 *       activeStreamExecution={flow.conv.activeStreamExecution}
 *       sandboxWorkspaceRoot={flow.sandboxWorkspaceRoot}
 *     />
 *     <SessionComposer
 *       onSubmit={flow.handleSubmit}
 *       isSubmitting={flow.conv.isSending}
 *       disabled={!flow.conv.canSendFollowUp}
 *       workspace={flow.workspace}
 *       agentRef={flow.agentRef}
 *       onAgentRefChange={flow.setAgentRef}
 *       onAgentResolutionChange={flow.setResolution}
 *       defaultModelId={flow.model[0]}
 *       onModelChange={flow.model[1]}
 *     />
 *   </>
 * );
 * ```
 */
export function useSessionPageFlow(options) {
    const { sessionId, org, getRuntimeEnv } = options;
    const stigmer = useStigmer();
    const conv = useSessionConversation(sessionId, org);
    const harness = fromProtoHarness(conv.session?.spec?.harness ?? Harness.UNSPECIFIED);
    const executionTarget = fromProtoExecutionTarget(conv.session?.spec?.executionTarget ?? ExecutionTarget.UNSPECIFIED);
    const [persistedModelId, setPersistedModelId] = usePersistedModel({ harness });
    const lastExecModelId = useMemo(() => {
        const lastExec = conv.completedExecutions.at(-1);
        return lastExec?.spec?.executionConfig?.modelName || undefined;
    }, [conv.completedExecutions]);
    const modelId = persistedModelId ?? lastExecModelId;
    const model = [modelId, setPersistedModelId];
    // Interaction mode mirrors the model derivation: an explicit user override
    // wins; otherwise reflect the latest execution's mode so a completed Plan
    // keeps the composer on "Plan" until the user implements or switches. This
    // is derived state (always consistent), never an effect-synced copy.
    const lastExecInteractionMode = useMemo(() => fromProtoInteractionMode(conv.completedExecutions.at(-1)?.spec?.executionConfig?.interactionMode), [conv.completedExecutions]);
    const [interactionModeOverride, setInteractionMode] = useState(null);
    const interactionMode = [
        interactionModeOverride ?? lastExecInteractionMode ?? "agent",
        setInteractionMode,
    ];
    const workspace = useWorkspaceEntries();
    const sessionVariables = useSessionVariables();
    const [mcpServerUsages, setMcpServerUsages] = useState([]);
    const [skillRefs, setSkillRefs] = useState([]);
    const initialSyncDone = useRef(false);
    // Session-scoped auto-approve, set at the approval gate (not pre-armed in the
    // composer). Lives only in memory for the life of this page — reset on reload.
    const [autoApproveAll, setAutoApproveAll] = useState(false);
    const submitApproval = useCallback(async (toolCallId, action, comment) => {
        // "Approve & don't ask again": remember the choice for the rest of this
        // live session so future follow-ups carry auto_approve_all. The control
        // plane separately resolves the current execution's remaining gates and
        // the runner skips the gate for the rest of that run.
        if (action === ApprovalAction.APPROVE_ALL) {
            setAutoApproveAll(true);
        }
        await conv.submitApproval(toolCallId, action, comment);
    }, [conv.submitApproval]);
    // -------------------------------------------------------------------------
    // Agent — derive from session, allow mid-session changes
    // -------------------------------------------------------------------------
    const sessionInstanceId = conv.session?.spec?.agentInstanceId ?? null;
    const { agentRef: derivedAgentRef } = useAgentRefFromSession(sessionInstanceId);
    const { agent: defaultAgent, isLoading: isDefaultAgentLoading } = useDefaultAgent(org);
    const [agentRef, setAgentRef] = useState(null);
    const [resolution, setResolution] = useState(null);
    const [isDefaultAgent, setIsDefaultAgent] = useState(false);
    const [agentInitDone, setAgentInitDone] = useState(false);
    if (!agentInitDone && derivedAgentRef && sessionInstanceId && !isDefaultAgentLoading) {
        setAgentInitDone(true);
        setAgentRef(derivedAgentRef);
        setResolution({ mode: "saved", instanceId: sessionInstanceId });
        const isDefault = defaultAgent &&
            derivedAgentRef.org === defaultAgent.metadata?.org &&
            derivedAgentRef.slug === defaultAgent.metadata?.slug;
        setIsDefaultAgent(!!isDefault);
    }
    // -------------------------------------------------------------------------
    // Session spec sync — hydrate workspace, MCP servers, and skills on first load
    // -------------------------------------------------------------------------
    useEffect(() => {
        if (!conv.session || initialSyncDone.current)
            return;
        initialSyncDone.current = true;
        const spec = conv.session.spec;
        // Workspace entries
        const protoEntries = spec?.workspaceEntries ?? [];
        for (const entry of protoEntries) {
            if (entry.source?.source.case === "gitRepo") {
                const { url, branch } = entry.source.source.value;
                workspace.addGitRepo(url, branch || undefined);
            }
            else if (entry.source?.source.case === "localPath") {
                workspace.addLocalPath(entry.source.source.value.path);
            }
        }
        // MCP server usages
        const mcpInputs = specMcpUsagesToInput(spec);
        if (mcpInputs?.length) {
            setMcpServerUsages(mcpInputs);
        }
        // Skill references
        const skillInputs = specSkillRefsToInput(spec);
        if (skillInputs?.length) {
            setSkillRefs(skillInputs);
        }
    }, [conv.session, workspace]);
    // -------------------------------------------------------------------------
    // Follow-up submission with agent override
    // -------------------------------------------------------------------------
    const [submitError, setSubmitError] = useState(null);
    const handleSubmit = useCallback(async (message, selectedModel, context) => {
        setSubmitError(null);
        // Pre-send work runs before conv.sendFollowUp so a failure here
        // aborts cleanly: no optimistic pending message, no session
        // mutation. The composer fires this handler without awaiting it,
        // so a rejection would otherwise be an unhandled rejection —
        // failures must land in submitError instead.
        let agentInstanceIdOverride;
        let runtimeEnv;
        try {
            if (resolution) {
                if (resolution.mode === "saved" &&
                    resolution.instanceId !== sessionInstanceId) {
                    agentInstanceIdOverride = resolution.instanceId;
                }
                else if (resolution.mode === "direct" && agentRef) {
                    const agent = await stigmer.agent.getByReference(agentRef);
                    const defaultId = agent.status?.defaultInstanceId;
                    if (defaultId && defaultId !== sessionInstanceId) {
                        agentInstanceIdOverride = defaultId;
                    }
                }
            }
            // Evaluated per follow-up so short-lived host credentials are
            // current; host values win over composer-collected env.
            runtimeEnv = getRuntimeEnv
                ? await resolveExecutionRuntimeEnv(getRuntimeEnv, context?.runtimeEnv)
                : context?.runtimeEnv;
        }
        catch (err) {
            setSubmitError(err instanceof Error ? err : new Error(String(err)));
            return;
        }
        conv.sendFollowUp(message, {
            agentInstanceId: agentInstanceIdOverride,
            modelName: selectedModel ?? modelId,
            workspaceEntries: workspace.hasEntries
                ? workspace.toInput()
                : undefined,
            mcpServerUsages: mcpServerUsages.length > 0 ? mcpServerUsages : undefined,
            skillRefs: skillRefs.length > 0 ? skillRefs : undefined,
            runtimeEnv,
            attachments: context?.attachments,
            interactionMode: context?.interactionMode,
            buildFromPlan: context?.buildFromPlan,
            // Sourced from the session-scoped preference set at the approval gate,
            // not from the composer (the pre-arm toggle was removed).
            autoApproveAll: autoApproveAll || undefined,
            workspaceFileRefs: context?.workspaceFileRefs,
        });
        sessionVariables.clear();
    }, [conv.sendFollowUp, modelId, workspace, mcpServerUsages, skillRefs, sessionVariables.clear, resolution, agentRef, sessionInstanceId, stigmer, autoApproveAll, getRuntimeEnv]);
    // -------------------------------------------------------------------------
    // Derived display state
    // -------------------------------------------------------------------------
    const displayExecution = useMemo(() => {
        if (conv.activeStreamExecution)
            return conv.activeStreamExecution;
        const completed = conv.completedExecutions;
        return completed.length > 0 ? completed[completed.length - 1] : null;
    }, [conv.activeStreamExecution, conv.completedExecutions]);
    const allExecutions = useMemo(() => [
        ...conv.completedExecutions,
        ...(conv.activeStreamExecution ? [conv.activeStreamExecution] : []),
    ], [conv.completedExecutions, conv.activeStreamExecution]);
    const sandboxWorkspaceRoot = useMemo(() => {
        const entries = conv.workspaceEntries;
        const hasGitRepo = entries.some((e) => e.source?.source.case === "gitRepo");
        return hasGitRepo ? DAYTONA_WORKSPACE_ROOT : undefined;
    }, [conv.workspaceEntries]);
    return {
        conv,
        harness,
        executionTarget,
        model,
        interactionMode,
        agentRef,
        setAgentRef,
        resolution,
        setResolution,
        isDefaultAgent,
        mcpServerUsages,
        setMcpServerUsages,
        skillRefs,
        setSkillRefs,
        workspace,
        sessionVariables,
        autoApproveAll,
        setAutoApproveAll,
        submitApproval,
        handleSubmit,
        submitError,
        displayExecution,
        allExecutions,
        sandboxWorkspaceRoot,
    };
}
//# sourceMappingURL=useSessionPageFlow.js.map