/**
 * @wrnexus/tracking — error tracking with pluggable sinks. Capture exceptions
 * manually or via middleware, and fan them out to any sink (console by default;
 * write a small sink to forward to Sentry/Datadog/etc.).
 *
 *   const tracker = createTracker({ sinks: [consoleSink] });
 *   app-middleware: tracker.middleware()   // captures + re-throws request errors
 *   tracker.capture(err, { userId });      // manual
 */

import type { Context, Middleware } from "@wrnexus/core";

export interface ErrorEvent {
  error: Error;
  /** Arbitrary structured context (request info, user id, tags…). */
  context: Record<string, unknown>;
  /** Epoch ms. */
  timestamp: number;
}

export interface ErrorSink {
  name?: string;
  capture(event: ErrorEvent): void | Promise<void>;
}

export interface Tracker {
  capture(error: unknown, context?: Record<string, unknown>): Promise<void>;
  addSink(sink: ErrorSink): void;
  /** Middleware that captures errors thrown downstream, then re-throws them. */
  middleware(): Middleware;
}

export interface TrackerOptions {
  sinks?: ErrorSink[];
  now?: () => number;
  /** Scrub/enrich an event before it hits sinks (return null to drop it). */
  beforeSend?: (event: ErrorEvent) => ErrorEvent | null;
}

/** A sink that logs a compact one-line error to the console. */
export const consoleSink: ErrorSink = {
  name: "console",
  capture(event) {
    const ctx = Object.keys(event.context).length ? ` ${JSON.stringify(event.context)}` : "";
    console.error(`[error] ${event.error.name}: ${event.error.message}${ctx}`);
  },
};

function toError(value: unknown): Error {
  if (value instanceof Error) return value;
  const err = new Error(typeof value === "string" ? value : JSON.stringify(value));
  err.name = "NonError";
  return err;
}

export function createTracker(options: TrackerOptions = {}): Tracker {
  const sinks = [...(options.sinks ?? [])];
  const now = options.now ?? Date.now;

  const capture: Tracker["capture"] = async (error, context = {}) => {
    let event: ErrorEvent | null = { error: toError(error), context, timestamp: now() };
    if (options.beforeSend) event = options.beforeSend(event);
    if (!event) return;
    await Promise.all(
      sinks.map(async (sink) => {
        try {
          await sink.capture(event as ErrorEvent);
        } catch {
          /* a failing sink must never break the app */
        }
      }),
    );
  };

  return {
    capture,
    addSink(sink) {
      sinks.push(sink);
    },
    middleware(): Middleware {
      return async (ctx: Context, next) => {
        try {
          return await next();
        } catch (error) {
          await capture(error, {
            method: ctx.req.method,
            path: ctx.url.pathname,
            requestId: ctx.locals.requestId,
          });
          throw error; // let the framework's error handler produce the response
        }
      };
    },
  };
}
export { createTelemetryPipeline, telemetryConsoleSink } from "./pipeline.ts";
export type {
  TelemetryKind,
  TelemetryEnvelope,
  TelemetrySink,
  TelemetryPipeline,
  TelemetryPipelineOptions,
} from "./pipeline.ts";
