/**
 * @beignet/core/tracing
 *
 * W3C trace context primitives used to correlate Beignet activity across
 * requests, use cases, providers, and instrumentation sinks.
 *
 * This module is dependency-free so client bundles that import app context
 * types stay clean.
 */

const TRACEPARENT_PATTERN = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
const MAX_TRACESTATE_LENGTH = 512;
const MAX_TRACESTATE_MEMBERS = 32;
const TRACESTATE_SIMPLE_KEY_PATTERN = /^[a-z][a-z0-9_*/-]{0,255}$/;
const TRACESTATE_VENDOR_KEY_PATTERN =
  /^[a-z0-9][a-z0-9_*/-]{0,240}@[a-z][a-z0-9_*/-]{0,13}$/;
const TRACESTATE_VALUE_PATTERN =
  /^[\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e]$/;

/** Current version of Beignet's durable trace carrier. */
export const TRACE_CARRIER_VERSION = 1 as const;

/**
 * Vendor-neutral trace context stored in durable messages and transport
 * envelopes. Unknown versions and malformed values are ignored by consumers.
 */
export interface TraceCarrier {
  /** Carrier schema version. */
  readonly version: typeof TRACE_CARRIER_VERSION;
  /** W3C traceparent value captured at the producing boundary. */
  readonly traceparent: string;
  /** Optional W3C tracestate value captured at the producing boundary. */
  readonly tracestate?: string;
}

/**
 * Trace context used to correlate related activity.
 */
export interface TraceContext {
  /**
   * W3C trace ID.
   */
  traceId: string;
  /**
   * Current span ID.
   */
  spanId: string;
  /**
   * Parent span ID when available.
   */
  parentSpanId?: string;
  /**
   * W3C traceparent header value.
   */
  traceparent: string;
  /**
   * Optional W3C tracestate value associated with this context.
   */
  tracestate?: string;
}

/**
 * Parsed W3C traceparent header.
 */
export interface ParsedTraceparent {
  /**
   * W3C trace ID.
   */
  traceId: string;
  /**
   * Span ID from the traceparent header.
   */
  spanId: string;
  /**
   * Trace flags from the traceparent header.
   */
  traceFlags: string;
  /**
   * Normalized traceparent header value.
   */
  traceparent: string;
}

/**
 * Input accepted when creating trace context.
 */
export interface TraceContextInput {
  traceId?: string;
  spanId?: string;
  parentSpanId?: string;
  traceparent?: string;
  tracestate?: string;
}

/** Values accepted as tracing attributes. */
export type TraceAttributeValue =
  | string
  | number
  | boolean
  | readonly string[]
  | readonly number[]
  | readonly boolean[];

/** Attributes attached to a traced Beignet operation. */
export type TraceAttributes = Readonly<Record<string, TraceAttributeValue>>;

/** Span kinds understood by tracing providers. */
export type TraceSpanKind =
  | "internal"
  | "server"
  | "client"
  | "producer"
  | "consumer";

/** Stable Beignet operation categories used by tracing and metrics providers. */
export type TraceOperationType =
  | "request"
  | "useCase"
  | "agentCapability"
  | "listener"
  | "job"
  | "schedule"
  | "task"
  | "outbox"
  | "provider";

/** Description of one operation to run inside an active trace span. */
export interface TraceOperation {
  name: string;
  type: TraceOperationType;
  kind?: TraceSpanKind;
  parent?: TraceContextInput;
  /** Attributes attached to the operation span. */
  attributes?: TraceAttributes;
  /**
   * Bounded attributes attached to operation metrics. Values must describe a
   * stable operation dimension, never a request, actor, tenant, or payload.
   */
  metricAttributes?: TraceAttributes;
}

/** Framework-neutral span handle exposed to Beignet execution wrappers. */
export interface TraceSpan {
  readonly context: TraceContext;
  setAttribute(name: string, value: TraceAttributeValue): void;
  setAttributes(attributes: TraceAttributes): void;
  addEvent(name: string, attributes?: TraceAttributes): void;
  setStatus(status: "ok" | "error"): void;
  recordError(error: unknown): void;
}

