"use client";
import { jsx as _jsx } from "react/jsx-runtime";
import { useCallback, useEffect, useRef, useState } from "react";
import { cn, resolvePresetClass } from "@stigmer/theme";
import { StigmerContext } from "./context";
import { DeploymentModeContext } from "./deployment-mode";
import { ExecutionTargetContext } from "./execution-target-context";
import { RunnerAdapterContext } from "./runner-adapter";
import { ColorModeContext, useSystemColorMode } from "./color-mode";
import { PortalContainerContext } from "./portal-container";
import { ModelRegistryContext } from "./models/ModelRegistryContext";
import { fetchModelRegistry } from "./models/registry";
import { TaskKindRegistryContext } from "./workflow/TaskKindRegistryContext";
/**
 * React provider that distributes a {@link Stigmer} SDK client to
 * descendant components via {@link StigmerContext}.
 *
 * Renders a `<div class="stgm">` container that scopes Stigmer's
 * CSS reset and design tokens. External consumers importing
 * `@stigmer/react/styles.css` get isolated styles that do not
 * leak into the host application.
 *
 * The `colorMode` prop controls dark/light appearance via a
 * `data-stgm-color-mode` attribute on the scoping container.
 * Host applications pass their theme state directly — no ancestor
 * `.dark` class or Tailwind convention is required.
 *
 * Pass {@link StigmerProviderProps.preset | preset} to apply a
 * built-in theme, or use {@link StigmerProviderProps.className | className}
 * for custom styling.
 *
 * @example
 * ```tsx
 * const client = useMemo(
 *   () => new Stigmer({ baseUrl, getAccessToken }),
 *   [getAccessToken],
 * );
 *
 * <StigmerProvider client={client} preset="fintech" colorMode="dark">
 *   <App />
 * </StigmerProvider>
 * ```
 */
export function StigmerProvider({ client, children, deploymentMode = "cloud", executionTarget, runnerAdapter, preset, className, colorMode = "light", }) {
    const systemMode = useSystemColorMode();
    const resolvedMode = colorMode === "system" ? systemMode : colorMode;
    const presetClass = preset ? resolvePresetClass(preset) : "";
    const portalContainer = usePortalContainer(resolvedMode, presetClass);
    const registryState = useModelRegistryFetch(client);
    const taskKindRegistryState = useTaskKindRegistryFetch(client);
    return (_jsx(StigmerContext.Provider, { value: client, children: _jsx(DeploymentModeContext.Provider, { value: deploymentMode, children: _jsx(ExecutionTargetContext.Provider, { value: executionTarget, children: _jsx(RunnerAdapterContext.Provider, { value: runnerAdapter ?? null, children: _jsx(ColorModeContext.Provider, { value: resolvedMode, children: _jsx(ModelRegistryContext.Provider, { value: registryState, children: _jsx(TaskKindRegistryContext.Provider, { value: taskKindRegistryState, children: _jsx(PortalContainerContext.Provider, { value: portalContainer, children: _jsx("div", { className: cn("stgm", presetClass, className), "data-stgm-color-mode": resolvedMode, children: children }) }) }) }) }) }) }) }) }));
}
const RETRY_DELAYS_MS = [1_000, 2_000, 4_000];
const TOKEN_POLL_INTERVAL_MS = 500;
const TOKEN_POLL_MAX_MS = 10_000;
/**
 * Fetches the model registry from the authenticated API and caches
 * the result for the lifetime of the provider.
 *
 * Handles the auth race condition where `StigmerProvider` mounts before
 * PKCE authentication is established (e.g. desktop release builds with
 * a fresh localStorage). When the initial token is `null`, polls for
 * a valid token before giving up. Retries on transient failures with
 * exponential backoff. Exposes `refetch` for manual retry from the UI.
 */
