text: "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  const tslib_1 = require("tslib");
  const util = require("util");
  const chalk_1 = require("chalk");
  const lodash = require("lodash");
  const guards_1 = require("../guards");
  const http_1 = require("./utils/http");
  const fs_1 = require("@ionic/cli-framework/utils/fs");
  const errors_1 = require("./errors");
  const FORMAT_ERROR_BODY_MAX_LENGTH = 1000;
  exports.CONTENT_TYPE_JSON = 'application/json';
  exports.ERROR_UNKNOWN_CONTENT_TYPE = 'UNKNOWN_CONTENT_TYPE';
  exports.ERROR_UNKNOWN_RESPONSE_FORMAT = 'UNKNOWN_RESPONSE_FORMAT';
  let CAS;
  let CERTS;
  let KEYS;
  function createRawRequest(method, url) {
      return tslib_1.__awaiter(this, void 0, void 0, function* () {
          const superagent = yield Promise.resolve().then(() => require('superagent'));
          const req = superagent(method, url);
          return { req };
      });
  }
  exports.createRawRequest = createRawRequest;
  class ResourceClient {
      applyModifiers(req, modifiers) {
          if (!modifiers) {
              return;
          }
          if (modifiers.fields) {
              req.query({ fields: modifiers.fields });
          }
      }
      applyAuthentication(req, token) {
          req.set('Authorization', `Bearer ${token}`);
      }
  }
  exports.ResourceClient = ResourceClient;
  function createRequest(config, method, url) {
      return tslib_1.__awaiter(this, void 0, void 0, function* () {
          const c = yield config.load();
          const [proxy,] = http_1.getGlobalProxy();
          const { req } = yield createRawRequest(method, url);
          if (proxy && req.proxy) {
              req.proxy(proxy);
          }
          if (c.ssl) {
              const conform = (p) => {
                  if (!p) {
                      return [];
                  }
                  if (typeof p === 'string') {
                      return [p];
                  }
                  return p;
              };
              if (!CAS) {
                  CAS = yield Promise.all(conform(c.ssl.cafile).map(p => fs_1.fsReadFile(p, { encoding: 'utf8' })));
              }
              if (!CERTS) {
                  CERTS = yield Promise.all(conform(c.ssl.certfile).map(p => fs_1.fsReadFile(p, { encoding: 'utf8' })));
              }
              if (!KEYS) {
                  KEYS = yield Promise.all(conform(c.ssl.keyfile).map(p => fs_1.fsReadFile(p, { encoding: 'utf8' })));
              }
              if (CAS.length > 0) {
                  req.ca(CAS);
              }
              if (CERTS.length > 0) {
                  req.cert(CERTS);
              }
              if (KEYS.length > 0) {
                  req.key(KEYS);
              }
          }
          return { req };
      });
  }
  exports.createRequest = createRequest;
  function download(config, url, ws, opts) {
      return tslib_1.__awaiter(this, void 0, void 0, function* () {
          const { req } = yield createRequest(config, 'get', url);
          const progressFn = opts ? opts.progress : undefined;
          return new Promise((resolve, reject) => {
              req
                  .on('response', res => {
                  if (res.statusCode !== 200) {
                      reject(new Error(`Encountered bad status code (${res.statusCode}) for ${url}\n` +
                          `This could mean the server is experiencing difficulties right now--please try again later.`));
                  }
                  if (progressFn) {
                      let loaded = 0;
                      const total = Number(res.headers['content-length']);
                      res.on('data', chunk => {
                          loaded += chunk.length;
                          progressFn(loaded, total);
                      });
                  }
              })
                  .on('error', err => {
                  if (err.code === 'ECONNABORTED') {
                      reject(new Error(`Timeout of ${err.timeout}ms reached for ${url}`));
                  }
                  else {
                      reject(err);
                  }
              })
                  .on('end', resolve);
              req.pipe(ws);
          });
      });
  }
  exports.download = download;
  class Client {
      constructor(config) {
          this.config = config;
      }
      make(method, path) {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              const url = path.startsWith('http://') || path.startsWith('https://') ? path : `${yield this.config.getAPIUrl()}${path}`;
              const { req } = yield createRequest(this.config, method, url);
              req
                  .set('Content-Type', exports.CONTENT_TYPE_JSON)
                  .set('Accept', exports.CONTENT_TYPE_JSON);
              return { req };
          });
      }
      do(req) {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              const res = yield req;
              const r = transformAPIResponse(res);
              if (guards_1.isAPIResponseError(r)) {
                  throw new errors_1.FatalException('API request was successful, but the response output format was that of an error.\n'
                      + formatAPIError(req, r));
              }
              return r;
          });
      }
      paginate(args) {
          return new Paginator(Object.assign({ client: this }, args));
      }
  }
  exports.Client = Client;
  class Paginator {
      constructor({ client, reqgen, guard, state, max }) {
          const defaultState = { page: 1, done: false, loaded: 0 };
          this.client = client;
          this.reqgen = reqgen;
          this.guard = guard;
          this.max = max;
          if (!state) {
              state = Object.assign({}, defaultState);
          }
          this.state = lodash.assign({}, state, defaultState);
      }
      next() {
          if (this.state.done) {
              return { done: true }; // TODO: why can't I exclude value?
          }
          return {
              done: false,
              value: (() => tslib_1.__awaiter(this, void 0, void 0, function* () {
                  const { req } = yield this.reqgen();
                  req.query(lodash.pick(this.state, ['page', 'page_size']));
                  const res = yield this.client.do(req);
                  if (!this.guard(res)) {
                      throw createFatalAPIFormat(req, res);
                  }
                  this.state.loaded += res.data.length;
                  if (res.data.length === 0 || // no resources in this page, we're done
                      (typeof this.max === 'number' && this.state.loaded >= this.max) || // met or exceeded maximum requested
                      (typeof this.state.page_size === 'number' && res.data.length < this.state.page_size) // number of resources less than page size, so nothing on next page
                  ) {
                      this.state.done = true;
                  }
                  this.state.page++;
                  return res;
              }))(),
          };
      }
      [Symbol.iterator]() {
          return this;
      }
  }
  exports.Paginator = Paginator;
  class TokenPaginator {
      constructor({ client, reqgen, guard, state, max }) {
          const defaultState = { done: false, loaded: 0 };
          this.client = client;
          this.reqgen = reqgen;
          this.guard = guard;
          this.max = max;
          if (!state) {
              state = Object.assign({}, defaultState);
          }
          this.state = lodash.assign({}, state, defaultState);
      }
      next() {
          if (this.state.done) {
              return { done: true }; // TODO: why can't I exclude value?
          }
          return {
              done: false,
              value: (() => tslib_1.__awaiter(this, void 0, void 0, function* () {
                  const { req } = yield this.reqgen();
                  if (this.state.page_token) {
                      req.query({ page_token: this.state.page_token });
                  }
                  const res = yield this.client.do(req);
                  if (!this.isPageTokenResponseMeta(res.meta)) {
                      throw createFatalAPIFormat(req, res);
                  }
                  const nextPageToken = res.meta.next_page_token;
                  if (!this.guard(res)) {
                      throw createFatalAPIFormat(req, res);
                  }
                  this.state.loaded += res.data.length;
                  if (res.data.length === 0 || // no resources in this page, we're done
                      (typeof this.max === 'number' && this.state.loaded >= this.max) || // met or exceeded maximum requested
                      !nextPageToken // no next page token, must be done
                  ) {
                      this.state.done = true;
                  }
                  this.state.page_token = nextPageToken;
                  return res;
              }))(),
          };
      }
      isPageTokenResponseMeta(m) {
          const meta = m;
          return meta
              && (!meta.prev_page_token || typeof meta.prev_page_token === 'string')
              && (!meta.next_page_token || typeof meta.next_page_token === 'string');
      }
      [Symbol.iterator]() {
          return this;
      }
  }
  exports.TokenPaginator = TokenPaginator;
  function transformAPIResponse(r) {
      if (r.status === 204) {
          r.body = { data: null, meta: { status: 204, version: '', request_id: '' } };
      }
      if (r.status !== 204 && r.type !== exports.CONTENT_TYPE_JSON) {
          throw exports.ERROR_UNKNOWN_CONTENT_TYPE;
      }
      let j = r.body;
      if (!j.meta) {
          throw exports.ERROR_UNKNOWN_RESPONSE_FORMAT;
      }
      return j;
  }
  exports.transformAPIResponse = transformAPIResponse;
  function createFatalAPIFormat(req, res) {
      return new errors_1.FatalException('API request was successful, but the response format was unrecognized.\n'
          + formatAPIResponse(req, res));
  }
  exports.createFatalAPIFormat = createFatalAPIFormat;
  function formatSuperAgentError(e) {
      const res = e.response;
      const req = res.request;
      const statusCode = e.response.status;
      let f = '';
      try {
          const r = transformAPIResponse(res);
          f += formatAPIResponse(req, r);
      }
      catch (e) {
          f += `HTTP Error ${statusCode}: ${req.method.toUpperCase()} ${req.url}\n`;
          // TODO: do this only if verbose?
          f += '\n' + (res.text ? res.text.substring(0, FORMAT_ERROR_BODY_MAX_LENGTH) : '<no buffered body>');
          if (res.text && res.text.length > FORMAT_ERROR_BODY_MAX_LENGTH) {
              f += ` ...\n\n[ truncated ${res.text.length - FORMAT_ERROR_BODY_MAX_LENGTH} characters ]`;
          }
      }
      return chalk_1.default.bold(chalk_1.default.red(f));
  }
  exports.formatSuperAgentError = formatSuperAgentError;
  function formatAPIResponse(req, r) {
      if (guards_1.isAPIResponseSuccess(r)) {
          return formatAPISuccess(req, r);
      }
      else {
          return formatAPIError(req, r);
      }
  }
  exports.formatAPIResponse = formatAPIResponse;
  function formatAPISuccess(req, r) {
      return `Request: ${req.method} ${req.url}\n`
          + `Response: ${r.meta.status}\n`
          + `Body: \n${util.inspect(r.data, { colors: chalk_1.default.enabled })}`;
  }
  exports.formatAPISuccess = formatAPISuccess;
  function formatAPIError(req, r) {
      return `Request: ${req.method} ${req.url}\n`
          + `Response: ${r.meta.status}\n`
          + `Body: \n${util.inspect(r.error, { colors: chalk_1.default.enabled })}`;
  }
  exports.formatAPIError = formatAPIError;
  