import { RUNTIME_LIB_NAME } from '../../config';
import { RenderContext } from '../common/RenderContext';
import { excludedTypes } from '../common/excludedTypes';
import { GraphQLEnumType, GraphQLSchema, isEnumType } from 'graphql';
import camelCase from 'lodash/camelCase';

const { version } = require('../../../package.json');

function renderClientCode(ctx: RenderContext) {
  const url = ctx.config?.endpoint ? `"${ctx.config.endpoint}"` : 'undefined';
  return `
function(options) {
    options = options || {}
    var optionsCopy = {
      url: ${url},
      queryRoot: typeMap.Query,
      mutationRoot: typeMap.Mutation,
      subscriptionRoot: typeMap.Subscription,
    }
    for (var name in options) {
      optionsCopy[name] = options[name];
    }
    return createClientOriginal(optionsCopy)
}`;
}

export function renderEnumsMaps(schema: GraphQLSchema, moduleType: 'esm' | 'cjs' | 'type') {
  let typeMap = schema.getTypeMap();

  const enums: GraphQLEnumType[] = [];
  for (const name in typeMap) {
    if (excludedTypes.includes(name)) continue;

    const type = typeMap[name];

    if (isEnumType(type)) {
      enums.push(type);
    }
  }
  if (enums.length === 0) return '';
  const declaration = (() => {
    if (moduleType === 'esm') {
      return 'export const ';
    } else if (moduleType === 'cjs') {
      return 'module.exports.';
    } else if (moduleType === 'type') {
      return 'export declare const ';
    }
    return '';
  })();
  return enums
    .map(
      (type) =>
        `${declaration}${camelCase('enum' + type.name)}${moduleType === 'type' ? ': ' : ' = '}{\n` +
        type
          .getValues()
          .map((v) => {
            if (!v?.name) {
              return '';
            }
            return `  ${moduleType === 'type' ? 'readonly ' : ''}${v.name}: '${v.name}'`;
          })
          .join(',\n') +
        `\n}\n`,
    )
    .join('\n');
}

export function renderClientCjs(schema: GraphQLSchema, ctx: RenderContext) {
  const prefix = ctx.config?.methodPrefix || '';
  const suffix = ctx.config?.methodSuffix || '';
  ctx.addCodeBlock(`
  const {
      linkTypeMap,
      createClient: createClientOriginal,
      generateGraphqlOperation,
      assertSameVersion,
  } = require('${RUNTIME_LIB_NAME}')
  var typeMap = linkTypeMap(require('./types.cjs'))

  var version = '${version}'
  assertSameVersion(version)

  module.exports.version = version

  module.exports.${prefix}createClient${suffix} = ${renderClientCode(ctx)}

  ${renderEnumsMaps(schema, 'cjs')}

  module.exports.generateQueryOp = function(fields) {
    return generateGraphqlOperation('query', typeMap.Query, fields)
  }
  module.exports.generateMutationOp = function(fields) {
    return generateGraphqlOperation('mutation', typeMap.Mutation, fields)
  }
  module.exports.generateSubscriptionOp = function(fields) {
    return generateGraphqlOperation('subscription', typeMap.Subscription, fields)
  }
  module.exports.everything = {
    __scalar: true
  }

  var schemaExports = require('./guards.cjs')
  for (var k in schemaExports) {
    module.exports[k] = schemaExports[k];
  }
  `);
}

export function renderClientEsm(schema: GraphQLSchema, ctx: RenderContext) {
  const prefix = ctx.config?.methodPrefix || '';
  const suffix = ctx.config?.methodSuffix || '';
  ctx.addCodeBlock(`
  import {
      linkTypeMap,
      createClient as createClientOriginal,
      generateGraphqlOperation,
      assertSameVersion,
  } from '${RUNTIME_LIB_NAME}'
  import types from './types.esm'
  var typeMap = linkTypeMap(types)
  export * from './guards.esm'

  export var version = ${JSON.stringify(version)}
  assertSameVersion(version)

  export var ${prefix}createClient${suffix} = ${renderClientCode(ctx)}

  ${renderEnumsMaps(schema, 'esm')}

  export var generateQueryOp = function(fields) {
    return generateGraphqlOperation('query', typeMap.Query, fields)
  }
  export var generateMutationOp = function(fields) {
    return generateGraphqlOperation('mutation', typeMap.Mutation, fields)
  }
  export var generateSubscriptionOp = function(fields) {
    return generateGraphqlOperation('subscription', typeMap.Subscription, fields)
  }
  export var everything = {
    __scalar: true
  }
  `);
}
