EngineGraphQLGitHub <- R6::R6Class(
  classname = "EngineGraphQLGitHub",
  inherit = EngineGraphQL,
  public = list(
    initialize = function(gql_api_url,
                          token,
                          scan_all = FALSE) {
      super$initialize(
        gql_api_url = gql_api_url,
        token = token,
        scan_all = scan_all
      )
      self$gql_query <- GQLQueryGitHub$new()
    },

    set_owner_type = function(owners, verbose) {
      user_or_org_query <- self$gql_query$user_or_org_query
      login_types <- gitstats_map(owners, function(owner) {
        cached <- private[["owner_types_cache"]][[owner]]
        if (!is.null(cached)) {
          return(cached)
        }
        response <- self$gql_response(
          gql_query = user_or_org_query,
          vars = list(
            "login" = owner
          ),
          verbose = verbose
        )
        if (length(response$errors) < 2) {
          type <- purrr::discard(response$data, is.null) |>
            names()
          attr(owner, "type") <- type
        } else {
          attr(owner, "type") <- "not found"
        }
        private[["owner_types_cache"]][[owner]] <- owner
        return(owner)
      })
      return(login_types)
    },

    get_orgs = function(output = c("only_names", "full_table"), verbose) {
      if (verbose) {
        cli::cli_alert("[Host:GitHub][Engine:{cli::col_yellow('GraphQL')}] Pulling organizations {cli_icons$org}...")
      }
      end_cursor <- ""
      has_next_page <- TRUE
      full_orgs_list <- list()
      while (has_next_page) {
        response <- self$gql_response(
          gql_query = self$gql_query$orgs(
            end_cursor = end_cursor
          ),
          verbose = verbose
        )
        if (!inherits(response, "graphql_error")) {
          if (output == "only_names") {
            orgs_list <- purrr::map(response$data$search$edges, ~ .$node$login)
          } else {
            orgs_list <- purrr::map(response$data$search$edges, ~ .$node)
          }
          full_orgs_list <- append(full_orgs_list, orgs_list)
          has_next_page <- response$data$search$pageInfo$hasNextPage
          end_cursor <- response$data$search$pageInfo$endCursor
        } else {
          full_orgs_list <- response
          break
        }
      }
      all_orgs <- if (output == "only_names") {
        unlist(full_orgs_list)
      } else {
        full_orgs_list
      }
      return(all_orgs)
    },

    prepare_orgs_table = function(full_orgs_list) {
      orgs_table <- purrr::map(full_orgs_list, function(orgs_node) {
        orgs_node$description <- orgs_node$description %||% ""
        orgs_node$repos_count <- orgs_node$repositories$totalCount
        orgs_node$members_count <- orgs_node$membersWithRole$totalCount
        orgs_node$membersWithRole <- NULL
        orgs_node$repositories <- NULL
        data.frame(orgs_node)
      }) |>
        purrr::list_rbind() |>
        dplyr::rename(path = login,
                      avatar_url = avatarUrl) |>
        dplyr::relocate(description, .after = name) |>
        tibble::as_tibble()
      return(orgs_table)
    },

    get_org = function(org, verbose = TRUE) {
      response <- self$gql_response(
        gql_query = self$gql_query$org(),
        vars = list("org" = org),
        verbose = verbose
      )
      return(response$data$organization)
    },

    get_repos = function(repos_ids, verbose) {
      repos_query <- self$gql_query$repos_by_ids()
      if (length(repos_ids) > 100) {
        iterations_number <- ceiling(length(repos_ids) / 100)
        x <- 1
        repos_nodes <- purrr::map(c(1:iterations_number), function(i) {
          if (i == iterations_number) {
            repos_ids_length <- length(repos_ids)
          } else {
            repos_ids_length <- i * 100
          }
          repos_response <- self$gql_response(
            gql_query = repos_query,
            vars = list(
              "ids" = repos_ids[x:repos_ids_length]
            ),
            verbose = verbose
          )
          x <<- x + 100
          return(repos_response$data$nodes)
        }) |>
          purrr::list_flatten()
      } else {
        repos_response <- self$gql_response(
          gql_query = repos_query,
          vars = list(
            "ids" = repos_ids
          ),
          verbose = verbose
        )
        repos_nodes <- repos_response$data$nodes
      }
      return(repos_nodes)
    },

    get_repos_from_org = function(org = NULL,
                                  owner_type = c("organization", "user"),
                                  verbose = TRUE) {
      full_repos_list <- list()
      next_page <- TRUE
      repo_cursor <- ""
      while (next_page) {
        repos_response <- private$get_repos_page(
          login = org,
          type = owner_type,
          repo_cursor = repo_cursor,
          verbose = verbose
        )
        repositories <- if (owner_type == "organization") {
          repos_response$data$repositoryOwner$repositories
        } else {
          repos_response$data$user$repositories
        }
        repos_list <- repositories$nodes
        next_page <- repositories$pageInfo$hasNextPage
        if (is.null(next_page)) next_page <- FALSE
        if (is.null(repos_list)) repos_list <- list()
        if (next_page) {
          repo_cursor <- repositories$pageInfo$endCursor
        } else {
          repo_cursor <- ""
        }
        full_repos_list <- append(full_repos_list, repos_list)
      }
      return(full_repos_list)
    },

    prepare_repos_table = function(repos_list, org) {
      if (length(repos_list) > 0) {
        repos_table <- purrr::map(repos_list, function(repo) {
          default_branch <- if (!is.null(repo$default_branch)) {
            repo$default_branch$name
          } else {
            ""
          }
          last_activity_at <- as.POSIXct(repo$last_activity_at)
          if (length(last_activity_at) == 0) {
            last_activity_at <- gts_to_posixt(repo$created_at)
          }
          data.frame(
            repo_id = repo$repo_id,
            repo_name = repo$repo_path,
            repo_fullpath = repo$repo_fullpath,
            default_branch = default_branch,
            stars = repo$stars,
            forks = repo$forks,
            created_at = gts_to_posixt(repo$created_at),
            last_activity_at = last_activity_at,
            languages = purrr::map_chr(repo$languages$nodes, ~ .$name) |>
              paste0(collapse = ", "),
            issues_open = repo$issues_open$totalCount,
            issues_closed = repo$issues_closed$totalCount,
            organization = repo$organization$login,
            repo_url = repo$repo_url,
            commit_sha = repo$defaultBranchRef$target$oid %||% NA_character_
          )
        }) |>
          purrr::list_rbind()
      } else {
        repos_table <- NULL
      }
      return(repos_table)
    },

    get_commits_from_repos = function(org,
                                      repos_names,
                                      since,
                                      until,
                                      verbose) {
      repos_list_with_commits <- gitstats_map(repos_names, function(repo) {
        private$get_commits_from_one_repo(
          org   = org,
          repo  = repo,
          since = since,
          until = until,
          verbose = verbose
        )
      })
      names(repos_list_with_commits) <- repos_names
      repos_list_with_commits <- repos_list_with_commits |>
        purrr::discard(~ length(.) == 0)
      return(repos_list_with_commits)
    },

    prepare_commits_table = function(repos_list_with_commits,
                                     org) {
      commits_table <- purrr::imap(repos_list_with_commits, function(repo, repo_name) {
        commits_row <- purrr::map(repo, function(commit) {
          if (!is.null(commit$node)) {
            commit_author <- commit$node$author
            data.frame(
              id = commit$node$id,
              committed_date = gts_to_posixt(commit$node$committed_date),
              author = commit_author$name,
              author_login = commit_author$user$login %||% NA_character_,
              author_name = commit_author$user$name %||% NA_character_,
              additions = commit$node$additions,
              deletions = commit$node$deletions,
              repo_url = commit$node$repository$url
            )
          } else {
            NULL
          }
        }) |>
          purrr::list_rbind()
        commits_row$repo_name <- repo_name
        commits_row
      }) |>
        purrr::discard(~ length(.) == 1) |>
        purrr::list_rbind()
      if (nrow(commits_table) > 0) {
        commits_table <- commits_table |>
          dplyr::mutate(
            organization = org,
            api_url = self$gql_api_url
          ) |>
          dplyr::relocate(
            repo_name,
            .before = id
          ) |>
          dplyr::relocate(
            any_of(c("author_login", "author_name")),
            .after = author
          ) |>
          dplyr::relocate(
            repo_url,
            .before = api_url
          )
      }
      commits_table <- private$fill_empty_authors(commits_table)
      return(commits_table)
    },

    get_files_from_org = function(org,
                                  owner_type,
                                  repos_data,
                                  file_paths = NULL,
                                  host_files_structure = NULL,
                                  verbose = TRUE) {
      repositories <- repos_data[["paths"]]
      def_branches <- repos_data[["def_branches"]]
      org_files_list <- private$get_repositories_with_files(
        repositories = repositories,
        def_branches = def_branches,
        org = org,
        file_paths = file_paths,
        host_files_structure = host_files_structure,
        verbose = verbose
      )
      names(org_files_list) <- repositories
      org_files_list <- purrr::map(org_files_list, function(repo_data) {
        purrr::discard(repo_data, function(file_data) {
          length(file_data$file) == 0
        })
      })
      return(org_files_list)
    },

    prepare_files_table = function(files_response, org) {
      if (!is.null(files_response)) {
        files_table <- purrr::map(files_response, function(repository) {
          purrr::discard(repository, ~ length(.$file) == 0) |>
            purrr::imap(function(file_data, file_name) {
              data.frame(
                "repo_id" = file_data$repo_id,
                "repo_name" = file_data$repo_name,
                "organization" = org,
                "file_path" = file_name,
                "file_content" = file_data$file$text %||% NA,
                "file_size" = file_data$file$byteSize,
                "file_id" = file_data$file$oid,
                "repo_url" = file_data$repo_url,
                "commit_sha" = file_data$defaultBranchRef$target$oid
              )
            }) |>
            purrr::list_rbind()
        }) |>
          purrr::list_rbind()
      } else {
        files_table <- NULL
      }
      return(files_table)
    },

    prepare_user_table = function(user_response) {
      if (!is.null(user_response$data$user)) {
        user_data <- user_response$data$user
        user_data[["name"]] <- user_data$name %||% ""
        user_data[["starred_repos"]] <- user_data$starred_repos$totalCount
        user_data[["commits"]] <- user_data$contributions$totalCommitContributions
        user_data[["issues"]] <- user_data$contributions$totalIssueContributions
        user_data[["pull_requests"]] <- user_data$contributions$totalPullRequestContributions
        user_data[["reviews"]] <- user_data$contributions$totalPullRequestReviewContributions
        user_data[["contributions"]] <- NULL
        user_data[["email"]] <- user_data$email %||% ""
        user_data[["location"]] <- user_data$location %||% ""
        user_data[["web_url"]] <- user_data$web_url %||% ""
        user_table <- tibble::as_tibble(user_data) |>
          dplyr::relocate(c(commits, issues, pull_requests, reviews),
            .after = starred_repos
          )
      } else {
        user_table <- NULL
      }
      return(user_table)
    },

    get_release_logs_from_org = function(repos_names, org, verbose = TRUE) {
      release_responses <- gitstats_map(repos_names, function(repository) {
        releases_from_repo_query <- self$gql_query$releases_from_repo()
        response <- self$gql_response(
          gql_query = releases_from_repo_query,
          vars = list(
            "org" = org,
            "repo" = repository
          ),
          verbose = verbose
        )
        return(response)
      }) |>
        purrr::discard(~ length(.$data$repository$releases$nodes) == 0)
      return(release_responses)
    },

    prepare_releases_table = function(releases_response, org) {
      if (length(releases_response) > 0) {
        releases_table <-
          purrr::map(releases_response, function(release) {
            release_table <- purrr::map(release$data$repository$releases$nodes, function(node) {
              data.frame(
                release_name = node$name,
                release_tag = node$tagName,
                published_at = gts_to_posixt(node$publishedAt),
                release_url = node$url,
                release_log = node$description
              )
            }) |>
              purrr::list_rbind() |>
              dplyr::mutate(
                repo_name = release$data$repository$name,
                repo_url = release$data$repository$url
              ) |>
              dplyr::relocate(
                repo_name, repo_url,
                .before = release_name
              )
            return(release_table)
          }) |>
          purrr::list_rbind()
      } else {
        releases_table <- NULL
      }
      return(releases_table)
    }
  ),
  private = list(
    get_repos_page = function(login = NULL,
                              type = c("organization"),
                              repo_cursor = "",
                              verbose = TRUE) {
      if (type == "organization") {
        repos_query <- self$gql_query$repos_by_org(
          repo_cursor = repo_cursor
        )
      } else if (type == "user") {
        repos_query <- self$gql_query$repos_by_user(
          repo_cursor = repo_cursor
        )
      }
      response <- self$gql_response(
        gql_query = repos_query,
        vars = list(
          "login" = login
        ),
        verbose = verbose
      )
      private$handle_graphql_error(
        responses_list = response,
        verbose = verbose
      )
      return(response)
    },

    get_commits_from_one_repo = function(org,
                                         repo,
                                         since,
                                         until,
                                         verbose = TRUE) {
      next_page <- TRUE
      full_commits_list <- list()
      commits_cursor <- ""
      while (next_page) {
        commits_response <- private$get_commits_page_from_repo(
          org = org,
          repo = repo,
          since = since,
          until = parse_until_param(until),
          commits_cursor = commits_cursor,
          verbose = verbose
        )
        if (!inherits(commits_response, "graphql_error")) {
          commits_list <- commits_response$data$repository$defaultBranchRef$target$history$edges
          next_page <- commits_response$data$repository$defaultBranchRef$target$history$pageInfo$hasNextPage
          if (is.null(next_page)) next_page <- FALSE
          if (is.null(commits_list)) commits_list <- list()
          if (next_page) {
            commits_cursor <- commits_response$data$repository$defaultBranchRef$target$history$pageInfo$endCursor
          } else {
            commits_cursor <- ""
          }
        } else {
          commits_list <- NULL
          next_page <- FALSE
        }
        full_commits_list <- append(full_commits_list, commits_list)
      }
      return(full_commits_list)
    },

    get_commits_page_from_repo = function(org,
                                          repo,
                                          since,
                                          until,
                                          commits_cursor = "",
                                          verbose = TRUE) {
      commits_from_repo_query <- self$gql_query$commits_from_repo(
        commits_cursor = commits_cursor
      )
      response <- self$gql_response(
        gql_query = commits_from_repo_query,
        vars = list(
          "org" = org,
          "repo" = repo,
          "since" = date_to_gts(since),
          "until" = date_to_gts(until)
        ),
        verbose = verbose
      )
      if (inherits(response, "graphql_error")) {
        response <- self$gql_response(
          gql_query = commits_from_repo_query,
          vars = list(
            "org" = org,
            "repo" = repo,
            "since" = date_to_gts(since),
            "until" = date_to_gts(until)
          ),
          verbose = verbose
        )
      }
      return(response)
    },

    get_pr_page_from_repo = function(org,
                                     repo,
                                     pr_cursor = "",
                                     verbose = TRUE) {
      pr_from_repo_query <- self$gql_query$pull_requests_from_repo(
        pr_cursor = pr_cursor
      )
      response <- self$gql_response(
        gql_query = pr_from_repo_query,
        vars = list(
          "org" = org,
          "repo" = repo
        ),
        verbose = verbose
      )
      if (inherits(response, "graphql_error")) {
        response <- self$gql_response(
          gql_query = pr_from_repo_query,
          vars = list(
            "org" = org,
            "repo" = repo
          ),
          verbose = verbose
        )
      }
      return(response)
    },

    get_pr_from_one_repo = function(org,
                                    repo,
                                    verbose = TRUE) {
      next_page <- TRUE
      full_pr_list <- list()
      pr_cursor <- ""
      while (next_page) {
        pr_response <- private$get_pr_page_from_repo(
          org = org,
          repo = repo,
          pr_cursor = pr_cursor,
          verbose = verbose
        )
        pr_list <- pr_response$data$repository$pullRequests$edges
        next_page <- pr_response$data$repository$pullRequests$pageInfo$hasNextPage
        if (is.null(next_page)) next_page <- FALSE
        if (is.null(pr_list)) pr_list <- list()
        if (next_page) {
          pr_cursor <- pr_response$data$repository$pullRequests$pageInfo$endCursor
        } else {
          pr_cursor <- ""
        }
        full_pr_list <- append(full_pr_list, pr_list)
      }
      return(full_pr_list)
    },

    get_issues_from_one_repo = function(org,
                                        repo,
                                        verbose = TRUE) {
      next_page <- TRUE
      full_issues_list <- list()
      issues_cursor <- ""
      while (next_page) {
        issues_response <- private$get_issues_page_from_repo(
          org = org,
          repo = repo,
          issues_cursor = issues_cursor,
          verbose = verbose
        )
        issues_list <- issues_response$data$repository$issues$edges
        next_page <- issues_response$data$repository$issues$pageInfo$hasNextPage
        if (is.null(next_page)) next_page <- FALSE
        if (is.null(issues_list)) issues_list <- list()
        if (next_page) {
          issues_cursor <- issues_response$data$repository$issues$pageInfo$endCursor
        } else {
          issues_cursor <- ""
        }
        full_issues_list <- append(full_issues_list, issues_list)
      }
      return(full_issues_list)
    },

    get_issues_page_from_repo = function(org,
                                         repo,
                                         issues_cursor = "",
                                         verbose = TRUE) {
      issues_from_repo_query <- self$gql_query$issues_from_repo(
        issues_cursor = issues_cursor
      )
      response <- self$gql_response(
        gql_query = issues_from_repo_query,
        vars = list(
          "org" = org,
          "repo" = repo
        ),
        verbose = verbose
      )
      if (inherits(response, "graphql_error")) {
        response <- self$gql_response(
          gql_query = issues_from_repo_query,
          vars = list(
            "org" = org,
            "repo" = repo
          ),
          verbose = verbose
        )
      }
      return(response)
    },

    get_repositories_with_files = function(repositories,
                                           def_branches,
                                           org,
                                           host_files_structure,
                                           file_paths,
                                           verbose) {
      gitstats_map2(repositories, def_branches, function(repo, def_branch) {
        if (!is.null(host_files_structure)) {
          file_paths <- private$get_path_from_files_structure(
            host_files_structure = host_files_structure,
            org = org,
            repo = repo
          )
        } else if (is.null(host_files_structure)) {
          file_paths <- file_paths[grepl(text_files_pattern, file_paths)]
        }
        repo_files_list <- purrr::map(file_paths, function(file_path) {
          private$get_file_response(
            org = org,
            repo = repo,
            def_branch = def_branch,
            file_path = file_path,
            files_query = self$gql_query$file_blob_from_repo(),
            verbose = verbose
          )
        }) |>
          purrr::map(~ .$data$repository)
        names(repo_files_list) <- file_paths
        return(repo_files_list)
      })
    },

    get_file_response = function(org, repo, def_branch, file_path, files_query, verbose = TRUE) {
      expression <- paste0(def_branch, ":", file_path)
      files_response <- self$gql_response(
        gql_query = files_query,
        vars = list(
          "org" = org,
          "repo" = repo,
          "expression" = expression
        ),
        verbose = verbose
      )
      return(files_response)
    },

    fill_empty_authors = function(commits_table) {
      if (length(commits_table) > 0) {
        commits_table <- commits_table |>
          dplyr::rowwise() |>
          dplyr::mutate(
            author_name = ifelse(is.na(author_name) & is_name(author), author, author_name),
            author_login = ifelse(is.na(author_login) & is_login(author), author, author_login)
          )
      }
      return(commits_table)
    }
  )
)
