/**
 * SDKObserver — first-class observability hooks for the GoodVibes SDK.
 *
 * Consumers can implement any subset of `SDKObserver` and pass the instance
 * via the `observer` option on supported client factories. All methods are
 * optional; the SDK works identically whether an observer is present or not.
 *
 * Observer call sites are wrapped through `invokeObserver`: an observer that
 * throws will not propagate into SDK logic. Failures are reported and returned
 * so observer isolation does not hide broken callbacks.
 *
 * @example
 * const sdk = createGoodVibesSdk({
 *   baseUrl: 'https://daemon.example.com',
 *   tokenStore: createMemoryTokenStore(),
 *   observer: createConsoleObserver({ level: 'debug' }),
 * });
 */
/** OpenTelemetry SpanStatusCode.ERROR. Use in custom observer implementations with `span.setStatus({ code: SPAN_STATUS_ERROR })`. */
export const SPAN_STATUS_ERROR = 2;
function reportObserverFailure(error, label) {
    try {
        const suffix = label ? `: ${label}` : '';
        globalThis.console?.warn?.(`[sdk:observer] observer callback failed${suffix}`, error);
    }
    catch {
        // Logging must not become another way for observer isolation to break SDK flow.
    }
}
/**
 * Safely invoke an observer method. Observer errors are isolated so they
 * never disrupt SDK control flow, but failures are returned and logged by
 * default. This is the canonical call pattern for all observer call sites
 * throughout the SDK.
 *
 * @param fn - Zero-argument thunk wrapping the observer call.
 */
export function invokeObserver(fn, options = {}) {
    try {
        fn();
        return { ok: true };
    }
    catch (error) {
        if (options.report !== false) {
            reportObserverFailure(error, options.label);
        }
        return { ok: false, error };
    }
}
/**
 * Create a development-friendly observer that logs SDK activity to the console.
 *
 * Use `level: 'debug'` to also log transport activity and every runtime event.
 *
 * @example
 * const sdk = createGoodVibesSdk({
 *   baseUrl: '...',
 *   observer: createConsoleObserver({ level: 'debug' }),
 * });
 */
export function createConsoleObserver(options = {}) {
    const level = options.level ?? 'info';
    return {
        /* eslint-disable no-console */
        onAuthTransition(transition) {
            console.log(`[sdk:observer] auth transition ${transition.from} → ${transition.to} (${transition.reason})`);
        },
        onError(err) {
            console.error(`[sdk:observer] error kind=${err.kind} category=${err.category}`, err.message);
        },
        onTransportActivity(activity) {
            if (level !== 'debug')
                return;
            const status = activity.status !== undefined ? ` status=${activity.status}` : '';
            const dur = activity.durationMs !== undefined ? ` ${activity.durationMs}ms` : '';
            console.debug(`[sdk:observer] transport ${activity.direction} ${activity.kind ?? 'http'} ${activity.url}${status}${dur}`);
        },
        onEvent(event) {
            if (level !== 'debug')
                return;
            console.debug(`[sdk:observer] event ${event.type}`);
        },
        /* eslint-enable no-console */
    };
}
/**
 * Create an observer that emits OpenTelemetry spans and metrics.
 *
 * Pass a pre-configured `Tracer` and `Meter` from your OpenTelemetry SDK setup.
 * This adapter has no hard dependency on `@opentelemetry/*` — it accepts the
 * abstractions defined above, which match the subset of the real
 * OpenTelemetry API.
 *
 * @example
 * import { trace, metrics } from '@opentelemetry/api';
 *
 * const observer = createOpenTelemetryObserver(
 *   trace.getTracer('goodvibes-sdk'),
 *   metrics.getMeter('goodvibes-sdk'),
 * );
 * const sdk = createGoodVibesSdk({ baseUrl: '...', observer });
 */
export function createOpenTelemetryObserver(tracer, meter) {
    const authCounter = meter.createCounter('sdk.auth.transitions', {
        description: 'Number of SDK auth state transitions',
    });
    const errorCounter = meter.createCounter('sdk.errors', {
        description: 'Number of SDK errors observed',
    });
    const transportHistogram = meter.createHistogram('sdk.transport.duration_ms', {
        description: 'SDK transport call duration',
        unit: 'ms',
    });
    return {
        onAuthTransition(transition) {
            authCounter.add(1, {
                from: transition.from,
                to: transition.to,
                reason: transition.reason,
            });
            const span = tracer.startSpan('sdk.auth.transition');
            span.setAttribute('sdk.auth.from', transition.from);
            span.setAttribute('sdk.auth.to', transition.to);
            span.setAttribute('sdk.auth.reason', transition.reason);
            span.end();
        },
        onError(err) {
            errorCounter.add(1, { kind: err.kind, category: err.category });
            const span = tracer.startSpan('sdk.error');
            span.setAttribute('sdk.error.kind', err.kind);
            span.setAttribute('sdk.error.category', err.category);
            span.setAttribute('sdk.error.recoverable', err.recoverable);
            span.recordException(err);
            span.setStatus({ code: SPAN_STATUS_ERROR, message: err.message });
            span.end();
        },
        onTransportActivity(activity) {
            if (activity.direction !== 'recv')
                return; // only record completed calls
            if (activity.durationMs !== undefined) {
                transportHistogram.record(activity.durationMs, {
                    kind: activity.kind ?? 'http',
                    ...(activity.status !== undefined && { status: activity.status }),
                });
            }
        },
        onEvent(_event) {
            // Event counting is high-cardinality; consumers can add their own meter
            // by wrapping this observer if they need per-event metrics.
        },
    };
}
