/**
 * Isolated tracer provider support for Autotel
 *
 * Allows Autotel to use a separate TracerProvider instance, avoiding conflicts
 * with other OpenTelemetry instrumentation in the application.
 *
 * **Use Case:** Library authors who want to use Autotel without interfering
 * with the application's global OpenTelemetry setup.
 *
 * **Limitation:** While this isolates span processing and export, OpenTelemetry
 * context (trace IDs, parent spans) is still shared globally. Spans created with
 * the isolated provider may inherit trace context from global spans.
 */

import { trace } from '@opentelemetry/api';
import type { TracerProvider } from '@opentelemetry/api';

/**
 * Symbol for storing isolated tracer provider in global scope
 * Using Symbol.for() ensures the same symbol across module boundaries
 */
const AUTOTEL_GLOBAL_SYMBOL = Symbol.for('autotel');

/**
 * Global state for Autotel
 */
type AutotelGlobalState = {
  isolatedTracerProvider: TracerProvider | null;
};

/**
 * Create initial state
 */
function createState(): AutotelGlobalState {
  return {
    isolatedTracerProvider: null,
  };
}

/**
 * Extend globalThis to include our symbol
 */
interface GlobalThis {
  [AUTOTEL_GLOBAL_SYMBOL]?: AutotelGlobalState;
}

/**
 * Get the global state, creating it if it doesn't exist
 * Handles edge cases like missing globalThis
 */
function getGlobalState(): AutotelGlobalState {
  const initialState = createState();

  try {
    const g = globalThis as typeof globalThis & GlobalThis;

    if (typeof g !== 'object' || g === null) {
      console.warn(
        '[autotel] globalThis is not available, using fallback state',
      );
      return initialState;
    }

    if (!g[AUTOTEL_GLOBAL_SYMBOL]) {
      Object.defineProperty(g, AUTOTEL_GLOBAL_SYMBOL, {
        value: initialState,
        writable: false, // Lock the slot (not the contents)
        configurable: false,
        enumerable: false,
      });
    }

    return g[AUTOTEL_GLOBAL_SYMBOL]!;
  } catch (error) {
    if (error instanceof Error) {
      console.error(
        `[autotel] Failed to access global state: ${error.message}`,
      );
    } else {
      console.error(
        `[autotel] Failed to access global state: ${String(error)}`,
      );
    }

    return initialState;
  }
}

/**
 * Sets an isolated TracerProvider for Autotel tracing operations.
 *
 * This allows Autotel to use its own TracerProvider instance, separate from
 * the global OpenTelemetry TracerProvider. This is useful for avoiding conflicts
 * with other OpenTelemetry instrumentation in the application.
 *
 * **Limitation: Span Context Sharing**
 *
 * While this function isolates span processing and export, it does NOT provide
 * complete trace isolation. OpenTelemetry context (trace IDs, parent spans) is
 * still shared between the global and isolated providers. This means:
 *
 * - Spans created with the isolated provider inherit trace IDs from global spans
 * - Spans created with the isolated provider inherit parent relationships from global spans
 * - This can result in spans from different providers being part of the same logical trace
 *
 * **Why this happens:**
 * OpenTelemetry uses a global context propagation mechanism that operates at the
 * JavaScript runtime level, independent of individual TracerProvider instances.
 * The context (containing trace ID, span ID) flows through async boundaries and
 * is inherited by all spans created within that context, regardless of which
 * TracerProvider creates them.
 *
 * **When to use this:**
 * - Library code that ships with embedded Autotel
 * - SDKs that want observability without requiring users to set up OpenTelemetry
 * - Applications that need separate span processing for different subsystems
 * - Testing scenarios where you want to isolate trace collection
 *
 * @param provider - The TracerProvider instance to use, or null to clear the isolated provider
 *
 * @example Library with embedded Autotel
 * ```typescript
 * import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
 * import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
 * import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
 * import { setAutolem

etryTracerProvider } from 'autotel/tracer-provider'
 *
 * // Create provider with span processors in constructor
 * const exporter = new OTLPTraceExporter({
 *   url: 'https://your-backend.com/v1/traces'
 * })
 *
 * const provider = new NodeTracerProvider()
 * provider.addSpanProcessor(new BatchSpanProcessor(exporter))
 *
 * // Set as Autotel's isolated provider (doesn't call provider.register())
 * setAutotelTracerProvider(provider)
 *
 * // Now all Autotel trace() calls use this provider
 * // But won't interfere with the application's global OpenTelemetry setup
 * ```
 *
 * @example Testing with isolated provider
 * ```typescript
 * import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
 * import { InMemorySpanExporter } from '@opentelemetry/sdk-trace-base'
 * import { setAutotelTracerProvider } from 'autotel/tracer-provider'
 *
 * // Test setup
 * const exporter = new InMemorySpanExporter()
 * const provider = new NodeTracerProvider()
 * provider.addSpanProcessor(new SimpleSpanProcessor(exporter))
 *
 * setAutotelTracerProvider(provider)
 *
 * // Run tests...
 * const spans = exporter.getFinishedSpans()
 *
 * // Cleanup
 * setAutotelTracerProvider(null)
 * ```
 *
 * @example Multiple subsystems with different exporters
 * ```typescript
 * import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
 * import { setAutotelTracerProvider } from 'autotel/tracer-provider'
 *
 * // Payment subsystem - send to payment team's backend
 * const paymentProvider = new NodeTracerProvider()
 * paymentProvider.addSpanProcessor(new BatchSpanProcessor(
 *   new OTLPTraceExporter({ url: 'https://payment-team-backend.com/v1/traces' })
 * ))
 *
 * // In payment module initialization
 * setAutotelTracerProvider(paymentProvider)
 * ```
 *
 * @public
 */