/** Optional app port implemented by production tracing integrations. */
export interface TracingPort {
  current(): TraceContext | undefined;
  startActiveSpan<T>(operation: TraceOperation, run: (span: TraceSpan) => T): T;
}

function isObject(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null;
}

/** Return whether a value implements `TracingPort`. */
export function isTracingPort(value: unknown): value is TracingPort {
  return (
    isObject(value) &&
    "current" in value &&
    "startActiveSpan" in value &&
    typeof value.current === "function" &&
    typeof value.startActiveSpan === "function"
  );
}

/** Resolve a tracing port from a direct port, ports object, or app context. */
export function resolveTracingPort(target: unknown): TracingPort | undefined {
  if (!target) return undefined;
  if (isTracingPort(target)) return target;
  if (!isObject(target)) return undefined;

  const direct = "tracing" in target ? (target.tracing as unknown) : undefined;
  if (isTracingPort(direct)) return direct;

  const ports = "ports" in target ? target.ports : undefined;
  if (!ports || ports === target) return undefined;
  return resolveTracingPort(ports);
}

function validTracestate(value: unknown): string | undefined {
  if (typeof value !== "string") return undefined;
  const normalized = value.trim();
  if (normalized.length === 0 || normalized.length > MAX_TRACESTATE_LENGTH) {
    return undefined;
  }

  const members = normalized.split(",");
  if (members.length > MAX_TRACESTATE_MEMBERS) return undefined;

  const keys = new Set<string>();
  for (const rawMember of members) {
    const member = rawMember.trim();
    const separator = member.indexOf("=");
    if (separator <= 0) return undefined;

    const key = member.slice(0, separator).trim();
    const memberValue = member.slice(separator + 1).trimStart();
    if (
      (!TRACESTATE_SIMPLE_KEY_PATTERN.test(key) &&
        !TRACESTATE_VENDOR_KEY_PATTERN.test(key)) ||
      !TRACESTATE_VALUE_PATTERN.test(memberValue) ||
      keys.has(key)
    ) {
      return undefined;
    }
    keys.add(key);
  }

  return normalized;
}

/**
 * Parse an untrusted durable trace carrier.
 *
 * Invalid carriers return `undefined`; trace metadata must never prevent
 * message delivery.
 */
export function parseTraceCarrier(value: unknown): TraceCarrier | undefined {
  if (!isObject(value) || value.version !== TRACE_CARRIER_VERSION) {
    return undefined;
  }

  const parsed = parseTraceparent(
    typeof value.traceparent === "string" ? value.traceparent : undefined,
  );
  if (!parsed) return undefined;

  if (value.tracestate !== undefined) {
    const tracestate = validTracestate(value.tracestate);
    if (!tracestate) return undefined;
    return {
      version: TRACE_CARRIER_VERSION,
      traceparent: parsed.traceparent,
      tracestate,
    };
  }

  return {
    version: TRACE_CARRIER_VERSION,
    traceparent: parsed.traceparent,
  };
}

/**
 * Capture the current trace context from a tracing port, ports object, app
 * context, or explicit trace context. Returns `undefined` when no valid
 * context is available.
 */
export function captureTraceCarrier(target: unknown): TraceCarrier | undefined {
  let context: TraceContextInput | undefined;
  try {
    context = resolveTraceContextInput(target);
  } catch {
    // Context-like inputs may be proxy-backed and reject unknown properties.
  }

  try {
    context = resolveTracingPort(target)?.current() ?? context;
  } catch {
    // Tracing is best-effort and must not block the owning operation.
  }

  try {
    if (!context?.traceparent) return undefined;

    return parseTraceCarrier({
      version: TRACE_CARRIER_VERSION,
      traceparent: context.traceparent,
      ...(context.tracestate ? { tracestate: context.tracestate } : {}),
    });
  } catch {
    // Trace contexts are app-provided and remain best-effort inputs.
    return undefined;
  }
}

