import {
  Arg,
  Args,
  Query,
  Root,
  Resolver,
  FieldResolver,
  ObjectType,
  Field,
  Int,
  ArgsType,
  Info,
  Ctx
} from 'type-graphql';
import graphqlFields from 'graphql-fields';
import { Inject } from 'typedi';
import { Min } from 'class-validator'
import { Fields, PageInfo, BaseContext } from '@joystream/warthog';

import {
  {{className}}CreateInput,
  {{className}}CreateManyArgs,
  {{className}}UpdateArgs,
  {{className}}WhereArgs,
  {{className}}WhereInput,
  {{className}}WhereUniqueInput,
  {{className}}OrderByEnum,
} from '{{{generatedFolderRelPath}}}';

import { {{className}} } from './{{kebabName}}.model';
import { {{className}}Service } from './{{kebabName}}.service';

{{#fieldResolverImports}}
  {{{.}}}
{{/fieldResolverImports}}

{{! Pagination objects -- }}

@ObjectType()
export class {{className}}Edge {
  @Field(() => {{className}}, { nullable: false })
  node!: {{className}};

  @Field(() => String, { nullable: false })
  cursor!: string;
}

@ObjectType()
export class {{className}}Connection {
  @Field(() => Int, { nullable: false })
  totalCount!: number;

  @Field(() => [{{className}}Edge], { nullable: false })
  edges!: {{className}}Edge[];

  @Field(() => PageInfo, { nullable: false })
  pageInfo!: PageInfo;
}

@ArgsType()
export class ConnectionPageInputOptions {
  @Field(() => Int, { nullable: true })
  @Min(0)
  first?: number

  @Field(() => String, { nullable: true })
  after?: string // V3: TODO: should we make a RelayCursor scalar?

  @Field(() => Int, { nullable: true })
  @Min(0)
  last?: number

  @Field(() => String, { nullable: true })
  before?: string
}

@ArgsType()
export class {{className}}ConnectionWhereArgs extends ConnectionPageInputOptions {
  @Field(() => {{className}}WhereInput, { nullable: true })
  where?: {{className}}WhereInput;

  @Field(() => [{{className}}OrderByEnum], { nullable: true })
  orderBy?: [{{className}}OrderByEnum];
}

{{! -- Pagination objects }}

@Resolver({{className}})
export class {{className}}Resolver {
  constructor(
    @Inject('{{className}}Service') public readonly service: {{className}}Service,

  ) {}

  @Query(() => [{{className}}])
  async {{camelNamePlural}}(
    @Args() { where, orderBy, limit, offset }: {{className}}WhereArgs,
    @Fields() fields: string[]
  ): Promise<{{className}}[]> {
    return this.service.find<{{className}}WhereInput>(where, orderBy, limit, offset, fields);
  }

  @Query(() => {{className}}, { nullable: true })
  async {{camelName}}ByUniqueInput(
    @Arg('where') where: {{className}}WhereUniqueInput,
    @Fields() fields: string[]
  ): Promise<{{className}} | null> {
    const result = await this.service.find(where, undefined, 1, 0, fields);
    return result && result.length >= 1 ? result[0] : null;
  }

  @Query(() => {{className}}Connection)
  async {{camelNamePlural}}Connection(
    @Args() { where, orderBy, ...pageOptions }: {{className}}ConnectionWhereArgs,
    @Info() info: any
  ): Promise<{{className}}Connection> {
    
    const rawFields = graphqlFields(info, {}, {});
    
    if (rawFields.edges && rawFields.edges.node) {
      Object.keys(rawFields.edges.node).map((item) => {
        if (Object.keys(rawFields.edges.node[item]).includes("__typename")) {
          rawFields.edges.node[item] = {};
        }
      });
    }

    let result: any = {
      totalCount: 0,
      edges: [],
      pageInfo: {
        hasNextPage: false,
        hasPreviousPage: false
      }
    };
    // If the related database table does not have any records then an error is thrown to the client
    // by warthog
    try {
      result = await this.service.findConnection<{{className}}WhereInput>(
        where,
        orderBy,
        pageOptions,
        rawFields
      );
    } catch (err) {
      console.log(err);
      // TODO: should continue to return this on `Error: Items is empty` or throw the error
      if (!(err.message as string).includes('Items is empty')) throw err;
    }

    return result as Promise<{{className}}Connection>;
  }

  {{#fieldResolvers}}
    @FieldResolver(() => {{returnTypeFunc}})
    async {{fieldName}}(@Root() {{rootArgName}}: {{rootArgType}}, @Ctx() ctx: BaseContext): {{{returnType}}} {
      return ctx.dataLoader.loaders.{{className}}.{{fieldName}}.load({{rootArgName}}); 
    }

  {{/fieldResolvers}}
}
