/**
 * Сжатие картинки фона перед отправкой на сервер.
 *
 * «При добавлении картинок ужимать их до приемлемого размера, чтобы не грузили
 * БД» — требование задачи. Жмём именно на КЛИЕНТЕ: фон всё равно рендерится в
 * браузере, а на backend нет Pillow, и ставить его ради одной операции незачем.
 * Серверная валидация (тип по магическим байтам, лимит размера) при этом
 * остаётся — клиенту верить нельзя, запрос может прийти и мимо интерфейса.
 */

/** Больше 1280 по длинной стороне фону не нужно — столько же у дефолтных сцен (1280×720). */
const MAX_SIDE_PX = 1280

/** Ступени качества WebP: жмём сильнее, только если с прошлой ступени не уложились. */
const QUALITY_STEPS = [0.82, 0.7, 0.6]

/** Цель по весу — 600 КБ. Серверный лимит вдвое больше (2 МБ), запас на подстраховку. */
const TARGET_BYTES = 600 * 1024

export class ImageDecodeError extends Error {}

/**
 * Ужать картинку до `MAX_SIDE_PX` по длинной стороне и вернуть WebP-Blob.
 *
 * Пропорции сохраняются: кадрировать под 16:9 здесь нельзя — какая часть
 * картинки важна, знает только пользователь, а сама библиотека замены фона
 * вписывает изображение в кадр сама.
 */
export async function resizeImageForBackground(file: File): Promise<Blob> {
  const bitmap = await decode(file)
  try {
    const scale = Math.min(1, MAX_SIDE_PX / Math.max(bitmap.width, bitmap.height))
    const width = Math.max(1, Math.round(bitmap.width * scale))
    const height = Math.max(1, Math.round(bitmap.height * scale))

    const canvas = document.createElement('canvas')
    canvas.width = width
    canvas.height = height
    const context = canvas.getContext('2d')
    if (!context) throw new ImageDecodeError('canvas 2d context unavailable')
    context.drawImage(bitmap, 0, 0, width, height)

    let result: Blob | null = null
    for (const quality of QUALITY_STEPS) {
      result = await toBlob(canvas, quality)
      if (result.size <= TARGET_BYTES) return result
    }
    // Даже на самом низком качестве не уложились (огромная детализованная
    // картинка) — отдаём как есть: серверный лимит вдвое выше цели, и шанс
    // пройти его остаётся; иначе пользователь получит честную 413.
    return result!
  } finally {
    bitmap.close()
  }
}

/** Декодировать файл в `ImageBitmap`; битый/не-картинка → `ImageDecodeError`. */
async function decode(file: File): Promise<ImageBitmap> {
  try {
    return await createImageBitmap(file)
  } catch (err) {
    throw new ImageDecodeError(String(err))
  }
}

function toBlob(canvas: HTMLCanvasElement, quality: number): Promise<Blob> {
  return new Promise((resolve, reject) => {
    canvas.toBlob(
      (blob) => (blob ? resolve(blob) : reject(new ImageDecodeError('canvas.toBlob returned null'))),
      'image/webp',
      quality,
    )
  })
}
