
/* eslint-disable @typescript-eslint/no-explicit-any */

import type { GraphQLSchema } from "graphql";
import type { IResolvers, TypeSource } from "@graphql-tools/utils";

import { makeExecutableSchema } from "@graphql-tools/schema";

export namespace Plugin {
  export enum Phase {
    PREPARE = 0, DEFAULT = 1, FINALIZE = 2,
  }

  export type Transformer =
    ( schema: GraphQLSchema ) => GraphQLSchema;

  export type Transformers = {
    [Key in `${Phase}`]?: Transformer;
  };

  export function apply<Context = any> (
    plugin: Plugin<Context>
  ) {
    let schema = makeExecutableSchema<Context>({
      typeDefs : plugin.typeDefs ?? [ ],
      resolvers: plugin.resolvers ?? { },
    });

    for (const phase of [ Phase.PREPARE, Phase.DEFAULT, Phase.FINALIZE, ]) {
      schema = getTransformerByPhase(plugin, phase)?.(schema) ?? schema;
    }

    return schema;
  }

  export function compose<Context = any> (
    plugins: Array<Plugin<Context>>
  ) {
    return {
      typeDefs: plugins.map(function mapTypeDefs ( plugin ) {
        return plugin.typeDefs;
      }),
      resolvers: plugins.reduce<IResolvers<any, Context>>(
        function reduceResolvers ( acc, plugin ) {
          return Object.assign(acc, plugin.resolvers);
        }, { }
      ),
      transformer: {
        [Phase.PREPARE] : composeTransformerByPhase(plugins, Phase.PREPARE),
        [Phase.DEFAULT] : composeTransformerByPhase(plugins, Phase.DEFAULT),
        [Phase.FINALIZE]: composeTransformerByPhase(plugins, Phase.FINALIZE),
      },
    } as const as Plugin<Context>;
  }

  export function composeTransformerByPhase<Context> (
    plugins: Array<Plugin<Context>>, phase: Phase
  ) {
    return function wrappedTransformerPhase ( schema: GraphQLSchema ) {
      let current = schema;

      for (const plugin of plugins) {
        const transformer = getTransformerByPhase(plugin, phase);

        if (transformer) { current = transformer(current); }
      }

      return current;
    };
  }

  export function getTransformerByPhase<Context> (
    plugin: Plugin<Context>, phase: Phase
  ) {
    if ("object" === typeof plugin.transformer) {
      return plugin.transformer[phase];
    }

    if (phase === Phase.DEFAULT && "function" === typeof plugin.transformer) {
      return plugin.transformer;
    }

    return undefined;
  }

}

export interface Plugin<Context = any> {
  typeDefs?   : TypeSource;
  resolvers?  : IResolvers<any, Context>;
  transformer?: Plugin.Transformer | Plugin.Transformers;
}
