import { toJsonObject, toJsonValue } from './internal/json-safe.js'
import type { JsonValue } from './internal/json-safe.js'

export type { JsonValue } from './internal/json-safe.js'

export interface ErrorReportError {
  name: string
  message: string
  stack?: string
  cause?: JsonValue
  value?: JsonValue
}

export interface ErrorReportEvent {
  timestamp: string
  error: ErrorReportError
  context: Record<string, JsonValue>
}

export interface ErrorReporterTransport {
  send(event: ErrorReportEvent): void | Promise<void>
}

export type ErrorReportResult =
  | { ok: true }
  | { ok: false; transportError: unknown }

export interface ErrorReporter {
  report(
    error: unknown,
    context?: Readonly<Record<string, unknown>>,
  ): Promise<ErrorReportResult>
}

export interface ErrorReporterOptions {
  transport: ErrorReporterTransport
  context?: Readonly<Record<string, unknown>>
  now?: () => Date
}

function normalizeError(error: unknown): ErrorReportError {
  if (error instanceof Error) {
    const normalized = toJsonValue(error)
    const object = normalized && !Array.isArray(normalized) && typeof normalized === 'object'
      ? normalized as Record<string, JsonValue>
      : {}

    return {
      name: typeof object.name === 'string' ? object.name : error.name,
      message: typeof object.message === 'string' ? object.message : error.message,
      ...(typeof object.stack === 'string' ? { stack: object.stack } : {}),
      ...(object.cause !== undefined ? { cause: object.cause } : {}),
    }
  }

  const value = toJsonValue(error)
  return {
    name: 'NonErrorThrown',
    message: typeof error === 'string' ? error : 'Non-Error value thrown',
    ...(value === undefined ? {} : { value }),
  }
}

export function createErrorReporter(options: ErrorReporterOptions): ErrorReporter {
  const now = options.now ?? (() => new Date())
  const baseContext = toJsonObject(options.context)

  return {
    async report(error, context) {
      const event: ErrorReportEvent = {
        timestamp: now().toISOString(),
        error: normalizeError(error),
        context: {
          ...baseContext,
          ...toJsonObject(context),
        },
      }

      try {
        await options.transport.send(event)
        return { ok: true }
      } catch (transportError) {
        return { ok: false, transportError }
      }
    },
  }
}
