/**
 * PuppeteerRenderer — Captures frames from a BradlyPlayer composition
 * using a headless browser (puppeteer-core).
 *
 * Flow:
 * 1. Start a local static file server for the renderer build
 * 2. Open headless Chrome → navigate to render page
 * 3. Inject composition props via page.evaluate()
 * 4. For each frame: seekTo(frame) → waitForRender → screenshot
 * 5. Return array of frame image buffers or write to disk
 */
import path from 'path';
import fs from 'fs';
import os from 'os';

export interface RenderFrameOptions {
  /** URL of the running app (e.g. http://localhost:5173) */
  serveUrl: string;
  /** Props to pass to the composition (RenderProps) */
  inputProps: Record<string, any>;
  /** Composition dimensions */
  width: number;
  height: number;
  /** Frames per second */
  fps: number;
  /** Total frames to render */
  durationInFrames: number;
  /** Output directory for frame images */
  framesDir: string;
  /** Image format for frames */
  imageFormat?: 'png' | 'jpeg';
  /** JPEG quality (1-100) */
  quality?: number;
  /** Progress callback */
  onProgress?: (rendered: number, total: number) => void;
}

export interface StillOptions {
  serveUrl: string;
  inputProps: Record<string, any>;
  width: number;
  height: number;
  outputPath: string;
  imageFormat?: 'png' | 'jpeg';
  quality?: number;
  /** Which frame to capture (default: 0) */
  frame?: number;
}

/**
 * Render all frames of a composition to disk as images.
 */
export async function renderFrames(options: RenderFrameOptions): Promise<string[]> {
  const {
    serveUrl,
    inputProps,
    width,
    height,
    fps,
    durationInFrames,
    framesDir,
    imageFormat = 'png',
    quality = 90,
    onProgress,
  } = options;

  // Ensure frames directory exists
  fs.mkdirSync(framesDir, { recursive: true });

  // Dynamic import puppeteer-core (heavy dep, lazy load)
  const puppeteer = await import('puppeteer-core');

  // Find Chrome/Chromium executable
  const executablePath = findChromePath();
  if (!executablePath) {
    throw new Error(
      'Could not find Chrome/Chromium. Install Chrome or set PUPPETEER_EXECUTABLE_PATH env var.'
    );
  }

  const browser = await puppeteer.launch({
    executablePath,
    headless: true,
    args: [
      `--window-size=${width},${height}`,
      '--no-sandbox',
      '--disable-setuid-sandbox',
      '--disable-dev-shm-usage',
    ],
  });

  try {
    const page = await browser.newPage();
    await page.setViewport({ width, height, deviceScaleFactor: 1 });

    // Navigate to the render page
    const renderUrl = `${serveUrl}?renderMode=true`;
    await page.goto(renderUrl, { waitUntil: 'networkidle2', timeout: 30000 });

    // Inject composition props and initialize the player
    await page.evaluate(
      (props, w, h, f, dur) => {
        // The render page exposes this global API
        const api = (window as any).__BRADLY_RENDER__;
        if (!api) throw new Error('Render API not found on page');
        api.init({
          inputProps: props,
          width: w,
          height: h,
          fps: f,
          durationInFrames: dur,
        });
      },
      inputProps, width, height, fps, durationInFrames
    );

    // Wait for player to be ready
    await page.waitForFunction(
      () => (window as any).__BRADLY_RENDER__?.ready === true,
      { timeout: 10000 }
    );

    // Capture each frame
    const framePaths: string[] = [];

    for (let frame = 0; frame < durationInFrames; frame++) {
      // Seek to frame
      await page.evaluate((f) => {
        (window as any).__BRADLY_RENDER__.seekTo(f);
      }, frame);

      // Wait for React to render the new frame and videos to seek
      await page.evaluate(() => {
        return new Promise<void>((resolve) => {
          // Use requestAnimationFrame to ensure paint is complete
          requestAnimationFrame(() => {
            requestAnimationFrame(() => {
              const checkVideoSeeks = () => {
                if (!(window as any).__pendingSeeks) {
                  resolve();
                } else {
                  setTimeout(checkVideoSeeks, 10);
                }
              };
              checkVideoSeeks();
            });
          });
        });
      });

      // Capture screenshot
      const frameName = `frame-${String(frame).padStart(6, '0')}.${imageFormat}`;
      const framePath = path.join(framesDir, frameName);

      await page.screenshot({
        path: framePath,
        type: imageFormat,
        quality: imageFormat === 'jpeg' ? quality : undefined,
        clip: { x: 0, y: 0, width, height },
        omitBackground: imageFormat === 'png',
      });

      framePaths.push(framePath);
      onProgress?.(frame + 1, durationInFrames);
    }

    return framePaths;
  } finally {
    await browser.close();
  }
}

/**
 * Render a single frame (still image).
 */
export async function renderStill(options: StillOptions): Promise<void> {
  const {
    serveUrl,
    inputProps,
    width,
    height,
    outputPath,
    imageFormat = 'png',
    quality = 90,
    frame = 0,
  } = options;

  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bradly-still-'));

  try {
    await renderFrames({
      serveUrl,
      inputProps,
      width,
      height,
      fps: 30,
      durationInFrames: 1,
      framesDir: tmpDir,
      imageFormat,
      quality,
      onProgress: undefined,
    });

    // Move the single frame to the output path
    const frameName = `frame-000000.${imageFormat}`;
    const framePath = path.join(tmpDir, frameName);

    if (fs.existsSync(framePath)) {
      fs.copyFileSync(framePath, outputPath);
    } else {
      throw new Error(`Frame capture failed: ${framePath} not found`);
    }
  } finally {
    // Cleanup temp dir
    fs.rmSync(tmpDir, { recursive: true, force: true });
  }
}

/**
 * Find Chrome/Chromium executable on the system.
 */
function findChromePath(): string | null {
  // 1. Environment variable
  if (process.env.PUPPETEER_EXECUTABLE_PATH) {
    return process.env.PUPPETEER_EXECUTABLE_PATH;
  }

  // 2. Common paths by OS
  const platform = process.platform;

  const candidates: string[] = [];

  if (platform === 'darwin') {
    candidates.push(
      '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
      '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
      '/Applications/Chromium.app/Contents/MacOS/Chromium',
      '/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
    );
  } else if (platform === 'linux') {
    candidates.push(
      '/usr/bin/google-chrome',
      '/usr/bin/google-chrome-stable',
      '/usr/bin/chromium-browser',
      '/usr/bin/chromium',
      '/snap/bin/chromium',
    );
  } else if (platform === 'win32') {
    candidates.push(
      'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
      'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
    );
  }

  for (const candidate of candidates) {
    if (fs.existsSync(candidate)) {
      return candidate;
    }
  }

  return null;
}
