import type { OpenAPIObject } from '@nestjs/swagger';

type SchemaObject = Exclude<
  Exclude<OpenAPIObject['components'], undefined>['schemas'],
  undefined
>[string];

const SUCCESS_ENVELOPE_NAME = 'ApiSuccessEnvelope';
const ERROR_ENVELOPE_NAME = 'ApiErrorEnvelope';

/**
 * Wrap every operation's response schema with the global API envelope so the
 * Scalar UI shows what callers actually receive at runtime:
 *   { code: number, msg: string, data: <inner> | null, details?: unknown }
 *
 * Adds two reusable schemas to components.schemas: ApiSuccessEnvelope and
 * ApiErrorEnvelope. Each operation's responses get rewritten:
 *   - 2xx → ApiSuccessEnvelope wrapping the original `data` schema
 *   - 4xx/5xx → ApiErrorEnvelope (data: null)
 *
 * Important: SSE endpoints (text/event-stream) are left untouched.
 */
export function wrapOpenApiResponses(doc: OpenAPIObject): OpenAPIObject {
  doc.components ??= {};
  doc.components.schemas ??= {};

  doc.components.schemas[ERROR_ENVELOPE_NAME] = {
    type: 'object',
    description: 'Wrapped error response. HTTP status is 200 unless the server crashed (5xx).',
    properties: {
      code: { type: 'integer', description: 'Non-zero business code (HTTP-status-style).' },
      msg: { type: 'string' },
      data: { type: 'object', nullable: true },
      details: { description: 'Optional structured detail (e.g. zod issues).' },
    },
    required: ['code', 'msg', 'data'],
  };

  for (const [, pathItem] of Object.entries(doc.paths ?? {})) {
    if (!pathItem) continue;
    for (const method of ['get', 'post', 'put', 'patch', 'delete'] as const) {
      const op = pathItem[method];
      if (!op || !op.responses) continue;

      for (const [statusCode, response] of Object.entries(op.responses)) {
        if (typeof response !== 'object' || response === null) continue;
        if ('$ref' in response) continue;

        const content = (response as { content?: Record<string, { schema?: unknown }> }).content;
        if (!content) continue;

        for (const [mediaType, body] of Object.entries(content)) {
          if (mediaType.startsWith('text/event-stream')) continue;
          if (!body || typeof body !== 'object') continue;

          const status = parseInt(statusCode, 10);
          if (Number.isNaN(status) || status >= 400) {
            // Error path — replace with the canonical error envelope
            (body as { schema?: unknown }).schema = {
              $ref: `#/components/schemas/${ERROR_ENVELOPE_NAME}`,
            };
          } else {
            // Success path — wrap the original schema as `data`
            const original = (body as { schema?: SchemaObject }).schema;
            (body as { schema?: unknown }).schema = {
              type: 'object',
              required: ['code', 'msg', 'data'],
              properties: {
                code: { type: 'integer', enum: [0] },
                msg: { type: 'string', enum: ['ok'] },
                data: original ?? { nullable: true },
              },
            };
          }
        }
      }
    }
  }

  return doc;
}
