import {
  getNamedType,
  GraphQLInterfaceType,
  GraphQLNamedType,
  GraphQLObjectType,
  isEnumType,
  isListType,
  isNonNullType,
  isScalarType,
  isUnionType,
} from 'graphql'
import { fieldComment, typeComment } from '../common/comment'
import { RenderContext } from '../common/RenderContext'
import { renderTyping } from '../common/renderTyping'
import { toArgsString } from '../common/toArgsString'
import { requestTypeName } from '../requestTypes/requestTypeName'

const packageJson = require('../../../package.json')

export const chainTypeName = (type: GraphQLNamedType, wrapper: 'Promise' | 'Observable') => `${type.name}${wrapper}Chain`

export const objectType = (
  type: GraphQLObjectType | GraphQLInterfaceType,
  ctx: RenderContext,
  wrapper: 'Promise' | 'Observable',
) => {
  const fieldStrings = Object.keys(type.getFields()).map(fieldName => {
    const field = type.getFields()[fieldName]
    const resolvedType = getNamedType(field.type)
    const stopChain =
      isListType(field.type) || (isNonNullType(field.type) && isListType(field.type.ofType)) || isUnionType(resolvedType)
    const resolvable = !(isEnumType(resolvedType) || isScalarType(resolvedType))
    const argsPresent = field.args.length > 0
    const argsOptional = !field.args.find(a => isNonNullType(a.type))
    const argsString = toArgsString(field)

    const executeReturnType = `${renderTyping(field.type, false, false, false)}`
    const executeReturnTypeWrapped = `${wrapper}<${executeReturnType}>`

    const fieldType = resolvable
      ? stopChain
        ? `{execute:(request:${requestTypeName(
            resolvedType,
          )},defaultValue?:${executeReturnType})=>${executeReturnTypeWrapped}}`
        : `${chainTypeName(resolvedType, wrapper)}&{execute:(request:${requestTypeName(
            resolvedType,
          )},defaultValue?:${executeReturnType})=>${executeReturnTypeWrapped}}`
      : `{execute:(request?:boolean|number,defaultValue?:${executeReturnType})=>${executeReturnTypeWrapped}}`

    const result = []

    if (argsPresent) {
      result.push(`((args${argsOptional ? '?' : ''}:${argsString})=>${fieldType})`)
    }

    if (!argsPresent || argsOptional) {
      result.push(`(${fieldType})`)
    }

    return `${fieldComment(field)}${field.name}:${result.join('&')}`
  })

  if (wrapper === 'Observable') {
    ctx.addImport(packageJson.name, false, 'Observable', true, true)
  }

  ctx.addCodeBlock(`${typeComment(type)}export interface ${chainTypeName(type, wrapper)}{${fieldStrings.join(',')}}`)
}
