import type { components } from './generated/schema'

export type EntityType =
  | 'activity'
  | 'agent'
  | 'agent-proposal'
  | 'project'
  | 'task'
  | 'run'
  | 'cron'
  | 'incident'
  | 'document'
  | 'notification'
  | 'task-board'
  | 'openclaw-task'
  | 'session'
  | 'approval'
  | 'config'
  | 'agent-file'
  | 'event-stream'

export type EntityRefDto =
  Omit<components['schemas']['EntityRefDto'], 'type'>
  & { type: EntityType }

export interface OperationResultDto
  extends Omit<
    components['schemas']['OperationResultDto'],
    'revision' | 'primaryRef' | 'affectedRefs'
  > {
  revision: number
  primaryRef: EntityRefDto | null
  affectedRefs: EntityRefDto[]
}

export interface DomainEventDto<TPayload = unknown> {
  sequence: number
  eventType: string
  occurredAt: string
  entity: EntityRefDto
  entityRevision: number
  payload: TPayload
}

export function normalizeOperationResult(
  value: components['schemas']['OperationResultDto'] | null | undefined,
): OperationResultDto | null {
  if (!value) return null
  return {
    ...value,
    revision: Number(value.revision),
    primaryRef: value.primaryRef
      ? { ...value.primaryRef, type: value.primaryRef.type as EntityType }
      : null,
    affectedRefs: value.affectedRefs.map(entity => ({
      ...entity,
      type: entity.type as EntityType,
    })),
  }
}

export type ProblemDetailsDto = Omit<
  components['schemas']['ProblemDetails'],
  | 'status'
  | 'code'
  | 'traceId'
  | 'operationId'
  | 'currentRevision'
  | 'retryAfterSeconds'
  | 'remaining'
> & {
  status?: number
  code?: string
  traceId?: string
  operationId?: string
  currentRevision?: number
  retryAfterSeconds?: number
  remaining?: number
  errors?: Record<string, string[]>
}

export type AppProblemKind =
  | 'validation'
  | 'authentication'
  | 'permission'
  | 'conflict'
  | 'unsupported'
  | 'offline'
  | 'timeout'
  | 'rate-limit'
  | 'not-found'
  | 'internal'

export type AppRecoveryAction =
  | 'retry'
  | 'reauthenticate'
  | 'reload-conflict'
  | 'inspect-connection'
  | 'none'

export class AppProblem extends Error {
  readonly status: number
  readonly problem: ProblemDetailsDto | null
  readonly code: string
  readonly kind: AppProblemKind
  readonly traceId: string | null
  readonly operationId: string | null
  readonly currentRevision: number | null
  readonly retryAfterSeconds: number | null
  readonly recoveryAction: AppRecoveryAction

  constructor(status: number, problem: ProblemDetailsDto | null, fallback: string) {
    super(problem?.detail || problem?.title || fallback)
    this.name = 'AppProblem'
    this.status = status
    this.problem = problem
    this.code = problem?.code || codeForStatus(status)
    this.kind = kindForCode(this.code, status)
    this.traceId = problem?.traceId || null
    this.operationId = problem?.operationId || null
    this.currentRevision = finiteNumber(problem?.currentRevision)
    this.retryAfterSeconds = finiteNumber(problem?.retryAfterSeconds)
    this.recoveryAction = recoveryForKind(this.kind)
  }
}

/** @deprecated Prefer AppProblem. Kept while API modules migrate. */
export class ApiProblem extends AppProblem {
  constructor(status: number, problem: ProblemDetailsDto | null, fallback: string) {
    super(status, problem, fallback)
    this.name = 'ApiProblem'
  }
}

function finiteNumber(value: unknown): number | null {
  if (value === null || value === undefined || value === '') return null
  const normalized = Number(value)
  return Number.isFinite(normalized) ? normalized : null
}

function codeForStatus(status: number): string {
  if (status === 400 || status === 422) return 'validation_failed'
  if (status === 401) return 'unauthenticated'
  if (status === 403) return 'forbidden'
  if (status === 404) return 'not_found'
  if (status === 409) return 'conflict'
  if (status === 429) return 'rate_limited'
  if (status === 501) return 'unsupported_capability'
  if (status === 502 || status === 503 || status === 0) return 'dependency_unavailable'
  if (status === 504) return 'timeout'
  return 'internal_error'
}

function kindForCode(code: string, status: number): AppProblemKind {
  if (code === 'validation_failed') return 'validation'
  if (code === 'unauthenticated' || status === 401) return 'authentication'
  if (code === 'forbidden' || status === 403) return 'permission'
  if (code === 'conflict' || status === 409) return 'conflict'
  if (code === 'unsupported_capability' || status === 501) return 'unsupported'
  if (code === 'dependency_unavailable' || status === 0 || status === 502 || status === 503) return 'offline'
  if (code === 'timeout' || status === 504) return 'timeout'
  if (code === 'rate_limited' || status === 429) return 'rate-limit'
  if (code === 'not_found' || status === 404) return 'not-found'
  return status >= 500 ? 'internal' : 'validation'
}

function recoveryForKind(kind: AppProblemKind): AppRecoveryAction {
  if (kind === 'authentication') return 'reauthenticate'
  if (kind === 'conflict') return 'reload-conflict'
  if (kind === 'offline' || kind === 'unsupported') return 'inspect-connection'
  if (kind === 'permission' || kind === 'not-found') return 'none'
  return 'retry'
}

export function asProblemDetails(value: unknown, status?: number): ProblemDetailsDto | null {
  if (!value || typeof value !== 'object') return null

  const source = value as Record<string, unknown>
  const detail = typeof source.detail === 'string'
    ? source.detail
    : typeof source.message === 'string'
      ? source.message
      : typeof source.error === 'string'
        ? source.error
        : undefined
  const normalizedStatus = finiteNumber(source.status) ?? status

  return {
    ...(source as ProblemDetailsDto),
    status: normalizedStatus ?? undefined,
    detail,
    code: typeof source.code === 'string' ? source.code : undefined,
    traceId: typeof source.traceId === 'string' ? source.traceId : undefined,
  }
}

export function toAppProblem(error: unknown, fallback = 'Die Anfrage ist fehlgeschlagen.'): AppProblem {
  if (error instanceof AppProblem) return error
  if (error instanceof DOMException && error.name === 'AbortError') {
    return new AppProblem(504, {
      code: 'timeout',
      detail: 'Die Anfrage wurde abgebrochen oder hat ihr Zeitlimit überschritten.',
    }, fallback)
  }
  if (error instanceof TypeError) {
    return new AppProblem(0, {
      code: 'dependency_unavailable',
      detail: error.message || 'Nexus konnte den Dienst nicht erreichen.',
    }, fallback)
  }
  if (error instanceof Error) {
    return new AppProblem(500, {
      code: 'internal_error',
      detail: error.message,
    }, fallback)
  }
  return new AppProblem(500, { code: 'internal_error', detail: fallback }, fallback)
}

export async function throwApiProblem(response: Response, fallback: string): Promise<never> {
  let problem: ProblemDetailsDto | null = null
  try {
    problem = asProblemDetails(await response.json(), response.status)
  } catch {
    // A structured ProblemDetails response is preferred, but legacy endpoints
    // can still return an empty error body during the migration.
  }
  throw new ApiProblem(response.status, problem, fallback)
}
