export interface MediaInputOptions {
  reconnect?: boolean;
  userAgent?: string;
  headers?: Record<string, string>;
}

export type MediaInput =
  | string
  | {
      path: string;
      source?: string;
      inputOptions?: MediaInputOptions;
      singleResolvedStream?: boolean;
      /**
       * The file keeps the original media timestamps instead of starting at zero (a
       * stream-copied window of a longer source). Seek with `-ss` against those
       * absolute timestamps rather than relative to the file's own start time.
       */
      absoluteTimestamps?: boolean;
    };

export type NormalizedMediaInput = {
  path: string;
  inputArgs: string[];
  singleResolvedStream: boolean;
};

const BLOCKED_FFMPEG_HEADER_NAMES = new Set(['authorization', 'cookie', 'proxy-authorization']);

function trimToNonEmptyString(value: unknown): string | null {
  if (typeof value !== 'string') {
    return null;
  }
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : null;
}

function normalizeHeaderName(value: string): string | null {
  const trimmed = value.trim();
  if (!/^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/.test(trimmed)) {
    return null;
  }
  if (BLOCKED_FFMPEG_HEADER_NAMES.has(trimmed.toLowerCase())) {
    return null;
  }
  return trimmed;
}

function serializeFfmpegHeaders(headers: Record<string, string> | undefined): string | null {
  if (!headers) {
    return null;
  }

  const lines: string[] = [];
  for (const [rawName, rawValue] of Object.entries(headers)) {
    const name = normalizeHeaderName(rawName);
    const value = trimToNonEmptyString(rawValue);
    if (!name || !value) {
      continue;
    }
    lines.push(`${name}: ${value.replace(/[\r\n]+/g, ' ')}`);
  }

  return lines.length > 0 ? `${lines.join('\r\n')}\r\n` : null;
}

export function normalizeMediaInput(input: MediaInput): NormalizedMediaInput {
  if (typeof input === 'string') {
    return { path: input, inputArgs: [], singleResolvedStream: false };
  }

  const inputArgs: string[] = [];
  if (input.inputOptions?.reconnect) {
    inputArgs.push(
      '-reconnect',
      '1',
      '-reconnect_streamed',
      '1',
      '-reconnect_on_network_error',
      '1',
      '-reconnect_on_http_error',
      '403,5xx',
      '-reconnect_delay_max',
      '5',
    );
  }

  const userAgent = trimToNonEmptyString(input.inputOptions?.userAgent);
  if (userAgent) {
    inputArgs.push('-user_agent', userAgent);
  }

  const headers = serializeFfmpegHeaders(input.inputOptions?.headers);
  if (headers) {
    inputArgs.push('-headers', headers);
  }

  if (input.absoluteTimestamps) {
    inputArgs.push('-seek_timestamp', '1');
  }

  return {
    path: input.path,
    inputArgs,
    singleResolvedStream: input.singleResolvedStream === true,
  };
}
