
import { Kind, print } from "graphql";
import {
  type ObjectValueNode, type ValueNode, isNode,
} from "graphql/language/ast";

export function parseError (
  type: string, ast: unknown, reason = "input syntax"
): never {
  throw new TypeError(`invalid ${reason} for type ${type}: ${
    isNode(ast) ? print(ast) : String(ast)
  }`);
}

export type Primitive =
  boolean | number | string | null | undefined;

export type Derivative =
  Array<Derivative> | Primitive | { [key: string]: Derivative; };

export function parseObject (
  type: string, ast: ObjectValueNode, variables?: Record<string, unknown> | null
) {
  const value = Object.create(null);

  for (const field of ast.fields) {
    value[field.name.value] = parseLiteral(type, field.value, variables);
  }

  return value as Record<string, Derivative>;
}

export function parseLiteral (
  type: string, ast: ValueNode, variables?: Record<string, unknown> | null
): Derivative {
  switch (ast.kind) {
    case Kind.STRING: case Kind.BOOLEAN: case Kind.INT: case Kind.FLOAT:
      return ast.value;
    case Kind.OBJECT:
      return parseObject(type, ast, variables);
    case Kind.LIST:
      return ast.values.map(function mapValue ( value ) {
        return parseLiteral(type, value, variables);
      });
    case Kind.NULL:
      return null;
    case Kind.VARIABLE:
      return variables?.[ast.name.value] as Derivative;
    default:
      return parseError(type, ast);
  }
}
