import type { InjectionKey } from 'vue'
import type { OpenApiSpecInstance } from '../lib/spec/createOpenApiSpec'
import type { OpenAPIDocument, ParsedOpenAPI } from '../types'
import type { PartialUseThemeConfig } from './useTheme'
import { inject } from 'vue'
import { parseOpenapi } from '../lib/parser/parseOpenapi'
import { createOpenApiSpec } from '../lib/spec/createOpenApiSpec'
import { parseSpec } from '../lib/utils/parseSpec'
import { useTheme } from './useTheme'

export const OPENAPI_LOCAL_KEY: InjectionKey<OpenApiSpecInstance> = Symbol('openapiLocal')

let globalInstance: OpenApiSpecInstance | null = null

function buildInstance(
  parsedSpec: ParsedOpenAPI,
  originalSpec: OpenAPIDocument,
): OpenApiSpecInstance {
  return createOpenApiSpec({ spec: parsedSpec, originalSpec })
}

function createInstance(options: {
  spec: OpenAPIDocument | string
  defaultTag?: string
  defaultTagDescription?: string
}): OpenApiSpecInstance {
  const originalSpec = parseSpec(options.spec)
  const parsedSpec = parseOpenapi().parseSync({
    spec: options.spec,
    defaultTag: options.defaultTag,
    defaultTagDescription: options.defaultTagDescription,
  })
  return buildInstance(parsedSpec, originalSpec)
}

async function createInstanceAsync(options: {
  spec: OpenAPIDocument | string
  defaultTag?: string
  defaultTagDescription?: string
}): Promise<OpenApiSpecInstance> {
  const originalSpec = parseSpec(options.spec)
  const parsedSpec = await parseOpenapi().parseAsync({
    spec: options.spec,
    defaultTag: options.defaultTag,
    defaultTagDescription: options.defaultTagDescription,
  })
  return buildInstance(parsedSpec, originalSpec)
}

export function injectOpenapi(): OpenApiSpecInstance | null {
  return inject(OPENAPI_LOCAL_KEY, null)
}

export function getGlobalOpenapi(): OpenApiSpecInstance | null {
  return globalInstance
}

export function useOpenapi(options: {
  spec?: OpenAPIDocument | string
  config?: PartialUseThemeConfig
} = {}): OpenApiSpecInstance & {
  async: (asyncOptions?: { spec?: OpenAPIDocument | string }) => Promise<OpenApiSpecInstance>
} {
  if (options.config) {
    useTheme(options.config)
  }

  if (options.spec) {
    globalInstance = createInstance({
      spec: options.spec,
      defaultTag: options.config?.spec?.defaultTag,
      defaultTagDescription: options.config?.spec?.defaultTagDescription,
    })
  }

  const instance = injectOpenapi() ?? globalInstance ?? createOpenApiSpec({})

  async function asyncParse(asyncOptions: { spec?: OpenAPIDocument | string } = {}): Promise<OpenApiSpecInstance> {
    const specToUse = asyncOptions.spec ?? options.spec
    if (!specToUse) {
      throw new Error('No spec provided')
    }
    globalInstance = await createInstanceAsync({
      spec: specToUse,
      defaultTag: options.config?.spec?.defaultTag,
      defaultTagDescription: options.config?.spec?.defaultTagDescription,
    })
    return globalInstance
  }

  return {
    ...instance,
    async: asyncParse,
  }
}