function useModelRegistryFetch(client) {
    const [version, setVersion] = useState(0);
    const [state, setState] = useState({
        models: [],
        isLoading: true,
        error: null,
    });
    const clientRef = useRef(client);
    clientRef.current = client;
    const fetchAttemptRef = useRef(0);
    const doFetch = useCallback(async (signal) => {
        const c = clientRef.current;
        let token = await c.getAuthCredential();
        if (!token) {
            const start = Date.now();
            while (!token && Date.now() - start < TOKEN_POLL_MAX_MS) {
                if (signal.aborted)
                    return;
                await new Promise((r) => setTimeout(r, TOKEN_POLL_INTERVAL_MS));
                if (signal.aborted)
                    return;
                token = await c.getAuthCredential();
            }
        }
        return fetchModelRegistry(c.baseUrl, token, c.fetch);
    }, []);
    useEffect(() => {
        const controller = new AbortController();
        const { signal } = controller;
        fetchAttemptRef.current = 0;
        const attempt = async () => {
            if (signal.aborted)
                return;
            setState((prev) => (prev.isLoading ? prev : { ...prev, isLoading: true }));
            try {
                const models = await doFetch(signal);
                if (!signal.aborted && models) {
                    setState({ models, isLoading: false, error: null });
                    fetchAttemptRef.current = 0;
                }
            }
            catch (err) {
                if (signal.aborted)
                    return;
                const retryIdx = fetchAttemptRef.current;
                fetchAttemptRef.current = retryIdx + 1;
                if (retryIdx < RETRY_DELAYS_MS.length) {
                    setTimeout(() => { if (!signal.aborted)
                        attempt(); }, RETRY_DELAYS_MS[retryIdx]);
                }
                else {
                    setState({
                        models: [],
                        isLoading: false,
                        error: err instanceof Error ? err : new Error(String(err)),
                    });
                }
            }
        };
        attempt();
        return () => { controller.abort(); };
    }, [doFetch, version]);
    const refetch = useCallback(() => {
        setState((prev) => {
            if (prev.isLoading)
                return prev;
            return { ...prev, isLoading: true, error: null };
        });
        setVersion((v) => v + 1);
    }, []);
    return { ...state, refetch };
}
// ---------------------------------------------------------------------------
// Task Kind Registry fetch
// ---------------------------------------------------------------------------
/**
 * Fetch the task kind registry from the authenticated API endpoint.
 *
 * The endpoint returns `{ version, generatedAt, descriptors: [...] }`.
 * Each descriptor maps directly to the `TaskKindDescriptor` interface
 * used by the React SDK's task palette, inspector, and YAML validation.
 */
async function fetchTaskKindRegistry(apiUrl, token, customFetch) {
    const doFetch = customFetch ?? globalThis.fetch;
    const headers = {};
    if (token) {
        headers["Authorization"] = `Bearer ${token}`;
    }
    const res = await doFetch(`${apiUrl}/v1/proxy/task-kind-registry`, { headers });
    if (!res.ok)
        throw new Error(`Task kind registry fetch failed: ${res.status}`);
    const data = await res.json();
    return parseTaskKindRegistryJson(data);
}
const VALID_CATEGORIES = new Set([
    "control_flow", "invocation", "ai", "data", "governance", "event", "unspecified",
]);
const VALID_FIELD_TYPES = new Set([
    "string", "int32", "float", "bool", "enum", "struct", "repeated", "map", "message",
]);
function parseTaskKindRegistryJson(data) {
    if (!data || typeof data !== "object")
        return [];
    const descriptors = data.descriptors;
    if (!Array.isArray(descriptors))
        return [];
    return descriptors
        .filter((d) => d != null && typeof d === "object" && typeof d.kind === "string")
        .map((d) => ({
        kind: d.kind,
        displayName: d.displayName ?? "",
        description: d.description ?? "",
        category: VALID_CATEGORIES.has(d.category)
            ? d.category
            : "unspecified",
        icon: d.icon ?? "",
        configProtoType: d.configProtoType ?? "",
        fields: Array.isArray(d.fields)
            ? d.fields.map((f) => ({
                name: f.name ?? "",
                displayName: f.displayName ?? "",
                description: f.description ?? "",
                type: VALID_FIELD_TYPES.has(f.type)
                    ? f.type
                    : "string",
                required: f.required === true,
                isExpression: f.isExpression === true ? true : undefined,
                defaultValue: typeof f.defaultValue === "string" ? f.defaultValue : undefined,
                enumValues: Array.isArray(f.enumValues) ? f.enumValues : undefined,
                groupId: typeof f.groupId === "string" ? f.groupId : undefined,
                fieldNumber: typeof f.fieldNumber === "number" ? f.fieldNumber : 0,
                elementType: typeof f.elementType === "string" ? f.elementType : undefined,
                validationHints: Array.isArray(f.validationHints) ? f.validationHints : undefined,
            }))
            : [],
        fieldGroups: Array.isArray(d.fieldGroups)
            ? d.fieldGroups.map((g) => ({
                id: g.id ?? "",
                displayName: g.displayName ?? "",
                description: typeof g.description === "string" ? g.description : undefined,
            }))
            : [],
        configJsonSchema: d.configJsonSchema != null && typeof d.configJsonSchema === "object"
            ? d.configJsonSchema
            : {},
        outputJsonSchema: d.outputJsonSchema != null && typeof d.outputJsonSchema === "object"
            ? d.outputJsonSchema
            : undefined,
        yamlExamples: Array.isArray(d.yamlExamples) ? d.yamlExamples : undefined,
        documentationUrl: d.documentationUrl ?? "",
        isAiNative: d.isAiNative === true,
        requiresExternalService: d.requiresExternalService === true,
    }));
}
/**
 * Fetches the task kind registry from the authenticated API and caches
 * the result for the lifetime of the provider.
 *
 * Follows the same pattern as {@link useModelRegistryFetch}: auth token
 * polling, exponential backoff retries, and `refetch` for manual retry.
 */