export function setAutotelTracerProvider(
  provider: TracerProvider | null,
): void {
  getGlobalState().isolatedTracerProvider = provider;
}

/**
 * Gets the TracerProvider for Autotel tracing operations.
 *
 * Returns the isolated TracerProvider if one has been set via setAutotelTracerProvider(),
 * otherwise falls back to the global OpenTelemetry TracerProvider.
 *
 * This function is used internally by Autotel's trace functions. Most users
 * will not need to call this directly.
 *
 * @returns The TracerProvider instance to use for Autotel tracing
 *
 * @example Getting the current provider
 * ```typescript
 * import { getAutotelTracerProvider } from 'autotel/tracer-provider'
 *
 * const provider = getAutotelTracerProvider()
 * const tracer = provider.getTracer('my-service', '1.0.0')
 * ```
 *
 * @example Checking if isolated provider is active
 * ```typescript
 * import { getAutotelTracerProvider, setAutotelTracerProvider } from 'autotel/tracer-provider'
 * import { trace } from '@opentelemetry/api'
 *
 * const currentProvider = getAutotelTracerProvider()
 * const globalProvider = trace.getTracerProvider()
 *
 * if (currentProvider === globalProvider) {
 *   console.log('Using global provider')
 * } else {
 *   console.log('Using isolated provider')
 * }
 * ```
 *
 * @public
 */
export function getAutotelTracerProvider(): TracerProvider {
  const { isolatedTracerProvider } = getGlobalState();

  if (isolatedTracerProvider) return isolatedTracerProvider;

  return trace.getTracerProvider();
}

/**
 * Gets the OpenTelemetry tracer instance for Autotel.
 *
 * This function returns a tracer specifically configured for Autotel
 * with the correct tracer name and version. Used internally by all
 * Autotel tracing functions to ensure consistent trace creation.
 *
 * Uses the isolated provider if set, otherwise uses the global provider.
 *
 * @param name - Tracer name (default: 'autotel')
 * @param version - Optional version string
 * @returns The Autotel OpenTelemetry tracer instance
 *
 * @example Basic usage
 * ```typescript
 * import { getAutotelTracer } from 'autotel/tracer-provider'
 *
 * const tracer = getAutotelTracer()
 * const span = tracer.startSpan('my-operation')
 * // ... use span
 * span.end()
 * ```
 *
 * @example Custom tracer name
 * ```typescript
 * import { getAutotelTracer } from 'autotel/tracer-provider'
 *
 * const tracer = getAutotelTracer('my-library', '2.1.0')
 * ```
 *
 * @public
 */
export function getAutotelTracer(name = 'autotel', version?: string) {
  return getAutotelTracerProvider().getTracer(name, version);
}
