var __awaiter = this && this.__awaiter || function (thisArg, _arguments, P, generator) {
  function adopt(value) {
    return value instanceof P ? value : new P(function (resolve) {
      resolve(value);
    });
  }

  return new (P || (P = Promise))(function (resolve, reject) {
    function fulfilled(value) {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    }

    function rejected(value) {
      try {
        step(generator["throw"](value));
      } catch (e) {
        reject(e);
      }
    }

    function step(result) {
      result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
    }

    step((generator = generator.apply(thisArg, _arguments || [])).next());
  });
};

var __rest = this && this.__rest || function (s, e) {
  var t = {};

  for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];

  if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
    if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
  }
  return t;
};

import { BeeError, BeeNotAJsonError, BeeRequestError, BeeResponseError } from "./error.js";
import kyFactory from 'ky-universal';
import { normalizeToReadableStream } from "./stream.js";
import { deepMerge } from "./merge.js";
import { isObject, isStrictlyObject } from "./type.js";
const DEFAULT_KY_CONFIG = {
  headers: {
    accept: 'application/json, text/plain, */*',
    'user-agent': `bee-js`
  }
};

function isHttpError(e) {
  return isObject(e) && typeof e.response !== 'undefined';
}

function isHttpRequestError(e) {
  return isObject(e) && typeof e.request !== 'undefined';
}

function headersToObject(header) {
  return [...header.entries()].reduce((obj, [key, val]) => {
    obj[key] = val;
    return obj;
  }, {});
}

function wrapRequest(request) {
  return {
    url: request.url,
    method: request.method.toUpperCase(),
    headers: headersToObject(request.headers)
  };
}

export function wrapRequestClosure(cb) {
  return request => __awaiter(this, void 0, void 0, function* () {
    yield cb(wrapRequest(request));
  });
}
export function wrapResponseClosure(cb) {
  return (request, options, response) => __awaiter(this, void 0, void 0, function* () {
    yield cb({
      headers: headersToObject(response.headers),
      status: response.status,
      statusText: response.statusText,
      request: wrapRequest(request)
    });
  });
}
/**
 * Filters out entries that has undefined value from headers object.
 * Modifies the original object!
 *
 * @param obj
 */
// eslint-disable-next-line @typescript-eslint/ban-types

export function filterHeaders(obj) {
  if (obj === undefined) {
    return undefined;
  }

  isStrictlyObject(obj);
  const typedObj = obj;

  for (const key in typedObj) {
    if (typedObj[key] === undefined) {
      delete typedObj[key];
    }
  }

  if (Object.keys(typedObj).length === 0) {
    return undefined;
  }

  return typedObj;
}
/**
 * Main utility function to make HTTP requests.
 * @param ky
 * @param config
 */

export function http(ky, config) {
  return __awaiter(this, void 0, void 0, function* () {
    try {
      const {
        path,
        responseType
      } = config,
            kyConfig = __rest(config, ["path", "responseType"]);

      const response = yield ky(path, Object.assign(Object.assign({}, kyConfig), {
        searchParams: filterHeaders(kyConfig.searchParams)
      }));

      switch (responseType) {
        case 'stream':
          if (!response.body) {
            throw new BeeError('Response was expected to get data but did not get any!');
          }

          response.data = normalizeToReadableStream(response.body);
          break;

        case 'arraybuffer':
          response.data = yield response.arrayBuffer();
          break;

        case 'json':
          try {
            response.data = yield response.json();
          } catch (e) {
            throw new BeeNotAJsonError();
          }

          break;

        default:
          break;
        // If responseType is not set, then no data are expected
      }

      return response;
    } catch (e) {
      // Passthrough thrown errors
      if (e instanceof BeeNotAJsonError) {
        throw e;
      }

      if (isHttpError(e)) {
        let message; // We store the response body here as it can be read only once in Response's lifecycle so to make it exposed
        // to the user in the BeeResponseError, for further analysis.

        const body = yield e.response.text();

        try {
          // The response can be Bee's JSON with structure `{code, message}` lets try to parse it
          message = JSON.parse(body).message;
        } catch (e) {}

        if (message) {
          throw new BeeResponseError(e.response.status, e.response, body, config, `${e.response.statusText}: ${message}`);
        } else {
          throw new BeeResponseError(e.response.status, e.response, body, config, e.response.statusText);
        }
      } else if (isHttpRequestError(e)) {
        throw new BeeRequestError(e.message, config);
      } else {
        // Node 18 has native `fetch` implementation called Undici. Errors from this implementation have top level generic
        // message "fetch failed" with the more specific error placed into `cause` property. Instead of "fetch failed" we
        // expose the underlying problem.
        if (e.cause) {
          throw new BeeError(e.cause.message);
        }

        throw new BeeError(e.message);
      }
    }
  });
}
export function makeDefaultKy(kyConfig) {
  return kyFactory.create(deepMerge(DEFAULT_KY_CONFIG, kyConfig));
}