function useTaskKindRegistryFetch(client) {
    const [version, setVersion] = useState(0);
    const [state, setState] = useState({
        descriptors: [],
        isLoading: true,
        error: null,
    });
    const clientRef = useRef(client);
    clientRef.current = client;
    const fetchAttemptRef = useRef(0);
    const doFetch = useCallback(async (signal) => {
        const c = clientRef.current;
        let token = await c.getAuthCredential();
        if (!token) {
            const start = Date.now();
            while (!token && Date.now() - start < TOKEN_POLL_MAX_MS) {
                if (signal.aborted)
                    return;
                await new Promise((r) => setTimeout(r, TOKEN_POLL_INTERVAL_MS));
                if (signal.aborted)
                    return;
                token = await c.getAuthCredential();
            }
        }
        return fetchTaskKindRegistry(c.baseUrl, token, c.fetch);
    }, []);
    useEffect(() => {
        const controller = new AbortController();
        const { signal } = controller;
        fetchAttemptRef.current = 0;
        const attempt = async () => {
            if (signal.aborted)
                return;
            setState((prev) => (prev.isLoading ? prev : { ...prev, isLoading: true }));
            try {
                const descriptors = await doFetch(signal);
                if (!signal.aborted && descriptors) {
                    setState({ descriptors, isLoading: false, error: null });
                    fetchAttemptRef.current = 0;
                }
            }
            catch (err) {
                if (signal.aborted)
                    return;
                const retryIdx = fetchAttemptRef.current;
                fetchAttemptRef.current = retryIdx + 1;
                if (retryIdx < RETRY_DELAYS_MS.length) {
                    setTimeout(() => { if (!signal.aborted)
                        attempt(); }, RETRY_DELAYS_MS[retryIdx]);
                }
                else {
                    setState({
                        descriptors: [],
                        isLoading: false,
                        error: err instanceof Error ? err : new Error(String(err)),
                    });
                }
            }
        };
        attempt();
        return () => { controller.abort(); };
    }, [doFetch, version]);
    const refetch = useCallback(() => {
        setState((prev) => {
            if (prev.isLoading)
                return prev;
            return { ...prev, isLoading: true, error: null };
        });
        setVersion((v) => v + 1);
    }, []);
    return { ...state, refetch };
}
/**
 * Creates and manages a portal container `<div>` appended to
 * `document.body` that mirrors the scoping attributes of the main
 * provider container.
 *
 * Portaled content (popovers, dialogs, menus) that targets this
 * element will inherit the correct `--stgm-*` token values —
 * including dark-mode overrides — because the container carries
 * `data-stgm-color-mode` and the preset class.
 *
 * The element is created once on mount and removed on unmount.
 * Attribute values are kept in sync with prop changes via a
 * separate effect.
 *
 * Returns `null` during SSR (no `document`).
 */
function usePortalContainer(colorMode, presetClass) {
    const elRef = useRef(null);
    const [container, setContainer] = useState(null);
    useEffect(() => {
        const el = document.createElement("div");
        el.className = cn("stgm", presetClass);
        el.setAttribute("data-stgm-color-mode", colorMode);
        el.setAttribute("data-stgm-portal", "");
        document.body.appendChild(el);
        elRef.current = el;
        setContainer(el);
        return () => {
            document.body.removeChild(el);
            elRef.current = null;
        };
        // Intentionally empty deps: create once on mount, remove on unmount.
        // Attribute syncing is handled by the effect below.
    }, []);
    useEffect(() => {
        const el = elRef.current;
        if (!el)
            return;
        el.className = cn("stgm", presetClass);
        el.setAttribute("data-stgm-color-mode", colorMode);
    }, [colorMode, presetClass]);
    return container;
}
//# sourceMappingURL=provider.js.map