"use client";
import { useCallback, useEffect, useState } from "react";
import { getUserMessage } from "@stigmer/sdk";
import { useDefaultAgent } from "../agent";
import { useModelRegistry } from "../models";
import { parseModelKey } from "../models/registry";
import { DEFAULT_HARNESS } from "../models/harness";
import { useWorkspaceEntries } from "../workspace";
import { useSessionVariables } from "../execution/useSessionVariables";
import { withTimeout } from "../internal/withTimeout";
import { useCreateSession } from "./useCreateSession";
import { useCreateAgentExecution } from "../execution/useCreateAgentExecution";
import { useExecutionTarget } from "../execution-target-context";
import { useRunnerAdapter } from "../runner-adapter";
const DEFAULT_AGENT_TIMEOUT_MS = 10_000;
const STORAGE_KEY_HARNESS = "stigmer:session:harness";
function modelStorageKey(harness) {
    return harness === "cursor"
        ? "stigmer:session:model:cursor"
        : "stigmer:session:model";
}
/**
 * Orchestrates the "create a new session" flow.
 *
 * Manages all the state required to configure and submit a new session:
 * model selection (with localStorage persistence), agent resolution,
 * MCP server/skill selection, workspace entries, and session
 * variables. On submission, creates the session and its first execution
 * in sequence, then notifies the consumer via `onSessionCreated`.
 *
 * This hook is framework-agnostic — it works identically in Next.js,
 * Vite, Tauri, or any React environment. Navigation, toast notifications,
 * and draft-mode logic are the consumer's responsibility.
 *
 * @example
 * ```tsx
 * const flow = useNewSessionFlow({
 *   org: "acme",
 *   onSessionCreated: (id) => navigate(`/sessions/${id}`),
 *   onError: (msg) => toast.error(msg),
 * });
 *
 * <SessionComposer
 *   onSubmit={flow.submit}
 *   isSubmitting={flow.isSubmitting}
 *   org="acme"
 *   workspace={flow.workspace}
 *   agentRef={flow.agentRef}
 *   onAgentRefChange={flow.setAgentRef}
 *   onAgentResolutionChange={flow.setResolution}
 *   mcpServerUsages={flow.mcpServerUsages}
 *   onMcpServerUsagesChange={flow.setMcpServerUsages}
 *   skillRefs={flow.skillRefs}
 *   onSkillRefsChange={flow.setSkillRefs}
   *   sessionVariables={flow.sessionVariables}
 *   defaultModelId={flow.modelId}
 *   onModelChange={flow.setModelId}
 * />
 * ```
 */
