import {
    ArgMap,
    CompressedField,
    CompressedFieldMap,
    CompressedTypeMap,
    TypeMap,
} from '@genql/runtime/dist/types'
import {
    GraphQLSchema,
    isEnumType,
    isInputObjectType,
    isInterfaceType,
    isObjectType,
    isScalarType,
    isUnionType,
} from 'graphql'
import { excludedTypes } from '../common/excludedTypes'
import { RenderContext } from '../common/RenderContext'
import { objectType } from './objectType'
import { unionType } from './unionType'

export const renderTypeMap = (schema: GraphQLSchema, ctx: RenderContext) => {
    // remove fields key,
    // remove the Type.type and Type.args, replace with [type, args]
    // reverse args.{name}
    // Args type is deduced and added only when the concrete type is different from type name, remove the scalar field and replace with a top level scalars array field.
    const result: TypeMap<string> = {
        scalars: [],
        types: {},
    }

    Object.keys(schema.getTypeMap())
        .filter((t) => !excludedTypes.includes(t))
        .map((t) => schema.getTypeMap()[t])
        .map((t) => {
            if (isObjectType(t) || isInterfaceType(t) || isInputObjectType(t))
                result.types[t.name] = objectType(t, ctx)
            else if (isUnionType(t)) result.types[t.name] = unionType(t, ctx)
            else if (isScalarType(t) || isEnumType(t)) {
                result.scalars.push(t.name)
                result.types[t.name] = {}
            }
        })

    // change names of query, mutation on schemas that chose different names (hasura)
    const q = schema.getQueryType()
    if (q?.name && q?.name !== 'Query') {
        delete result.types[q.name]
        result.types.Query = objectType(q, ctx)
        // result.Query.name = 'Query'
    }

    const m = schema.getMutationType()
    if (m?.name && m.name !== 'Mutation') {
        delete result.types[m.name]
        result.types.Mutation = objectType(m, ctx)
        // result.Mutation.name = 'Mutation'
    }

    const s = schema.getSubscriptionType()
    if (s?.name && s.name !== 'Subscription') {
        delete result.types[s.name]
        result.types.Subscription = objectType(s, ctx)
        // result.Subscription.name = 'Subscription'
    }

    ctx.addCodeBlock(
        JSON.stringify(replaceTypeNamesWithIndexes(result), null, 4),
    )
    


}

export function replaceTypeNamesWithIndexes(
    typeMap: TypeMap<string>,
): CompressedTypeMap<number> {
    const nameToIndex: Record<string, number> = Object.assign(
        {},
        ...Object.keys(typeMap.types).map((k, i) => ({ [k]: i })),
    )
    const scalars = typeMap.scalars.map((x) => nameToIndex[x])
    const types = Object.assign(
        {},
        ...Object.keys(typeMap.types || {}).map((k) => {
            const type = typeMap.types[k]
            const fieldsMap = type || {}
            // processFields(fields, indexToName)
            const fields = Object.assign(
                {},
                ...Object.keys(fieldsMap).map(
                    (f): CompressedFieldMap<number> => {
                        const content = fieldsMap[f] as any
                        if (!content) {
                            throw new Error('no content in field ' + f)
                        }
                        const [typeName, args] = [content.type, content.args]
                        const res: CompressedField<number> = [
                            typeName ? nameToIndex[typeName] : -1,
                        ]
                        if (args) {
                            res[1] = Object.assign(
                                {},
                                ...Object.keys(args || {}).map((k) => {
                                    const arg = args?.[k]
                                    if (!arg) {
                                        throw new Error(
                                            'replaceTypeNamesWithIndexes: no arg for ' +
                                                k,
                                        )
                                    }
                                    return {
                                        [k]: [
                                            nameToIndex[arg[0]],
                                            ...arg.slice(1),
                                        ],
                                    } as ArgMap<number>
                                }),
                            )
                        }

                        return {
                            [f]: res,
                        }
                    },
                ),
            )
            return {
                [k]: {
                    ...fields,
                },
            }
        }),
    )
    return {
        scalars,
        types,
    }
}