import { ConfigurationError, GoodVibesSdkError } from '@pellux/goodvibes-errors';
import { createOperatorSdk, } from '@pellux/goodvibes-operator-sdk';
import { createPeerSdk, } from '@pellux/goodvibes-peer-sdk';
import { normalizeAuthToken } from '@pellux/goodvibes-transport-http';
import { createEventSourceConnector, createRemoteRuntimeEvents, createWebSocketConnector, } from '@pellux/goodvibes-transport-realtime';
import { createGoodVibesAuthClient, createMemoryTokenStore, } from './auth.js';
import { AutoRefreshCoordinator, createAutoRefreshMiddleware, } from './client-auth/index.js';
function requireBaseUrl(baseUrl) {
    const normalized = baseUrl.trim();
    if (!normalized) {
        throw new ConfigurationError('GoodVibes baseUrl is required. Pass a non-empty string in your createGoodVibesSdk options.');
    }
    return normalized;
}
function requireFetchImplementation(fetchImpl) {
    const resolved = fetchImpl ?? globalThis.fetch;
    if (typeof resolved !== 'function') {
        throw new ConfigurationError('Fetch implementation is required. Pass options.fetch or use a runtime that provides global fetch.');
    }
    return resolved;
}
function requireWebSocketImplementation(webSocketImpl) {
    const resolved = webSocketImpl ?? globalThis.WebSocket;
    if (typeof resolved !== 'function') {
        throw new ConfigurationError('WebSocket implementation is required. Pass options.WebSocketImpl or use a runtime that provides global WebSocket.');
    }
    return resolved;
}
function createClientOptions(options) {
    const getAuthToken = options.tokenStore
        ? () => options.tokenStore.getToken()
        : options.getAuthToken;
    return {
        baseUrl: requireBaseUrl(options.baseUrl),
        authToken: options.authToken ?? null,
        ...(getAuthToken ? { getAuthToken } : {}),
        ...(options.fetch ? { fetch: options.fetch } : {}),
        ...(options.headers ? { headers: options.headers } : {}),
        ...(options.getHeaders ? { getHeaders: options.getHeaders } : {}),
        ...(options.retry ? { retry: options.retry } : {}),
        ...(options.middleware ? { middleware: options.middleware } : {}),
        // The `as T` cast is necessary because the return type is generic (T extends
        // a union of ClientOptions subtypes). The object literal satisfies all members
        // of GoodVibesSdkOptions; T is always a structural subset of that shape.
    };
}
/**
 * Create a GoodVibes SDK instance.
 *
 * This is the runtime-agnostic constructor. For environments with sensible
 * defaults already configured, prefer the platform-specific wrappers:
 * `createBrowserGoodVibesSdk`, `createReactNativeGoodVibesSdk`.
 *
 * @see https://github.com/mgd34msu/goodvibes-sdk/blob/main/docs/getting-started.md
 *
 * @example
 * // Example only: replace baseUrl and authToken with your own values.
 * import { createGoodVibesSdk } from '@pellux/goodvibes-sdk';
 *
 * const sdk = createGoodVibesSdk({
 *   baseUrl: 'https://daemon.example.com',
 *   authToken: process.env.GV_TOKEN,
 * });
 *
 * const agents = await sdk.operator.agents.list();
 * console.log(agents);
 */
export function createGoodVibesSdk(options) {
    const baseUrl = requireBaseUrl(options.baseUrl);
    const tokenStore = options.tokenStore ?? (options.getAuthToken ? null : createMemoryTokenStore(options.authToken ?? null));
    const getAuthToken = tokenStore
        ? () => tokenStore.getToken()
        : options.getAuthToken;
    // Single normalized resolver used by realtime connectors. Invoked per-connect
    // (not once at construction) so the latest token is always used.
    const tokenResolver = normalizeAuthToken(getAuthToken ?? options.authToken ?? undefined);
    const fetchImpl = () => requireFetchImplementation(options.fetch);
    const { observer } = options;
    // Build the auto-refresh coordinator when a writable token store is present
    // and autoRefresh is not explicitly disabled.
    const autoRefreshEnabled = (options.autoRefresh?.autoRefresh ?? true) && tokenStore !== null;
    const coordinator = autoRefreshEnabled && tokenStore
        ? new AutoRefreshCoordinator({
            tokenStore,
            autoRefresh: true,
            refreshLeewayMs: options.autoRefresh?.refreshLeewayMs ?? 60_000,
            refresh: options.autoRefresh?.refresh,
            observer,
        })
        : null;
    // Build the merged middleware list: auto-refresh first (if enabled), then
    // consumer-provided middleware. This ensures the auto-refresh pre-flight runs
    // before any consumer middleware sees ctx.headers.Authorization, and that the
    // reactive 401 retry is transparent to consumer middleware.
    //
    // Lazy transport proxy: populated immediately after createOperatorSdk /
    // createPeerSdk returns. The middleware only invokes requestJson on a reactive
    // 401 — never at construction time — so the holder is always populated before
    // it is accessed. `let` is required here because the reference is reassigned
    // after the SDK instance is created (not const-initializable at declaration).
    let operatorRequestJson = null;
    let peerRequestJson = null;
    const buildMiddleware = (getRequestJson) => {
        const mws = [];
        if (coordinator) {
            // Wrap the coordinator + lazy transport reference into a minimal proxy
            // that satisfies createAutoRefreshMiddleware's Pick<HttpJsonTransport, 'requestJson'>.
            const transportProxy = {
                requestJson(url, opts) {
                    const rj = getRequestJson();
                    if (!rj)
                        throw new GoodVibesSdkError('Auto-refresh: transport not yet initialised', { category: 'internal', source: 'runtime', recoverable: false });
                    return rj(url, opts);
                },
            };
            mws.push(createAutoRefreshMiddleware(coordinator, transportProxy, tokenStore));
        }
        if (options.middleware) {
            mws.push(...options.middleware);
        }
        return mws;
    };
    const operator = createOperatorSdk(createClientOptions({
        ...options,
        tokenStore: tokenStore ?? undefined,
        middleware: buildMiddleware(() => operatorRequestJson),
    }));
    // Populate the lazy reference now that the transport is fully constructed.
    operatorRequestJson = (url, opts) => operator.transport.requestJson(url, opts);
    const peer = createPeerSdk(createClientOptions({
        ...options,
        tokenStore: tokenStore ?? undefined,
        middleware: buildMiddleware(() => peerRequestJson),
    }));
    // Populate the lazy reference now that the transport is fully constructed.
    peerRequestJson = (url, opts) => peer.transport.requestJson(url, opts);
    return {
        operator,
        peer,
        auth: createGoodVibesAuthClient(operator, tokenStore, getAuthToken, observer, options.autoRefresh, coordinator),
        use(middleware) {
            operator.transport.use(middleware);
            peer.transport.use(middleware);
        },
        realtime: {
            viaSse() {
                return createRemoteRuntimeEvents(createEventSourceConnector(baseUrl, tokenResolver, fetchImpl(), {
                    reconnect: options.realtime?.sseReconnect,
                    observer,
                }), {
                    observer,
                    onError: (error) => options.realtime?.onError?.(error),
                });
            },
            viaWebSocket(webSocketImpl) {
                return createRemoteRuntimeEvents(createWebSocketConnector(baseUrl, tokenResolver, requireWebSocketImplementation(webSocketImpl ?? options.WebSocketImpl), {
                    reconnect: options.realtime?.webSocketReconnect,
                    observer,
                }), {
                    observer,
                    onError: (error) => options.realtime?.onError?.(error),
                });
            },
        },
    };
}
