import { writeFile, readFile, unlink } from 'fs/promises';
import { execFile } from 'child_process';
import { promisify } from 'util';
import os from 'os';
import path from 'path';

const execFileP = promisify(execFile);

// Detect image format from magic bytes
function detectFormat(buf) {
  if (buf.length >= 12 && buf.toString('ascii', 0, 4) === 'RIFF' && buf.toString('ascii', 8, 12) === 'WEBP') return 'webp';
  if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) return 'png';
  if (buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) return 'jpeg';
  if (buf.toString('ascii', 0, 3) === 'GIF') return 'gif';
  return 'unknown';
}

// Check if image (e.g. webp) is animated (more than 1 frame)
async function isAnimated(inputPath) {
  try {
    const { stdout } = await execFileP('ffprobe', [
      '-v', 'error', '-select_streams', 'v:0', '-count_frames',
      '-show_entries', 'stream=nb_read_frames',
      '-of', 'default=nokey=1:noprint_wrappers=1', inputPath,
    ]);
    return parseInt(stdout.trim()) > 1;
  } catch {
    return false;
  }
}

/**
 * Ensure an image buffer is in a format Discord accepts (PNG/JPEG/GIF).
 * WebP (and other unsupported formats) are converted via ffmpeg:
 *   - static  -> PNG
 *   - animated -> GIF
 * Returns the (possibly converted) buffer.
 */
export async function ensureDiscordImage(buf) {
  const fmt = detectFormat(buf);
  if (fmt === 'png' || fmt === 'jpeg' || fmt === 'gif') return buf;

  // webp / unknown -> convert
  const tmpIn = path.join(os.tmpdir(), `emoji-in-${Date.now()}-${Math.random().toString(36).slice(2)}`);
  await writeFile(tmpIn, buf);
  try {
    const animated = await isAnimated(tmpIn);
    const ext = animated ? 'gif' : 'png';
    const tmpOut = `${tmpIn}.${ext}`;
    // -pix_fmt rgba: keep alpha channel so transparent PNG/WebP stays transparent
    const args = animated
      ? ['-y', '-i', tmpIn, tmpOut]
      : ['-y', '-i', tmpIn, '-frames:v', '1', '-pix_fmt', 'rgba', tmpOut];
    await execFileP('ffmpeg', args);
    const out = await readFile(tmpOut);
    await unlink(tmpOut).catch(() => {});
    return out;
  } finally {
    await unlink(tmpIn).catch(() => {});
  }
}

/**
 * Shrink an image (PNG/GIF) under maxBytes by progressively downscaling via ffmpeg.
 * Discord emoji hard-limit is 256 KB regardless of input size, so we accept big
 * uploads and resize them down. Transparency (alpha) is preserved for PNG.
 * Returns the smallest buffer produced (caller should still verify final size).
 */
export async function fitEmojiSize(buf, maxBytes = 256 * 1024) {
  if (buf.length <= maxBytes) return buf;

  const animated = detectFormat(buf) === 'gif';
  const ext = animated ? 'gif' : 'png';
  const tmpIn = path.join(os.tmpdir(), `emoji-fit-${Date.now()}-${Math.random().toString(36).slice(2)}`);
  await writeFile(tmpIn, buf);

  try {
    let best = buf;
    // Emoji render <=128px in Discord, so downscaling barely affects appearance
    for (const w of [128, 112, 96, 80, 64, 48, 32, 24]) {
      const tmpOut = `${tmpIn}-${w}.${ext}`;
      const scale = `scale=${w}:-1:flags=lanczos`;
      const args = animated
        ? ['-y', '-i', tmpIn, '-vf', scale, tmpOut]
        : ['-y', '-i', tmpIn, '-frames:v', '1', '-vf', scale, '-pix_fmt', 'rgba', tmpOut];
      try {
        await execFileP('ffmpeg', args);
        const out = await readFile(tmpOut);
        await unlink(tmpOut).catch(() => {});
        if (out.length < best.length) best = out;
        if (out.length <= maxBytes) return out;
      } catch { /* try next size */ }
    }
    return best; // smallest we managed; caller verifies and errors if still over
  } finally {
    await unlink(tmpIn).catch(() => {});
  }
}

/**
 * Full emoji prep: convert to a Discord-supported format, then resize under 256 KB.
 */
export async function ensureDiscordEmoji(buf) {
  const converted = await ensureDiscordImage(buf);
  return fitEmojiSize(converted, 256 * 1024);
}
