import { execFile } from 'node:child_process';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { promisify } from 'node:util';

import ffmpegStatic from 'ffmpeg-static';

import { getSharp } from './sharpRuntime.mjs';

const execFileAsync = promisify(execFile);

/** Longest edge; presentation uses the original asset. */
export const SCENE_PREVIEW_THUMB_MAX_PX = 320;

/**
 * Builds a small WebP still for graph/list previews. Returns null if generation fails (import still succeeds).
 * For images, prefer a Buffer so callers are not racy with reconcileAssetFiles unlinking paths.
 */
export async function generateScenePreviewThumbnailBytes(
  source: string | Buffer,
  kind: 'image' | 'video',
): Promise<Buffer | null> {
  try {
    const sharp = getSharp();
    if (kind === 'image') {
      return await sharp(source)
        .rotate()
        .resize(SCENE_PREVIEW_THUMB_MAX_PX, SCENE_PREVIEW_THUMB_MAX_PX, {
          fit: 'inside',
          withoutEnlargement: true,
        })
        .webp({ quality: 82 })
        .toBuffer();
    }

    const sourceAbsPath = typeof source === 'string' ? source : null;
    if (!sourceAbsPath) return null;

    const ffmpegPath = ffmpegStatic;
    if (!ffmpegPath) return null;

    const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dnd-thumb-'));
    const tmpPng = path.join(tmpDir, 'frame.png');
    try {
      const seekSeconds = ['0.5', '0.25', '0'];
      let extracted = false;
      for (const ss of seekSeconds) {
        await fs.rm(tmpPng, { force: true }).catch(() => undefined);
        try {
          await execFileAsync(
            ffmpegPath,
            [
              '-hide_banner',
              '-loglevel',
              'error',
              '-y',
              '-ss',
              ss,
              '-i',
              sourceAbsPath,
              '-frames:v',
              '1',
              tmpPng,
            ],
            { maxBuffer: 16 * 1024 * 1024 },
          );
          const st = await fs.stat(tmpPng).catch(() => null);
          if (st !== null && st.isFile() && st.size > 0) {
            extracted = true;
            break;
          }
        } catch {
          /* try next seek */
        }
      }
      if (!extracted) return null;

      return await sharp(tmpPng)
        .resize(SCENE_PREVIEW_THUMB_MAX_PX, SCENE_PREVIEW_THUMB_MAX_PX, {
          fit: 'inside',
          withoutEnlargement: true,
        })
        .webp({ quality: 82 })
        .toBuffer();
    } finally {
      await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => undefined);
    }
  } catch {
    return null;
  }
}
