// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import type { FinalRequestOptions } from './request-options';
import { Stream } from '../core/streaming';
import { type BaseAnthropic } from '../client';
import { formatRequestDetails, loggerFor } from './utils/log';
import { releaseRequestSignal } from './request-signal';
import type { AbstractPage } from '../core/pagination';

export type APIResponseProps = {
  response: Response;
  options: FinalRequestOptions;
  controller: AbortController;
  requestLogID: string;
  retryOfRequestLogID: string | undefined;
  startTime: number;
};

export async function defaultParseResponse<T>(
  client: BaseAnthropic,
  props: APIResponseProps,
): Promise<WithRequestID<T>> {
  const { response, requestLogID, retryOfRequestLogID, startTime } = props;
  const body = await (async () => {
    if (props.options.stream) {
      loggerFor(client).debug('response', response.status, response.url, response.headers, response.body);

      // Note: there is an invariant here that isn't represented in the type system
      // that if you set `stream: true` the response type must also be `Stream<T>`

      return Stream.fromSSEResponse(response, props.controller) as any;
    }

    // fetch refuses to read the body when the status code is 204.
    if (response.status === 204) {
      return null as T;
    }

    if (props.options.__binaryResponse) {
      return response as unknown as T;
    }

    const contentType = response.headers.get('content-type');
    const mediaType = contentType?.split(';')[0]?.trim();
    const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json');
    if (isJSON) {
      const contentLength = response.headers.get('content-length');
      if (contentLength === '0') {
        // if there is no content we can't do anything
        return undefined as T;
      }

      const json = await response.json();
      return addRequestID(json as T, response);
    }

    const text = await response.text();
    return text as unknown as T;
  })().finally(() => {
    // The body is settled (or parsing threw), so the caller-signal abort
    // listener has nothing left to cancel. Streams release in their own
    // teardown; a raw Response (`__binaryResponse`) keeps the listener so
    // aborting an in-flight download still works.
    if (!props.options.stream && !props.options.__binaryResponse) {
      releaseRequestSignal(props.controller);
    }
  });
  loggerFor(client).debug(
    `[${requestLogID}] response parsed`,
    formatRequestDetails({
      retryOfRequestLogID,
      url: response.url,
      status: response.status,
      body,
      durationMs: Date.now() - startTime,
    }),
  );
  return body;
}

export type WithRequestID<T> =
  T extends Array<any> | Response | AbstractPage<any> ? T
  : T extends Record<string, any> ? T & { _request_id?: string | null }
  : T;

export function addRequestID<T>(value: T, response: Response): WithRequestID<T> {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return value as WithRequestID<T>;
  }

  return Object.defineProperty(value, '_request_id', {
    value: response.headers.get('request-id'),
    enumerable: false,
  }) as WithRequestID<T>;
}
