
import type { Plugin } from "../plugins/Plugin";
import { type Derivative, parseError, parseLiteral } from "./parse";
import {
  GraphQLScalarType, type GraphQLScalarValueParser, type ValueNode,
} from "graphql";

export namespace createScalar {
  export interface Types {
    string : string;
    number : number;
    boolean: boolean;
    object : Record<string, Derivative>;
    array  : Array<Derivative>;
  }

  export type Invalid = ( reason?: string ) => string;

  export type Casts = {
    [Key in keyof Types]?: boolean | ((
      value: Types[Key], invalid: Invalid
    ) => string);
  };

  export type Serializer = ( value: unknown ) => unknown;
}

function getTypeOf ( value: Derivative ) {
  return (
    value instanceof Array ? "array" : typeof value
  ) as keyof createScalar.Casts;
}

export function createScalar (
  name: string, description: string,
  casts: createScalar.Casts,
  serialize?: createScalar.Serializer | undefined
) {
  const asType = createScalarCast(name, casts);

  return {
    typeDefs : `scalar ${name}`,
    resolvers: {
      [name]: new GraphQLScalarType({
        name,
        description,
        serialize : serialize!,
        parseValue: asType,
        parseLiteral ( ast, variables ) {
          return asType(parseLiteral(name, ast, variables));
        },
      }),
    },
  } as const as Plugin;
}

export function createScalarCast (
  name: string, casts: createScalar.Casts
) {
  return function asType ( value: Derivative, ast?: ValueNode ) {
    if (null === value) { return null; }

    const type = getTypeOf(value);

    let result: unknown = null;

    try {
      const cast = casts[type];

      if (cast instanceof Function) {
        result = cast(value as never, function invalid ( reason ) {
          parseError(type, ast ?? value, reason);
        });
      } else if (cast) {
        switch (type) {
          case "object": case "array":
            result = JSON.stringify(value);
            break;
          default:
            result = String(value);
            break;
        }
      }
    } catch { /* ignored */ }

    if (null === result) {
      parseError(name, ast ?? value);
    }

    return result;
  } as GraphQLScalarValueParser<unknown>;
}
