// Local type aliases matching the contracts in @repo/core-shared/instrumentation.
// Kept inline to avoid a build-graph cycle between core-testing and core-shared.
type AttributeValue = string | number | boolean | null;

type SpanOpts = {
  name: string;
  op?: string;
  attributes?: Record<string, AttributeValue>;
};

interface ISpan {
  setAttribute(key: string, value: AttributeValue): void;
  setStatus(status: "ok" | "error", message?: string): void;
}

interface ITracer {
  startSpan<T>(opts: SpanOpts, fn: (span: ISpan) => Promise<T>): Promise<T>;
}

export type RecordedSpan = {
  name: string;
  op?: string;
  attributes: Record<string, AttributeValue>;
  status: "ok" | "error";
  statusMessage?: string;
  durationMs: number;
};

// Exported so callers can use it as a compatible ITracer via structural typing.
export class RecordingTracer implements ITracer {
  spans: RecordedSpan[] = [];

  async startSpan<T>(opts: SpanOpts, fn: (span: ISpan) => Promise<T>): Promise<T> {
    const start = performance.now();
    const recorded: RecordedSpan = {
      name: opts.name,
      op: opts.op,
      attributes: { ...(opts.attributes ?? {}) },
      status: "ok",
      durationMs: 0,
    };
    const span: ISpan = {
      setAttribute(key, value) {
        recorded.attributes[key] = value;
      },
      setStatus(status, message) {
        recorded.status = status;
        recorded.statusMessage = message;
      },
    };
    try {
      const result = await fn(span);
      recorded.durationMs = performance.now() - start;
      this.spans.push(recorded);
      return result;
    } catch (err) {
      recorded.status = "error";
      recorded.statusMessage = err instanceof Error ? err.message : String(err);
      recorded.durationMs = performance.now() - start;
      this.spans.push(recorded);
      throw err;
    }
  }

  reset(): void {
    this.spans = [];
  }

  findSpan(name: string): RecordedSpan | undefined {
    return this.spans.find((s) => s.name === name);
  }
}
