// ─── Manual Span Patterns for Node.js/TypeScript ───
// Use these patterns when auto-instrumentation doesn't cover your business logic.

import { trace, SpanStatusCode, SpanKind } from '@opentelemetry/api';

// Create a tracer for your module
const tracer = trace.getTracer('__MODULE_NAME__');

// ─── Pattern 1: Wrap async function with active span ───
// The span automatically becomes the active span in the context,
// so child spans created inside will be linked as children.

export async function processOrder(orderId: string) {
  return tracer.startActiveSpan(
    'processOrder',
    { attributes: { 'order.id': orderId } },
    async (span) => {
      try {
        // Your business logic here
        const result = await doWork(orderId);

        // Add attributes as you learn them
        span.setAttribute('order.status', result.status);
        span.setAttribute('order.items_count', result.items.length);

        return result;
      } catch (error) {
        // Record the exception and set error status
        span.recordException(error as Error);
        span.setStatus({
          code: SpanStatusCode.ERROR,
          message: (error as Error).message,
        });
        throw error;
      } finally {
        span.end();
      }
    }
  );
}

// ─── Pattern 2: Add attributes to current auto-instrumented span ───
// When auto-instrumentation already creates a span (e.g., HTTP handler),
// you can enrich it with custom attributes.

export function enrichCurrentSpan(key: string, value: string | number | boolean) {
  const currentSpan = trace.getActiveSpan();
  if (currentSpan) {
    currentSpan.setAttribute(key, value);
  }
}

// ─── Pattern 3: Database operation span (OTel DB semantic conventions) ───
// Span name: prefer `{db.query.summary}` when available (e.g., "SELECT users"); fallback to `{db.operation.name} {db.collection.name}`.
// Use current attribute names: db.system.name, db.operation.name, db.collection.name. Call get_semantic_conventions(database) for the full table including deprecated names.

export async function fetchUserFromDb(userId: string) {
  return tracer.startActiveSpan('SELECT users', {
    kind: SpanKind.CLIENT,
    attributes: {
      'db.system.name': 'postgresql',
      'db.operation.name': 'SELECT',
      'db.collection.name': 'users',
      'db.namespace': 'public',
      'user.id': userId,
    },
  }, async (span) => {
    try {
      // const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
      // return user;
      return {};
    } finally {
      span.end();
    }
  });
}

// ─── Pattern 4: Event logging within a span ───

export function logEvent(name: string, attributes?: Record<string, string>) {
  const span = trace.getActiveSpan();
  if (span) {
    span.addEvent(name, attributes);
  }
}

// ─── Pattern 5: RPC/gRPC span (OTel RPC semantic conventions) ───
// Span name = "{rpc.method}" (the value of rpc.method; for gRPC this is the fully-qualified "package.Service/Method" string, e.g., "mypackage.UserService/GetUser").
// Use rpc.system.name, rpc.service, rpc.method. Call get_semantic_conventions(rpc_client) for the full table.

export async function callUserService(userId: string) {
  return tracer.startActiveSpan('mypackage.UserService/GetUser', {
    kind: SpanKind.CLIENT,
    attributes: {
      'rpc.system.name': 'grpc',
      'rpc.service': 'mypackage.UserService',
      'rpc.method': 'GetUser',
      'server.address': 'user-service.internal',
      'server.port': 50051,
    },
  }, async (span) => {
    try {
      // const result = await grpcClient.getUser({ id: userId });
      // return result;
      return {};
    } catch (error) {
      span.recordException(error as Error);
      span.setStatus({ code: SpanStatusCode.ERROR, message: (error as Error).message });
      throw error;
    } finally {
      span.end();
    }
  });
}

// ─── Pattern 6: Messaging span (OTel Messaging semantic conventions) ───
// Span name = "{operation} {destination}" (e.g., "publish shop.orders").
// Use messaging.system, messaging.operation.name, messaging.destination.name.

export async function publishOrderEvent(orderId: string, event: { id: string }) {
  return tracer.startActiveSpan('publish shop.orders', {
    kind: SpanKind.PRODUCER,
    attributes: {
      'messaging.system': 'kafka',
      'messaging.operation.name': 'publish',
      'messaging.destination.name': 'shop.orders',
      'messaging.message.id': event.id,
      'server.address': 'kafka.internal',
    },
  }, async (span) => {
    try {
      // await producer.send({ topic: 'shop.orders', messages: [{ value: JSON.stringify(event) }] });
    } catch (error) {
      span.recordException(error as Error);
      span.setStatus({ code: SpanStatusCode.ERROR, message: (error as Error).message });
      throw error;
    } finally {
      span.end();
    }
  });
}

// Helper used in examples
async function doWork(_orderId: string) {
  return { status: 'completed', items: [] as string[] };
}
