import type { LogEvent } from '@ffmpeg/ffmpeg';
import {
  parseBuildConfiguration,
  parseCodecs,
  parseFilters,
  parseFormats,
  parseNamedComponents,
} from './capability-parser';
import { EngineError } from './ffmpeg-error';
import type { FFmpegAdapter, FFmpegCapabilities } from './ffmpeg.types';

async function captureCommand(
  ffmpeg: FFmpegAdapter,
  args: string[]
): Promise<string> {
  const messages: string[] = [];
  const listener = ({ message }: LogEvent) => messages.push(message);
  ffmpeg.on('log', listener);
  try {
    const exitCode = await ffmpeg.exec(args);
    if (exitCode !== 0) {
      throw new EngineError(
        'execution-failed',
        `FFmpeg capability query failed with exit code ${exitCode}`,
        { exitCode, details: messages.join('\n') }
      );
    }
  } finally {
    ffmpeg.off('log', listener);
  }
  return messages.join('\n');
}

export async function queryCapabilities(
  ffmpeg: FFmpegAdapter
): Promise<FFmpegCapabilities> {
  // A core is a single process and must not receive overlapping executions.
  const versionText = await captureCommand(ffmpeg, ['-version']);
  const buildText = await captureCommand(ffmpeg, ['-buildconf']);
  const formatsText = await captureCommand(ffmpeg, [
    '-hide_banner',
    '-formats',
  ]);
  const codecsText = await captureCommand(ffmpeg, ['-hide_banner', '-codecs']);
  const encodersText = await captureCommand(ffmpeg, [
    '-hide_banner',
    '-encoders',
  ]);
  const decodersText = await captureCommand(ffmpeg, [
    '-hide_banner',
    '-decoders',
  ]);
  const filtersText = await captureCommand(ffmpeg, [
    '-hide_banner',
    '-filters',
  ]);

  const formats = parseFormats(formatsText);
  const codecs = parseCodecs(codecsText);
  const explicitEncoders = parseNamedComponents(encodersText);
  for (const encoder of explicitEncoders) codecs.encoders.add(encoder);
  const explicitDecoders = parseNamedComponents(decodersText);
  for (const decoder of explicitDecoders) codecs.decoders.add(decoder);

  return {
    versionText,
    buildConfiguration: parseBuildConfiguration(`${versionText}\n${buildText}`),
    demuxers: formats.demuxers,
    muxers: formats.muxers,
    decoders: codecs.decoders,
    encoders: codecs.encoders,
    filters: parseFilters(filtersText),
  };
}
