text: import { format, parse } from 'es-content-type';
  import { eTag } from '@tinyhttp/etag';
  import { Stats, statSync, createReadStream } from 'fs';
  import { STATUS_CODES } from 'http';
  import { isAbsolute, join, extname } from 'path';
  import { contentType } from 'es-mime-types';
  
  /**
   * Respond with stringified JSON object
   * @param res Response
   */
  const json = (res) => (body, ...args) => {
      res.setHeader('Content-Type', 'application/json');
      if (typeof body === 'object' && body != null)
          res.end(JSON.stringify(body, null, 2), ...args);
      else if (typeof body === 'string')
          res.end(body, ...args);
      else if (body == null) {
          res.removeHeader('Content-Length');
          res.removeHeader('Transfer-Encoding');
          res.end(null, ...args);
      }
      return res;
  };
  
  const createETag = (body, encoding) => {
      if (body instanceof Stats) {
          return eTag(body, { weak: true });
      }
      else {
          return eTag(!Buffer.isBuffer(body) ? Buffer.from(body, encoding) : body, { weak: true });
      }
  };
  function setCharset(type, charset) {
      const parsed = parse(type);
      parsed.parameters.charset = charset;
      return format(parsed);
  }
  
  /**
   * Sends the HTTP response.
   *
   * The body parameter can be a Buffer object, a string, an object, or an array.
   *
   * This method performs many useful tasks for simple non-streaming responses.
   * For example, it automatically assigns the Content-Length HTTP response header field (unless previously defined) and provides automatic HEAD and HTTP cache freshness support.
   *
   * @param req Request
   * @param res Response
   */
  const send = (req, res) => (body) => {
      let bodyToSend = body;
      // in case of object - turn it to json
      if (typeof body === 'object' && body !== null) {
          bodyToSend = JSON.stringify(body, null, 2);
      }
      else {
          if (typeof body === 'string') {
              // reflect this in content-type
              const type = res.getHeader('Content-Type');
              if (type && typeof type === 'string') {
                  res.setHeader('Content-Type', setCharset(type, 'utf-8'));
              }
              else
                  res.setHeader('Content-Type', setCharset('text/html', 'utf-8'));
          }
      }
      // Set encoding
      const encoding = 'utf8';
      // populate ETag
      let etag;
      if (body && !res.getHeader('etag') && (etag = createETag(bodyToSend, encoding))) {
          res.setHeader('etag', etag);
      }
      // strip irrelevant headers
      if (res.statusCode === 204 || res.statusCode === 304) {
          res.removeHeader('Content-Type');
          res.removeHeader('Content-Length');
          res.removeHeader('Transfer-Encoding');
          bodyToSend = '';
      }
      if (req.method === 'HEAD') {
          res.end('');
          return;
      }
      if (typeof body === 'object') {
          if (body == null) {
              res.end('');
              return;
          }
          else if (Buffer.isBuffer(body)) {
              if (!res.getHeader('Content-Type'))
                  res.setHeader('content-type', 'application/octet-stream');
          }
          else
              json(res)(bodyToSend, encoding) ;
      }
      else {
          if (typeof bodyToSend !== 'string')
              bodyToSend = bodyToSend.toString();
          res.end(bodyToSend, encoding) ;
      }
      return res;
  };
  
  /**
   * Sets the response HTTP status code to statusCode and send its string representation as the response body.
   *
   * If an unsupported status code is specified, the HTTP status is still set to statusCode and the string version of the code is sent as the response body.
   *
   * @param req Request
   * @param res Response
   */
  const sendStatus = (req, res) => (statusCode) => {
      const body = STATUS_CODES[statusCode] || String(statusCode);
      res.statusCode = statusCode;
      res.setHeader('Content-Type', 'text/plain');
      return send(req, res)(body);
  };
  
  /**
   * Sets the HTTP status for the response. It is a chainable alias of Node’s `response.statusCode`.
   *
   * @param res Response
   */
  const status = (res) => (status) => {
      res.statusCode = status;
      return res;
  };
  
  const enableCaching = (res, caching) => {
      let cc = caching.maxAge != null && `public,max-age=${caching.maxAge}`;
      if (cc && caching.immutable)
          cc += ',immutable';
      else if (cc && caching.maxAge === 0)
          cc += ',must-revalidate';
      res.setHeader('Cache-Control', cc);
  };
  /**
   * Sends a file by piping a stream to response.
   *
   * It also checks for extension to set a proper `Content-Type` header.
   *
   * Path argument must be absolute. To use a relative path, specify the `root` option first.
   *
   * @param res Response
   */
  const sendFile = (req, res) => (path, opts = {}, cb) => {
      const { root, headers = {}, encoding = 'utf-8', caching, ...options } = opts;
      if (!isAbsolute(path) && !root)
          throw new TypeError('path must be absolute');
      if (caching)
          enableCaching(res, caching);
      const filePath = root ? join(root, path) : path;
      const stats = statSync(filePath);
      headers['Content-Encoding'] = encoding;
      headers['Last-Modified'] = stats.mtime.toUTCString();
      headers['Content-Type'] = contentType(extname(path));
      headers['ETag'] = createETag(stats, encoding);
      let status = 200;
      if (req.headers['range']) {
          status = 206;
          const [x, y] = req.headers.range.replace('bytes=', '').split('-');
          const end = (options.end = parseInt(y, 10) || stats.size - 1);
          const start = (options.start = parseInt(x, 10) || 0);
          if (start >= stats.size || end >= stats.size) {
              res
                  .writeHead(416, {
                  'Content-Range': `bytes */${stats.size}`
              })
                  .end();
              return res;
          }
          headers['Content-Range'] = `bytes ${start}-${end}/${stats.size}`;
          headers['Content-Length'] = end - start + 1;
          headers['Accept-Ranges'] = 'bytes';
      }
      else {
          headers['Content-Length'] = stats.size;
      }
      for (const [k, v] of Object.entries(headers))
          res.setHeader(k, v);
      res.writeHead(status, headers);
      const stream = createReadStream(filePath, options);
      if (cb)
          stream.on('error', (err) => cb(err)).on('end', () => cb());
      stream.pipe(res);
      return res;
  };
  
  export { enableCaching, json, send, sendFile, sendStatus, status };
  