/** Resolve trace fields from a trace context or context-like object. */
export function resolveTraceContextInput(
  target: unknown,
): TraceContextInput | undefined {
  if (!isObject(target)) return undefined;

  const context = target as TraceContextInput;
  if (
    context.traceId ||
    context.spanId ||
    context.parentSpanId ||
    context.traceparent ||
    context.tracestate
  ) {
    return {
      ...(context.traceId ? { traceId: context.traceId } : {}),
      ...(context.spanId ? { spanId: context.spanId } : {}),
      ...(context.parentSpanId ? { parentSpanId: context.parentSpanId } : {}),
      ...(context.traceparent ? { traceparent: context.traceparent } : {}),
      ...(context.tracestate ? { tracestate: context.tracestate } : {}),
    };
  }

  const traceContext = "trace" in target ? target.trace : undefined;
  if (!traceContext || traceContext === target) return undefined;
  return resolveTraceContextInput(traceContext);
}

/** Run a callback inside a tracing span when an app tracing port is installed. */
export function runWithTracing<T>(
  target: unknown,
  operation: TraceOperation,
  run: (span?: TraceSpan) => T,
): T {
  const tracing = resolveTracingPort(target);
  if (!tracing) return run();
  return tracing.startActiveSpan(operation, (span) => run(span));
}

function createHexId(length: number): string {
  const bytes = new Uint8Array(length / 2);
  if (typeof crypto !== "undefined" && "getRandomValues" in crypto) {
    crypto.getRandomValues(bytes);
    return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(
      "",
    );
  }

  let value = "";
  while (value.length < length) {
    value += Math.floor(Math.random() * 16).toString(16);
  }
  return value.slice(0, length);
}

function isNonZeroHex(value: string): boolean {
  return !/^0+$/.test(value);
}

/**
 * Create a non-zero W3C trace ID.
 */
export function createTraceId(): string {
  let traceId = createHexId(32);
  while (!isNonZeroHex(traceId)) {
    traceId = createHexId(32);
  }
  return traceId;
}

/**
 * Create a non-zero W3C span ID.
 */
export function createSpanId(): string {
  let spanId = createHexId(16);
  while (!isNonZeroHex(spanId)) {
    spanId = createHexId(16);
  }
  return spanId;
}

/**
 * Create a W3C traceparent header value.
 */
export function createTraceparent(args: {
  traceId: string;
  spanId: string;
  traceFlags?: string;
}): string {
  return `00-${args.traceId}-${args.spanId}-${args.traceFlags ?? "01"}`;
}

/**
 * Parse and validate a W3C traceparent header value.
 */
export function parseTraceparent(
  value: string | null | undefined,
): ParsedTraceparent | undefined {
  if (!value) return undefined;
  const normalized = value.trim().toLowerCase();
  const match = TRACEPARENT_PATTERN.exec(normalized);
  if (!match) return undefined;

  const [, traceId, spanId, traceFlags] = match;
  if (!isNonZeroHex(traceId) || !isNonZeroHex(spanId)) return undefined;

  return {
    traceId,
    spanId,
    traceFlags,
    traceparent: normalized,
  };
}

/**
 * Create trace context from explicit IDs or an existing traceparent.
 */
export function createTraceContext(
  input: TraceContextInput = {},
): TraceContext {
  const parsed = parseTraceparent(input.traceparent);
  const traceId = input.traceId ?? parsed?.traceId ?? createTraceId();
  const parentSpanId = input.parentSpanId ?? parsed?.spanId;
  const spanId = input.spanId ?? createSpanId();

  return {
    traceId,
    spanId,
    ...(parentSpanId ? { parentSpanId } : {}),
    traceparent: createTraceparent({
      traceId,
      spanId,
      traceFlags: parsed?.traceFlags,
    }),
    ...(input.tracestate ? { tracestate: input.tracestate } : {}),
  };
}

/**
 * Create child trace context from a parent context.
 */
export function createChildTraceContext(
  parent: TraceContextInput,
): TraceContext {
  return createTraceContext({
    traceId: parent.traceId,
    parentSpanId: parent.spanId,
    traceparent: parent.traceparent,
    tracestate: parent.tracestate,
  });
}