export function useNewSessionFlow(options) {
    const { org, onSessionCreated, onError } = options;
    const contextTarget = useExecutionTarget();
    const executionTarget = options.executionTarget ?? contextTarget;
    const adapter = useRunnerAdapter();
    const [harness, setHarnessRaw] = useState(() => {
        if (typeof window === "undefined")
            return DEFAULT_HARNESS;
        const stored = localStorage.getItem(STORAGE_KEY_HARNESS);
        return stored === "cursor" ? "cursor" : DEFAULT_HARNESS;
    });
    const { getModel, isLoading: isModelsLoading } = useModelRegistry({ harness });
    const { create: createSession } = useCreateSession();
    const { create: createExecution } = useCreateAgentExecution();
    const { agent: defaultAgent, isLoading: isDefaultAgentLoading, error: defaultAgentError, waitForResolution: waitForDefaultAgent, } = useDefaultAgent(org);
    const workspace = useWorkspaceEntries();
    const sessionVariables = useSessionVariables();
    const [modelId, setModelId] = useState(undefined);
    const [agentRef, setAgentRef] = useState(null);
    const [resolution, setResolution] = useState(null);
    const [mcpServerUsages, setMcpServerUsages] = useState([]);
    const [skillRefs, setSkillRefs] = useState([]);
    const [isSubmitting, setIsSubmitting] = useState(false);
    const [submitError, setSubmitError] = useState(null);
    const validModelId = modelId && getModel(modelId) ? modelId : undefined;
    // Persist harness on change
    useEffect(() => {
        localStorage.setItem(STORAGE_KEY_HARNESS, harness);
    }, [harness]);
    const setHarness = useCallback((h) => {
        setHarnessRaw(h);
        const storedModel = localStorage.getItem(modelStorageKey(h));
        const plain = storedModel ? (parseModelKey(storedModel)?.modelId ?? storedModel) : undefined;
        setModelId(plain);
    }, []);
    // Restore persisted model — only after the registry has loaded so
    // getModel can actually validate the stored ID against live data.
    useEffect(() => {
        if (isModelsLoading)
            return;
        const stored = localStorage.getItem(modelStorageKey(harness));
        if (stored) {
            const plain = parseModelKey(stored)?.modelId ?? stored;
            if (getModel(plain)) {
                setModelId(plain);
            }
        }
    }, [getModel, harness, isModelsLoading]);
    // Persist model on change (using current harness key).
    // Strip compound keys (e.g. "cursor/default") to plain modelId before storing.
    useEffect(() => {
        if (modelId) {
            const plain = parseModelKey(modelId)?.modelId ?? modelId;
            localStorage.setItem(modelStorageKey(harness), plain);
        }
    }, [modelId, harness]);
    const submit = useCallback(async (message, selectedModel, context) => {
        if (isSubmitting)
            return;
        if (!org) {
            const msg = "Select an organization before starting a session.";
            setSubmitError(msg);
            onError?.(msg);
            return;
        }
        setIsSubmitting(true);
        setSubmitError(null);
        try {
            const sessionFields = {
                org,
                workspaceEntries: workspace.hasEntries
                    ? workspace.toInput()
                    : undefined,
                mcpServerUsages: mcpServerUsages.length > 0 ? mcpServerUsages : undefined,
                skillRefs: skillRefs.length > 0 ? skillRefs : undefined,
                harness,
                executionTarget,
            };
            const executionFields = {
                org,
                message,
                modelName: selectedModel ?? validModelId,
                runtimeEnv: context?.runtimeEnv,
                attachments: context?.attachments,
                interactionMode: context?.interactionMode,
                workspaceFileRefs: context?.workspaceFileRefs,
            };
            let sessionId;
            if (agentRef && resolution) {
                if (resolution.mode === "saved") {
                    ({ sessionId } = await createSession({
                        ...sessionFields,
                        agentInstanceId: resolution.instanceId,
                    }));
                }
                else {
                    ({ sessionId } = await createSession({
                        ...sessionFields,
                        agentRef,
                    }));
                }
            }
            else {
                let resolvedInstanceId = defaultAgent?.status?.defaultInstanceId;
                if (!resolvedInstanceId) {
                    if (isDefaultAgentLoading) {
                        const resolved = await withTimeout(waitForDefaultAgent(), DEFAULT_AGENT_TIMEOUT_MS, "Default agent did not load in time. Please try again.");
                        resolvedInstanceId = resolved?.status?.defaultInstanceId;
                    }
                    else if (defaultAgentError) {
                        throw new Error("Failed to load default agent. Please try again.");
                    }
                    if (!resolvedInstanceId) {
                        throw new Error("No default agent available. Select an agent to start a session.");
                    }
                }
                ({ sessionId } = await createSession({
                    ...sessionFields,
                    agentInstanceId: resolvedInstanceId,
                }));
            }
            // Local execution: attach the session's runner worker before creating
            // the first execution. The session view (useSessionConversation) owns
            // the steady-state lifecycle, but it is not mounted yet — navigation
            // happens after onSessionCreated below — so without this eager attach
            // the first execution would have no poller until the view mounts.
            if (adapter && executionTarget === "local") {
                await adapter.onSessionOpened(sessionId);
            }
            try {
                await createExecution({ ...executionFields, sessionId });
            }
            catch (err) {
                // The first execution failed and the session view will not mount,
                // so detach the worker we just attached to avoid leaking it.
                if (adapter && executionTarget === "local") {
                    adapter.onSessionClosed(sessionId).catch(() => { });
                }
                throw err;
            }
            sessionVariables.clear();
            onSessionCreated(sessionId);
        }
        catch (err) {
            const detail = getUserMessage(err, "Failed to start session");
            setSubmitError(detail);
            onError?.(detail);
        }
        finally {
            setIsSubmitting(false);
        }
    }, [
        isSubmitting,
        org,
        harness,
        executionTarget,
        adapter,
        validModelId,
        workspace,
        mcpServerUsages,
        skillRefs,
        agentRef,
        resolution,
        defaultAgent,
        isDefaultAgentLoading,
        defaultAgentError,
        waitForDefaultAgent,
        createSession,
        createExecution,
        sessionVariables,
        onSessionCreated,
        onError,
    ]);
    return {
        harness,
        setHarness,
        modelId: validModelId,
        setModelId,
        agentRef,
        setAgentRef,
        resolution,
        setResolution,
        mcpServerUsages,
        setMcpServerUsages,
        skillRefs,
        setSkillRefs,
        workspace,
        sessionVariables,
        isSubmitting,
        submitError,
        submit,
    };
}
//# sourceMappingURL=useNewSessionFlow.js.map