import {
  createPlannedInput,
  createPlannedOutput,
  diagnostic,
  normalizeSeconds,
  type SourceReference,
} from './command-utils';
import { freezeCommandPlan, type FFmpegCommandPlan } from './command-plan';

export interface PreviewProxyOptions {
  readonly jobId: string;
  readonly source: SourceReference;
  readonly sourceDurationSeconds: number;
  readonly startSeconds?: number;
  readonly maxDurationSeconds?: number;
  readonly maxWidth?: number;
  readonly includeAudio?: boolean;
  /**
   * Defaults to true for backwards compatibility. Set false for an
   * audio-only source; the proxy then becomes a bounded AAC/M4A derivative.
   */
  readonly hasVideo?: boolean;
}

/**
 * Builds a deliberately bounded H.264/AAC MP4 or audio-only AAC/M4A preview
 * derivative. The proxy is disposable cache data and never replaces or
 * mutates the source.
 */
export function buildPreviewProxyPlan(
  options: PreviewProxyOptions
): FFmpegCommandPlan {
  if (
    !Number.isFinite(options.sourceDurationSeconds) ||
    options.sourceDurationSeconds <= 0
  ) {
    throw new RangeError('Preview source duration must be positive');
  }
  const startSeconds = options.startSeconds ?? 0;
  const maxDurationSeconds = options.maxDurationSeconds ?? 30;
  const maxWidth = options.maxWidth ?? 960;
  const hasVideo = options.hasVideo !== false;
  const includeAudio = options.includeAudio !== false;
  if (
    !Number.isFinite(startSeconds) ||
    startSeconds < 0 ||
    startSeconds >= options.sourceDurationSeconds
  ) {
    throw new RangeError('Preview start must be inside the source duration');
  }
  if (
    !Number.isFinite(maxDurationSeconds) ||
    maxDurationSeconds <= 0 ||
    maxDurationSeconds > 300
  ) {
    throw new RangeError('Preview duration must be from 0 to 300 seconds');
  }
  if (
    hasVideo &&
    (!Number.isSafeInteger(maxWidth) || maxWidth < 160 || maxWidth > 1920)
  ) {
    throw new RangeError(
      'Preview maximum width must be an integer from 160 to 1920'
    );
  }
  if (!hasVideo && !includeAudio) {
    throw new RangeError(
      'An audio-only preview proxy must include an audio stream'
    );
  }
  const durationSeconds = Math.min(
    maxDurationSeconds,
    options.sourceDurationSeconds - startSeconds
  );
  const input = createPlannedInput(options.jobId, options.source);
  const output = createPlannedOutput(options.jobId, {
    baseName: options.source.fileName,
    operation: 'preview',
    extension: hasVideo ? 'mp4' : 'm4a',
  });
  const args = [
    '-ss',
    normalizeSeconds(startSeconds),
    '-t',
    normalizeSeconds(durationSeconds),
    '-i',
    input.path,
    ...(hasVideo
      ? [
          '-map',
          '0:v:0',
          ...(includeAudio ? ['-map', '0:a:0?'] : []),
          '-vf',
          `scale=w='max(2,trunc(min(${maxWidth},iw)/2)*2)':h=-2`,
          '-c:v',
          'libx264',
          '-preset',
          'veryfast',
          '-crf',
          '28',
          '-pix_fmt',
          'yuv420p',
        ]
      : ['-map', '0:a:0', '-vn']),
    ...(includeAudio
      ? ['-c:a', 'aac', '-b:a', '96k', '-ac', '2', '-ar', '48000']
      : ['-an']),
    '-movflags',
    '+faststart',
    '-map_metadata',
    '-1',
    '-map_chapters',
    '-1',
    '-progress',
    'pipe:1',
    '-nostats',
    output.path,
  ];
  return freezeCommandPlan({
    id: `${options.jobId}:preview-proxy`,
    operation: 'preview-proxy',
    inputs: [input],
    temporaryFiles: [],
    args,
    outputs: [output],
    expectedDurationSeconds: durationSeconds,
    requiredCapabilities: {
      muxers: [hasVideo ? 'mp4' : 'ipod'],
      encoders: [
        ...(hasVideo ? ['libx264'] : []),
        ...(includeAudio ? ['aac'] : []),
      ],
      filters: hasVideo ? ['scale'] : [],
    },
    diagnostics: [
      diagnostic(
        'disposable-preview',
        'info',
        hasVideo
          ? `Preview is limited to ${normalizeSeconds(durationSeconds)} seconds and ${maxWidth}px width.`
          : `Audio preview is limited to ${normalizeSeconds(durationSeconds)} seconds at 96 kbit/s AAC.`
      ),
    ],
  });
}
