'use strict';

const path = require('node:path');
const zlib = require('node:zlib');
const { URL } = require('node:url');

const MAX_BODY_BYTES = 256 * 1024 * 1024;

function isCompressible(contentType) {
  const t = String(contentType || '');
  return /^text\//.test(t) || /application\/(json|javascript)/.test(t);
}

function send(res, status, body = '', contentType = 'text/plain; charset=utf-8', headers = {}) {
  let bytes = Buffer.isBuffer(body) ? body : Buffer.from(String(body), 'utf8');
  const outHeaders = {
    'Content-Type': contentType,
    'Cache-Control': 'no-store',
    ...headers
  };
  // Gzip text/JSON responses when the client accepts it (single-user app: sync gzip is fine).
  if (res._acceptGzip && isCompressible(contentType) && bytes.length > 1024) {
    bytes = zlib.gzipSync(bytes);
    outHeaders['Content-Encoding'] = 'gzip';
    outHeaders['Vary'] = 'Accept-Encoding';
  }
  outHeaders['Content-Length'] = bytes.length;
  res.writeHead(status, outHeaders);
  res.end(bytes);
}

function json(res, status, data, headers = {}) {
  send(res, status, JSON.stringify(data), 'application/json; charset=utf-8', headers);
}

async function readBody(req) {
  const chunks = [];
  let size = 0;
  for await (const chunk of req) {
    size += chunk.length;
    if (size > MAX_BODY_BYTES) throw new Error('request body too large');
    chunks.push(chunk);
  }
  return Buffer.concat(chunks);
}

async function readJson(req) {
  const body = await readBody(req);
  if (!body.length) return {};
  return JSON.parse(body.toString('utf8'));
}

function safeName(name, fallback = 'file') {
  return String(name || fallback)
    .replace(/[<>:"/\\|?*\x00-\x1f]/g, '_')
    .replace(/\s+/g, ' ')
    .trim()
    .slice(0, 180) || fallback;
}

function mimeByExt(file) {
  const ext = path.extname(file).toLowerCase();
  if (ext === '.html') return 'text/html; charset=utf-8';
  if (ext === '.js' || ext === '.mjs') return 'text/javascript; charset=utf-8';
  if (ext === '.css') return 'text/css; charset=utf-8';
  if (ext === '.json') return 'application/json; charset=utf-8';
  if (ext === '.png') return 'image/png';
  if (ext === '.webp') return 'image/webp';
  if (ext === '.gif') return 'image/gif';
  if (ext === '.svg') return 'image/svg+xml';
  if (ext === '.woff2') return 'font/woff2';
  if (ext === '.woff') return 'font/woff';
  if (ext === '.ttf') return 'font/ttf';
  if (ext === '.otf') return 'font/otf';
  return 'image/jpeg';
}

function extFromContentType(type, sourceUrl) {
  const contentType = String(type || '').toLowerCase();
  if (contentType.includes('png')) return '.png';
  if (contentType.includes('webp')) return '.webp';
  if (contentType.includes('gif')) return '.gif';
  if (contentType.includes('jpeg') || contentType.includes('jpg')) return '.jpg';
  try {
    return path.extname(new URL(sourceUrl).pathname) || '.jpg';
  } catch {
    return '.jpg';
  }
}

module.exports = { send, json, readBody, readJson, safeName, mimeByExt, extFromContentType, MAX_BODY_BYTES };
