const knex = require("../knex")

class Connection {
  constructor(type, {edgeFields = [], connectionFields = []} = {}) {
    this._type = type
    this.edgeFields = edgeFields
    this.connectionFields = connectionFields
  }

  get edgeType() {
    return `
      type ${this._type}Edge {
        cursor: String!
        node: ${this._type}
        ${this.edgeFields.join("\n")}
      }
    `
  }

  get connectionType() {
    return `
      type ${this._type}Connection {
        edges: [${this._type}Edge]
        nodes: [${this._type}]
        pageInfo: PageInfo!
        totalCount: Int!
        ${this.connectionFields.join("\n")}
      }
    `
  }

  field(name, customArgs = []) {
    const args = [
      "first: Int",
      "after: String",
      "last: Int",
      "before: String",
      ...customArgs,
    ]
    return `${name}(${args.join(", ")}): ${this._type}Connection`
  }

  get type() {
    return `
      ${this.edgeType}

      ${this.connectionType}
    `
  }

  encode(value) {
    return Buffer.from(JSON.stringify(value)).toString("base64")
  }

  decode(value) {
    return Buffer.from(value, "base64").toString()
  }

  async queryCount(query) {
    const countQuery = knex.count().from(query.clone().as("query"))
    const [{count}] = await countQuery

    return count
  }

  resolver(query, {cursorFrom = "created_at", direction = "desc"} = {}) {
    return async (_, {first, after, last, before}) => {
      query = query.clone()
      if (!first && !last) {
        throw new Error(
          "You must provide a 'first' or 'last' value to properly paginate" +
            ` the ${this._type}Connection'`,
        )
      }

      first = Math.max(first, 0)
      last = Math.max(last, 0)

      query.orderBy(cursorFrom, direction)

      const totalCount = await this.queryCount(query)

      if (after) {
        const afterCursor = this.decode(after)
        const operator = direction === "desc" ? "<" : ">"
        query.where(cursorFrom, operator, afterCursor)
      }

      if (before) {
        const beforeCursor = this.decode(before)
        const operator = direction === "desc" ? ">" : "<"
        query.where(cursorFrom, operator, beforeCursor)
      }

      const slicedNodesCount = await this.queryCount(query)

      if (first) {
        query.limit(first)
      }

      if (last) {
        query.offset(slicedNodesCount - last)
      }

      const nodes = await query.select()
      const pageInfo = {
        endCursor: this.encode(nodes[nodes.length - 1][cursorFrom]),
        startCursor: this.encode(nodes[0][cursorFrom]),
        hasNextPage: !!(
          first &&
          nodes.length >= first &&
          slicedNodesCount > first
        ),
        hasPreviousPage: !!(
          last &&
          nodes.length >= last &&
          slicedNodesCount > last
        ),
      }
      const edges = nodes.map(node => ({
        cursor: this.encode(node[cursorFrom]),
        node,
      }))

      return {
        edges,
        nodes,
        totalCount,
        pageInfo,
      }
    }
  }
}

exports.PageInfo = `
  type PageInfo {
    endCursor: String
    hasNextPage: Boolean!
    hasPreviousPage: Boolean!
    startCursor: String
  }
`

exports.connection = function connection(...args) {
  return new Connection(...args)
}

@@ -1432,21 +1432,27 @@
 %7B%0A      
-query
+const clone
  = query
@@ -1652,24 +1652,323 @@
  )%0A      %7D%0A%0A
+      if ((first && before) %7C%7C (last && after) %7C%7C (first && last)) %7B%0A        const params =%0A          first && before%0A            ? %22'first' and 'before'%22%0A            : first && last ? %22'first' and 'last'%22 : %22'last' and 'after'%22%0A        throw new Error(%60Mixing $%7Bparams%7D is not supported%60)%0A      %7D%0A%0A
       first 
@@ -2026,21 +2026,21 @@
 %0A%0A      
-query
+clone
 .orderBy
@@ -2103,37 +2103,37 @@
 this.queryCount(
-query
+clone
 )%0A%0A      if (aft
@@ -2243,37 +2243,37 @@
 %22 : %22%3E%22%0A        
-query
+clone
 .where(cursorFro
@@ -2442,21 +2442,21 @@
         
-query
+clone
 .where(c
@@ -2552,21 +2552,21 @@
 ryCount(
-query
+clone
 )%0A%0A     
@@ -2587,21 +2587,21 @@
         
-query
+clone
 .limit(f
@@ -2645,21 +2645,21 @@
     
-query
+clone
 .offset(
 slic
@@ -2654,16 +2654,28 @@
 .offset(
+Math.max(0, 
 slicedNo
@@ -2690,16 +2690,17 @@
  - last)
+)
 %0A      %7D
@@ -2727,21 +2727,21 @@
 = await 
-query
+clone
 .select(
@@ -2758,39 +2758,42 @@
 nst 
-pageInfo = %7B%0A        endCursor:
+endCursor = nodes.length%0A        ?
  thi
@@ -2829,33 +2829,32 @@
  1%5D%5BcursorFrom%5D)
-,
 %0A        startCu
@@ -2850,54 +2850,176 @@
     
-startCursor: this.encode(nodes%5B0%5D%5BcursorFrom%5D)
+: null%0A      const startCursor = nodes.length%0A        ? this.encode(nodes%5B0%5D%5BcursorFrom%5D)%0A        : null%0A      const pageInfo = %7B%0A        endCursor,%0A        startCursor
 ,%0A  
@@ -3055,61 +3055,47 @@
     
-fir
+(la
 st &&
-%0A          nodes.length %3E= first &&%0A         
+ before) %7C%7C%0A          (first &&
  sli
@@ -3110,21 +3110,29 @@
 Count %3E 
-first
+nodes.length)
 %0A       
@@ -3178,59 +3178,46 @@
     
-la
+(fir
 st &&
-%0A          nodes.length %3E= last &&%0A         
+ after) %7C%7C%0A          (last &&
  sli
@@ -3232,20 +3232,29 @@
 Count %3E 
-last
+nodes.length)
 %0A       

Improve connection-helpers