const IMAGE_LIMIT_BYTES = 5 * 1024 * 1024;
const VIDEO_LIMIT_BYTES = 15 * 1024 * 1024;

type UploadedFile = {
  size?: number;
  type?: string;
  mime?: string;
  mimetype?: string;
};

const collectFiles = (value: unknown, files: UploadedFile[], seen = new WeakSet<object>()) => {
  if (!value || typeof value !== 'object') return;
  if (seen.has(value)) return;
  seen.add(value);

  if (Array.isArray(value)) {
    value.forEach((item) => collectFiles(item, files, seen));
    return;
  }

  const maybeFile = value as UploadedFile;
  const mime = maybeFile.mimetype || maybeFile.mime || maybeFile.type || '';
  if (typeof maybeFile.size === 'number' && mime) {
    files.push(maybeFile);
    return;
  }

  Object.values(value as Record<string, unknown>).forEach((item) => collectFiles(item, files, seen));
};

const formatMb = (bytes: number) => `${Math.round(bytes / 1024 / 1024)}MB`;

export default () => {
  return async (ctx: any, next: () => Promise<void>) => {
    const files: UploadedFile[] = [];
    collectFiles(ctx.request.files, files);

    for (const file of files) {
      const mime = file.mimetype || file.mime || file.type || '';
      const size = file.size ?? 0;

      if (mime.startsWith('image/') && size > IMAGE_LIMIT_BYTES) {
        return ctx.throw(413, `Image uploads must be ${formatMb(IMAGE_LIMIT_BYTES)} or smaller.`);
      }

      if (mime.startsWith('video/') && size > VIDEO_LIMIT_BYTES) {
        return ctx.throw(413, `Video uploads must be ${formatMb(VIDEO_LIMIT_BYTES)} or smaller.`);
      }
    }

    await next();
  };
};
