/**
 * OpenAPI Extractors
 * 
 * Ported from Hoppscotch's openapi/index.ts
 * Converts fp-ts functional programming to native TypeScript
 */

/* eslint-disable @typescript-eslint/no-unused-vars */

import type {
  OpenAPI,
  OpenAPIV2,
  OpenAPIV3,
  OpenAPIV3_1 as OpenAPIV31,
} from 'openapi-types'
import type {
  BrainfishRESTParam,
  BrainfishRESTHeader,
  BrainfishRESTRequestVariable,
  BrainfishRESTRequestResponses,
  BrainfishRESTResponseOriginalRequest,
} from '../../../types'
import { isNumeric, getStatusCodeReasonPhrase } from '../../../utils'

// Type definitions
export type OpenAPIPathInfoType =
  | OpenAPIV2.PathItemObject<Record<string, unknown>>
  | OpenAPIV3.PathItemObject<Record<string, unknown>>
  | OpenAPIV31.PathItemObject<Record<string, unknown>>

export type OpenAPIParamsType =
  | OpenAPIV2.ParameterObject
  | OpenAPIV3.ParameterObject
  | OpenAPIV31.ParameterObject

export type OpenAPIOperationType =
  | OpenAPIV2.OperationObject
  | OpenAPIV3.OperationObject
  | OpenAPIV31.OperationObject

// Known content types (from Hoppscotch)
const knownContentTypes: Record<string, boolean> = {
  'application/json': true,
  'application/xml': true,
  'text/plain': true,
  'text/html': true,
  'application/x-www-form-urlencoded': true,
  'multipart/form-data': true,
  'application/octet-stream': true,
}

/**
 * Replaces OpenAPI path templating ({id}) with Brainfish templating (<<id>>)
 */
export function replaceOpenApiPathTemplating(path: string): string {
  return path.replace(/{/g, '<<').replace(/}/g, '>>')
}

/**
 * Parses OpenAPI query parameters
 */
export function parseOpenAPIParams(
  params: OpenAPIParamsType[]
): BrainfishRESTParam[] {
  return params
    .filter((param) => param.in === 'query')
    .map((param) => ({
      key: param.name,
      value: '', // TODO: Can we parse default values?
      active: true,
      description: param.description ?? '',
    }))
}

/**
 * Parses OpenAPI path variables
 */
export function parseOpenAPIVariables(
  variables: OpenAPIParamsType[]
): BrainfishRESTRequestVariable[] {
  return variables
    .filter((param) => param.in === 'path')
    .map((param) => ({
      key: param.name,
      value: '', // TODO: Can we parse default values?
      active: true,
    }))
}

/**
 * Parses OpenAPI header parameters
 */
export function parseOpenAPIHeaders(
  params: OpenAPIParamsType[]
): BrainfishRESTHeader[] {
  return params
    .filter((param) => param.in === 'header')
    .map((header) => ({
      key: header.name,
      value: '', // TODO: Can we parse default values?
      active: true,
      description: header.description ?? '',
    }))
}

/**
 * Parses OpenAPI v3 responses
 */
export function parseOpenAPIV3Responses(
  op: OpenAPIV3.OperationObject | OpenAPIV31.OperationObject,
  originalRequest: BrainfishRESTResponseOriginalRequest
): BrainfishRESTRequestResponses {
  const responses = op.responses
  if (!responses) return {}

  const res: BrainfishRESTRequestResponses = {}

  for (const [key, value] of Object.entries(responses)) {
    const response = value as
      | OpenAPIV3.ResponseObject
      | OpenAPIV31.ResponseObject

    const contentType = Object.keys(response.content ?? {})[0]
    const body = response.content?.[contentType]

    const name = response.description ?? key
    const code = isNumeric(key) ? Number(key) : 200
    const status = getStatusCodeReasonPhrase(code)

    const headers: BrainfishRESTHeader[] = [
      {
        key: 'content-type',
        value: contentType ?? 'application/json',
        description: '',
        active: true,
      },
    ]

    let stringifiedBody = ''

    try {
      stringifiedBody = JSON.stringify(body ?? '')
    } catch (e) {
      // Ignore circular reference errors
    }

    res[name] = {
      name,
      status,
      code,
      headers,
      body: stringifiedBody,
      originalRequest,
    }
  }

  return res
}

/**
 * Parses OpenAPI v2 responses
 */
export function parseOpenAPIV2Responses(
  op: OpenAPIV2.OperationObject,
  originalRequest: BrainfishRESTResponseOriginalRequest
): BrainfishRESTRequestResponses {
  const responses = op.responses
  if (!responses) return {}

  const res: BrainfishRESTRequestResponses = {}

  for (const [key, value] of Object.entries(responses)) {
    const response = value as OpenAPIV2.ResponseObject

    const contentType = Object.keys(response.examples ?? {})[0]
    const body = response.examples?.[contentType]

    const name = response.description ?? key
    const code = isNumeric(Number(key)) ? Number(key) : 200
    const status = getStatusCodeReasonPhrase(code)

    const headers: BrainfishRESTHeader[] = [
      {
        key: 'content-type',
        value: contentType ?? 'application/json',
        description: '',
        active: true,
      },
    ]

    res[name] = {
      name,
      status,
      code,
      headers,
      body: body ?? '',
      originalRequest,
    }
  }

  return res
}

/**
 * Parses OpenAPI responses (v2 or v3)
 */
export function parseOpenAPIResponses(
  doc: OpenAPI.Document,
  op: OpenAPIOperationType,
  originalRequest: BrainfishRESTResponseOriginalRequest
): BrainfishRESTRequestResponses {
  return isOpenAPIV3Operation(doc, op)
    ? parseOpenAPIV3Responses(op, originalRequest)
    : parseOpenAPIV2Responses(op as OpenAPIV2.OperationObject, originalRequest)
}

/**
 * Checks if an operation is OpenAPI v3
 */
export function isOpenAPIV3Operation(
  doc: OpenAPI.Document,
  op: OpenAPIOperationType
): op is OpenAPIV3.OperationObject | OpenAPIV31.OperationObject {
  return (
    'openapi' in doc &&
    typeof doc.openapi === 'string' &&
    doc.openapi.startsWith('3.')
  )
}

// Export knownContentTypes for use in body parsers
export { knownContentTypes }
