export const CHAT_IMAGE_UPLOAD_MAX_INPUT_BYTES = 8 * 1024 * 1024;
export const CHAT_IMAGE_UPLOAD_MAX_OUTPUT_BYTES = 1.5 * 1024 * 1024;
export const CHAT_IMAGE_MAX_SIDE = 1920;
const MAX_PIXELS = 2_500_000;
const ACCEPTED_IMAGE_MIME = new Set(['image/jpeg', 'image/png', 'image/webp']);

type CompressOptions = {
  maxInputBytes?: number;
  maxOutputBytes?: number;
  maxDimension?: number;
};

function loadImageFromUrl(src: string): Promise<HTMLImageElement> {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = () => reject(new Error('图片读取失败'));
    img.src = src;
  });
}

function computeOutputMime(inputType: string) {
  if (ACCEPTED_IMAGE_MIME.has(inputType)) {
    return 'image/jpeg';
  }
  return 'image/jpeg';
}

function canvasToFile(canvas: HTMLCanvasElement, mime: string, quality: number, fileName: string): Promise<File> {
  return new Promise((resolve, reject) => {
    canvas.toBlob(
      (blob) => {
        if (!blob) {
          reject(new Error('图片压缩失败'));
          return;
        }
        resolve(new File([blob], fileName, { type: mime }));
      },
      mime,
      quality,
    );
  });
}

function fitDimensions(width: number, height: number, maxSide: number) {
  if (width <= maxSide && height <= maxSide && width * height <= MAX_PIXELS) {
    return { width, height };
  }

  const scale = Math.min(maxSide / width, maxSide / height, Math.sqrt(MAX_PIXELS / (width * height)));
  return {
    width: Math.max(1, Math.round(width * scale)),
    height: Math.max(1, Math.round(height * scale)),
  };
}

function drawImageToCanvas(img: HTMLImageElement, width: number, height: number) {
  const canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;
  const ctx = canvas.getContext('2d');
  if (!ctx) {
    throw new Error('图片处理环境不可用');
  }

  ctx.fillStyle = '#fff';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  return canvas;
}

async function encodeCanvasWithinLimit(
  canvas: HTMLCanvasElement,
  outputType: string,
  targetName: string,
  maxOutputBytes: number,
) {
  let quality = 0.86;
  let candidate = await canvasToFile(canvas, outputType, quality, targetName);

  while (candidate.size > maxOutputBytes && quality > 0.5) {
    quality = Math.max(0.5, quality - 0.08);
    candidate = await canvasToFile(canvas, outputType, quality, targetName);
  }

  return candidate;
}

export async function compressImageForUpload(
  file: File,
  options: CompressOptions = {},
): Promise<File> {
  const maxInputBytes = options.maxInputBytes ?? CHAT_IMAGE_UPLOAD_MAX_INPUT_BYTES;
  const maxOutputBytes = options.maxOutputBytes ?? CHAT_IMAGE_UPLOAD_MAX_OUTPUT_BYTES;
  const maxDimension = options.maxDimension ?? CHAT_IMAGE_MAX_SIDE;

  if (!file.type.startsWith('image/')) {
    throw new Error('只支持图片文件');
  }

  if (file.size > maxInputBytes) {
    throw new Error(`单张图片大小不能超过 ${(maxInputBytes / (1024 * 1024)).toFixed(0)}MB`);
  }

  if (file.type === 'image/gif') {
    if (file.size > maxOutputBytes) {
      throw new Error(`GIF 当前不支持自动压缩，请使用 JPEG/PNG/WEBP，且单张不超过 ${(maxOutputBytes / (1024 * 1024)).toFixed(0)}MB`);
    }
    return file;
  }

  const objectUrl = URL.createObjectURL(file);
  try {
    const img = await loadImageFromUrl(objectUrl);
    let { width, height } = fitDimensions(img.width, img.height, maxDimension);
    const needsResize = width !== img.width || height !== img.height;

    if (!needsResize && file.size <= maxOutputBytes) {
      return file;
    }

    const outputType = computeOutputMime(file.type);
    const targetName = `${file.name.replace(/\.[^.]+$/, '') || 'image'}.jpg`;

    let canvas = drawImageToCanvas(img, width, height);
    let candidate = await encodeCanvasWithinLimit(canvas, outputType, targetName, maxOutputBytes);

    while (candidate.size > maxOutputBytes && Math.max(width, height) > 640) {
      width = Math.max(1, Math.round(width * 0.85));
      height = Math.max(1, Math.round(height * 0.85));
      canvas = drawImageToCanvas(img, width, height);
      candidate = await encodeCanvasWithinLimit(canvas, outputType, targetName, maxOutputBytes);
    }

    if (candidate.size > maxOutputBytes) {
      throw new Error(`图片压缩后仍过大，请上传更小尺寸图片，目标不超过 ${(maxOutputBytes / (1024 * 1024)).toFixed(0)}MB`);
    }

    return candidate;
  } finally {
    URL.revokeObjectURL(objectUrl);
  }
}
