import type {
  FFmpegCapabilities,
  SerializableFFmpegCapabilities,
} from './ffmpeg.types';

function normalizedLines(text: string): string[] {
  return text
    .replaceAll('\r', '')
    .split('\n')
    .map((line) => line.trimEnd());
}

export function parseFormats(text: string): {
  demuxers: Set<string>;
  muxers: Set<string>;
} {
  const demuxers = new Set<string>();
  const muxers = new Set<string>();

  for (const line of normalizedLines(text)) {
    const match = /^ ([D ])([E ])\s+([^\s]+)\s/.exec(line);
    if (!match) continue;
    const names = (match[3] ?? '')
      .split(',')
      .filter((name) => /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(name));
    for (const name of names) {
      if (match[1] === 'D') demuxers.add(name);
      if (match[2] === 'E') muxers.add(name);
    }
  }
  return { demuxers, muxers };
}

export function parseCodecs(text: string): {
  decoders: Set<string>;
  encoders: Set<string>;
} {
  const decoders = new Set<string>();
  const encoders = new Set<string>();
  for (const line of normalizedLines(text)) {
    const match = /^ ([D.])([E.])[VASD.TILX]{4}\s+([^\s]+)\s/.exec(line);
    if (!match) continue;
    const name = match[3];
    if (!name || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/u.test(name)) continue;
    if (match[1] === 'D') decoders.add(name);
    if (match[2] === 'E') encoders.add(name);
  }
  return { decoders, encoders };
}

export function parseNamedComponents(text: string): Set<string> {
  const names = new Set<string>();
  for (const line of normalizedLines(text)) {
    const match = /^ [A-Z.]{1,8}\s+([^\s=]+)\s/.exec(line);
    if (match?.[1]) names.add(match[1]);
  }
  return names;
}

export function parseFilters(text: string): Set<string> {
  const names = new Set<string>();
  for (const line of normalizedLines(text)) {
    const match = /^ [TSC.]{3}\s+([^\s]+)\s+[AVN|]+->[AVN|]+/.exec(line);
    if (match?.[1]) names.add(match[1]);
  }
  return names;
}

export function parseBuildConfiguration(text: string): string[] {
  const line = normalizedLines(text).find((entry) =>
    entry.trimStart().startsWith('configuration:')
  );
  if (!line) return [];
  return line
    .slice(line.indexOf(':') + 1)
    .trim()
    .split(/\s+/)
    .filter(Boolean);
}

export function serializeCapabilities(
  capabilities: FFmpegCapabilities
): SerializableFFmpegCapabilities {
  const sorted = (values: Set<string>) =>
    [...values].sort((left, right) => left.localeCompare(right, 'en'));
  return {
    versionText: capabilities.versionText,
    buildConfiguration: [...capabilities.buildConfiguration],
    demuxers: sorted(capabilities.demuxers),
    muxers: sorted(capabilities.muxers),
    decoders: sorted(capabilities.decoders),
    encoders: sorted(capabilities.encoders),
    filters: sorted(capabilities.filters),
  };
}

export function deserializeCapabilities(
  capabilities: SerializableFFmpegCapabilities
): FFmpegCapabilities {
  return {
    versionText: capabilities.versionText,
    buildConfiguration: [...capabilities.buildConfiguration],
    demuxers: new Set(capabilities.demuxers),
    muxers: new Set(capabilities.muxers),
    decoders: new Set(capabilities.decoders),
    encoders: new Set(capabilities.encoders),
    filters: new Set(capabilities.filters),
  };
}
