import { Type } from '@nestjs/common';
import { ObjectType, Field } from '@nestjs/graphql';
import Relay from 'graphql-relay';

// type tobe used in pagination
// return the actual data
const typeMap = {};

/**
 * ## Paginated
 *
 * The pagination of the object graphql
 * allow to paginated and sort the data
 * using the cursor, or the offset and limit
 *
 *
 * @param type
 * @returns
 */
export function Paginated<T>(type: Type<T>): any {
  const { name } = type;
  if (typeMap[`${name}`]) return typeMap[`${name}`];

  /**
   * ### Edge
   *
   * The edge object for pagination
   * will come with cursor, and the node field
   * will generate and Edge based on the type extends into the params
   *
   * This class will never registered into the scheme
   *
   */
  @ObjectType(`${name}Edge`, { isAbstract: true })
  class Edge<T> implements Relay.Edge<T> {
    public name = `${name}Edge`;

    @Field({ nullable: true, description: `The cursor for ${name} pagination` })
    public cursor!: Relay.ConnectionCursor;

    @Field(() => type, {
      nullable: true,
      description: `Node of the ${name} data`,
    })
    public node!: T;
  }

  /**
   * ### PageInfo
   *
   * Page information of the edge
   * will generate some of info depends on the parameters class extends
   *
   *
   */
  @ObjectType(`${name}PageInfo`, { isAbstract: true })
  class PageInfo implements Relay.PageInfo {
    @Field({ nullable: true, description: `The start cursor for ${name}` })
    public startCursor!: Relay.ConnectionCursor;

    @Field({ nullable: true, description: `The end cursor for ${name}` })
    public endCursor!: Relay.ConnectionCursor;

    @Field(() => Boolean, {
      description: `Whether there are more items to fetch before for ${name}`,
    })
    public hasPreviousPage!: boolean;

    @Field(() => Boolean, {
      description: `Whether there is a next page for ${name}`,
    })
    public hasNextPage!: boolean;
  }

  /**
   * ### Connection
   *
   * Will generated connection for the type extends
   * cover some data inside like edges, pageInfo,
   *
   *
   */
  @ObjectType(`${name}Connection`, { isAbstract: true })
  class Connection implements Relay.Connection<T> {
    public name = `${name}Connection`;

    @Field(() => [Edge], {
      nullable: true,
      description: `Edges of the ${name} data`,
    })
    public edges!: Edge<T>[];

    @Field(() => PageInfo, {
      nullable: true,
      description: `Page info of the edges, like the cursor and posibillity to next or prev for ${name} data`,
    })
    public pageInfo!: PageInfo;
  }

  /**
   * ### Page
   *
   * The final result will be become when data
   * become paginated, the actual data will show is page, and pageData
   */
  @ObjectType(`${name}Page`, { isAbstract: true })
  abstract class Page {
    public name = `${name}Page`;

    @Field(() => Connection, { description: `The ${name} data of the page` })
    public page!: Connection;

    @Field(() => PageData, {
      nullable: true,
      description: `The ${name} data information will be showed like count, limit, and the offset`,
    })
    public pageData!: PageData;
  }

  typeMap[`${name}`] = Page;
  return typeMap[`${name}`];
}

/**
 * ### PageData
 *
 * Show some info of the pagination
 * like count of the data, limit and the offset
 *
 *
 */
@ObjectType()
export class PageData {
  @Field({ description: `Data count in the page` })
  public count: number;

  @Field({ description: `Limit data for the page` })
  public limit: number;

  @Field({ description: `The offset data will be showed next and before` })
  public offset: number;
}
