import { Button } from 'primereact/button';
import { Dropdown } from 'primereact/dropdown';

function formatDuration(minutes) {
  const value = Number(minutes || 0);
  if (!Number.isFinite(value)) {
    return '-';
  }
  return `${value.toFixed(2)} min`;
}

function formatBytes(bytes) {
  const value = Number(bytes || 0);
  if (!Number.isFinite(value) || value <= 0) {
    return '-';
  }
  const units = ['B', 'KB', 'MB', 'GB', 'TB'];
  let size = value;
  let index = 0;
  while (size >= 1024 && index < units.length - 1) {
    size /= 1024;
    index += 1;
  }
  return `${size.toFixed(2)} ${units[index]}`;
}

function normalizeTrackId(value) {
  const parsed = Number(value);
  if (!Number.isFinite(parsed) || parsed <= 0) {
    return null;
  }
  return Math.trunc(parsed);
}

function normalizeTrackIdList(values) {
  const list = Array.isArray(values) ? values : [];
  const seen = new Set();
  const output = [];
  for (const value of list) {
    const normalized = normalizeTrackId(value);
    if (normalized === null) {
      continue;
    }
    const key = String(normalized);
    if (seen.has(key)) {
      continue;
    }
    seen.add(key);
    output.push(normalized);
  }
  return output;
}

function isBurnedSubtitleTrack(track) {
  const flags = Array.isArray(track?.subtitlePreviewFlags)
    ? track.subtitlePreviewFlags
    : (Array.isArray(track?.flags) ? track.flags : []);
  const hasBurnedFlag = flags.some((flag) => String(flag || '').trim().toLowerCase() === 'burned');
  const summary = `${track?.subtitlePreviewSummary || ''} ${track?.subtitleActionSummary || ''}`;
  return Boolean(
    track?.subtitlePreviewBurnIn
    || track?.burnIn
    || hasBurnedFlag
    || /burned/i.test(summary)
  );
}

function isDuplicateSubtitleTrack(track) {
  return Boolean(track?.duplicate);
}

function normalizeTrackIdSequence(values, options = {}) {
  const list = Array.isArray(values) ? values : [];
  const dedupe = options?.dedupe !== false;
  const seen = new Set();
  const output = [];
  for (const value of list) {
    const normalized = normalizeTrackId(value);
    if (normalized === null) {
      continue;
    }
    const key = String(normalized);
    if (dedupe && seen.has(key)) {
      continue;
    }
    if (dedupe) {
      seen.add(key);
    }
    output.push(normalized);
  }
  return output;
}

function normalizeSubtitleLanguage(value) {
  const raw = String(value || '').trim().toLowerCase();
  if (!raw) {
    return 'und';
  }
  if (raw.length >= 3) {
    return raw.slice(0, 3);
  }
  return raw;
}

function normalizeSubtitleLanguageOrder(rawOrder = []) {
  const list = Array.isArray(rawOrder) ? rawOrder : [];
  const seen = new Set();
  const output = [];
  for (const item of list) {
    const language = normalizeSubtitleLanguage(item);
    if (!language || seen.has(language)) {
      continue;
    }
    seen.add(language);
    output.push(language);
  }
  return output;
}

function normalizeSubtitleVariantSelectionMap(rawSelection) {
  const map = {};
  if (!rawSelection || typeof rawSelection !== 'object') {
    return map;
  }
  for (const [language, value] of Object.entries(rawSelection)) {
    const normalizedLanguage = normalizeSubtitleLanguage(language);
    map[normalizedLanguage] = {
      full: Boolean(value?.full),
      forced: Boolean(value?.forced)
    };
  }
  return map;
}

function normalizeSubtitleConfidenceValue(value) {
  const raw = String(value || '').trim().toLowerCase();
  if (raw === 'high' || raw === 'medium' || raw === 'low') {
    return raw;
  }
  return 'low';
}

function subtitleConfidenceScore(value) {
  const normalized = normalizeSubtitleConfidenceValue(value);
  if (normalized === 'high') {
    return 3;
  }
  if (normalized === 'medium') {
    return 2;
  }
  return 1;
}

function resolveSubtitleTrackVariantFlags(track) {
  const subtitleType = String(track?.subtitleType || '').trim().toLowerCase();
  const isForcedOnly = Boolean(
    track?.isForcedOnly
    ?? track?.subtitlePreviewForcedOnly
    ?? track?.forcedOnly
    ?? subtitleType === 'forced'
  );
  const fullHasForced = !isForcedOnly && Boolean(
    track?.fullHasForced
    ?? track?.subtitleFullHasForced
    ?? track?.hasForcedVariant
  );
  return { isForcedOnly, fullHasForced };
}

function compareForcedSubtitleCandidate(a, b) {
  const defaultDiff = Number(Boolean(b?.defaultFlag)) - Number(Boolean(a?.defaultFlag));
  if (defaultDiff !== 0) {
    return defaultDiff;
  }
  const confidenceDiff = subtitleConfidenceScore(b?.confidence) - subtitleConfidenceScore(a?.confidence);
  if (confidenceDiff !== 0) {
    return confidenceDiff;
  }
  if (a.id !== b.id) {
    return a.id - b.id;
  }
  return a.originalIndex - b.originalIndex;
}

function compareFullSubtitleCandidate(a, b) {
  const defaultDiff = Number(Boolean(b?.defaultFlag)) - Number(Boolean(a?.defaultFlag));
  if (defaultDiff !== 0) {
    return defaultDiff;
  }
  if (a.id !== b.id) {
    return a.id - b.id;
  }
  return a.originalIndex - b.originalIndex;
}

function resolveDeterministicSubtitleCliSelectionForPreview({
  subtitleTracks,
  subtitleVariantSelection,
  subtitleLanguageOrder
}) {
  const tracks = (Array.isArray(subtitleTracks) ? subtitleTracks : [])
    .map((track, index) => {
      const id = normalizeTrackId(track?.id ?? track?.sourceTrackId);
      if (id === null) {
        return null;
      }
      if (isBurnedSubtitleTrack(track) || isDuplicateSubtitleTrack(track)) {
        return null;
      }
      const flags = resolveSubtitleTrackVariantFlags(track);
      return {
        id,
        language: normalizeSubtitleLanguage(track?.language || track?.languageLabel || 'und'),
        isForcedOnly: flags.isForcedOnly,
        fullHasForced: flags.fullHasForced,
        defaultFlag: Boolean(track?.defaultFlag ?? track?.subtitlePreviewDefaultTrack ?? track?.defaultTrack),
        confidence: normalizeSubtitleConfidenceValue(track?.sourceConfidence || track?.confidence),
        selectedForEncode: Boolean(track?.selectedForEncode),
        originalIndex: index
      };
    })
    .filter(Boolean);

  const grouped = new Map();
  for (const track of tracks) {
    if (!grouped.has(track.language)) {
      grouped.set(track.language, []);
    }
    grouped.get(track.language).push(track);
  }

  const normalizedVariantSelection = normalizeSubtitleVariantSelectionMap(subtitleVariantSelection || {});
  const hasExplicitVariantSelection = Object.keys(normalizedVariantSelection).length > 0;
  const normalizedLanguageOrder = normalizeSubtitleLanguageOrder(subtitleLanguageOrder || []);
  const sortedLanguages = Array.from(grouped.entries())
    .map(([language, languageTracks]) => ({
      language,
      minId: languageTracks.reduce((minValue, track) => Math.min(minValue, track.id), Number.MAX_SAFE_INTEGER)
    }))
    .sort((a, b) => a.minId - b.minId || a.language.localeCompare(b.language))
    .map((item) => item.language);

  const order = [];
  const seen = new Set();
  const pushLanguage = (language) => {
    if (!grouped.has(language) || seen.has(language)) {
      return;
    }
    seen.add(language);
    order.push(language);
  };
  normalizedLanguageOrder.forEach(pushLanguage);
  sortedLanguages.forEach(pushLanguage);

  const subtitleTrackIds = [];
  const subtitleForcedTrackIndexes = [];
  for (const language of order) {
    const languageTracks = grouped.get(language) || [];
    const forcedOnly = languageTracks.filter((track) => track.isForcedOnly).sort(compareForcedSubtitleCandidate)[0] || null;
    const fullTracks = languageTracks.filter((track) => !track.isForcedOnly).sort(compareFullSubtitleCandidate);
    const bestFull = fullTracks[0] || null;
    const bestFullHasForced = fullTracks.filter((track) => track.fullHasForced).sort(compareFullSubtitleCandidate)[0] || null;

    const explicit = normalizedVariantSelection[language] || null;
    const fallbackFull = languageTracks.some((track) => track.selectedForEncode && !track.isForcedOnly) || Boolean(bestFull);
    const fallbackForced = languageTracks.some((track) => track.selectedForEncode && track.isForcedOnly) || Boolean(forcedOnly);
    const requestedFull = explicit
      ? Boolean(explicit.full)
      : (hasExplicitVariantSelection ? false : fallbackFull);
    const requestedForced = explicit
      ? Boolean(explicit.forced)
      : (hasExplicitVariantSelection ? false : fallbackForced);
    if (!requestedFull && !requestedForced) {
      continue;
    }

    if (forcedOnly) {
      subtitleTrackIds.push(forcedOnly.id);
      if (requestedFull && bestFull) {
        subtitleTrackIds.push(bestFull.id);
      }
      continue;
    }

    if (bestFullHasForced) {
      if (requestedFull && requestedForced) {
        subtitleTrackIds.push(bestFullHasForced.id);
        subtitleTrackIds.push(bestFullHasForced.id);
        subtitleForcedTrackIndexes.push(subtitleTrackIds.length);
      } else if (requestedForced) {
        subtitleTrackIds.push(bestFullHasForced.id);
        subtitleForcedTrackIndexes.push(subtitleTrackIds.length);
      } else if (requestedFull) {
        subtitleTrackIds.push(bestFullHasForced.id);
      }
      continue;
    }

    if (requestedFull && bestFull) {
      subtitleTrackIds.push(bestFull.id);
    }
  }

  return {
    subtitleTrackIds,
    subtitleForcedTrackIndexes
  };
}

function splitArgs(input) {
  if (!input || typeof input !== 'string') {
    return [];
  }

  const args = [];
  let current = '';
  let quote = null;
  let escaping = false;

  for (const ch of input) {
    if (escaping) {
      current += ch;
      escaping = false;
      continue;
    }

    if (ch === '\\') {
      escaping = true;
      continue;
    }

    if (quote) {
      if (ch === quote) {
        quote = null;
      } else {
        current += ch;
      }
      continue;
    }

    if (ch === '"' || ch === "'") {
      quote = ch;
      continue;
    }

    if (/\s/.test(ch)) {
      if (current.length > 0) {
        args.push(current);
        current = '';
      }
      continue;
    }

    current += ch;
  }

  if (current.length > 0) {
    args.push(current);
  }

  return args;
}

const AUDIO_SELECTION_KEYS_WITH_VALUE = new Set(['-a', '--audio', '--audio-lang-list']);
const AUDIO_SELECTION_KEYS_FLAG_ONLY = new Set(['--all-audio', '--first-audio']);
const SUBTITLE_SELECTION_KEYS_WITH_VALUE = new Set(['-s', '--subtitle', '--subtitle-lang-list']);
const SUBTITLE_SELECTION_KEYS_FLAG_ONLY = new Set(['--all-subtitles', '--first-subtitle']);
const SUBTITLE_FLAG_KEYS_WITH_VALUE = new Set(['--subtitle-burned', '--subtitle-default', '--subtitle-forced']);
const TITLE_SELECTION_KEYS_WITH_VALUE = new Set(['-t', '--title']);

function removeSelectionArgs(extraArgs) {
  const args = Array.isArray(extraArgs) ? extraArgs : [];
  const filtered = [];

  for (let i = 0; i < args.length; i += 1) {
    const token = String(args[i] || '');
    const key = token.includes('=') ? token.slice(0, token.indexOf('=')) : token;

    const isAudioWithValue = AUDIO_SELECTION_KEYS_WITH_VALUE.has(key);
    const isAudioFlagOnly = AUDIO_SELECTION_KEYS_FLAG_ONLY.has(key);
    const isSubtitleWithValue = SUBTITLE_SELECTION_KEYS_WITH_VALUE.has(key)
      || SUBTITLE_FLAG_KEYS_WITH_VALUE.has(key);
    const isSubtitleFlagOnly = SUBTITLE_SELECTION_KEYS_FLAG_ONLY.has(key);
    const isTitleWithValue = TITLE_SELECTION_KEYS_WITH_VALUE.has(key);
    const skip = isAudioWithValue || isAudioFlagOnly || isSubtitleWithValue || isSubtitleFlagOnly || isTitleWithValue;

    if (!skip) {
      filtered.push(token);
      continue;
    }

    if ((isAudioWithValue || isSubtitleWithValue || isTitleWithValue) && !token.includes('=')) {
      const nextToken = String(args[i + 1] || '');
      if (nextToken && !nextToken.startsWith('-')) {
        i += 1;
      }
    }
  }

  return filtered;
}

function shellQuote(value) {
  const raw = String(value ?? '');
  if (raw.length === 0) {
    return "''";
  }
  if (/^[A-Za-z0-9_./:=,+-]+$/.test(raw)) {
    return raw;
  }
  return `'${raw.replace(/'/g, `'"'"'`)}'`;
}

function buildHandBrakeCommandPreview({
  review,
  title,
  selectedAudioTrackIds,
  selectedSubtitleTrackIds,
  selectedSubtitleVariantSelection = {},
  selectedSubtitleLanguageOrder = [],
  commandOutputPath = null,
  presetOverride = null
}) {
  const inputPath = String(title?.filePath || review?.encodeInputPath || '').trim();
  const handBrakeCmd = String(
    review?.selectors?.handbrakeCommand
    || review?.selectors?.handBrakeCommand
    || 'HandBrakeCLI'
  ).trim() || 'HandBrakeCLI';
  const preset = presetOverride !== null
    ? String(presetOverride.handbrakePreset || '').trim()
    : String(review?.selectors?.preset || '').trim();
  const extraArgs = presetOverride !== null
    ? String(presetOverride.extraArgs || '').trim()
    : String(review?.selectors?.extraArgs || '').trim();
  const rawMappedTitleId = Number(review?.handBrakeTitleId);
  const mappedTitleId = Number.isFinite(rawMappedTitleId) && rawMappedTitleId > 0
    ? Math.trunc(rawMappedTitleId)
    : null;

  const resolvedSubtitleSelection = resolveDeterministicSubtitleCliSelectionForPreview({
    subtitleTracks: Array.isArray(title?.subtitleTracks) ? title.subtitleTracks : [],
    subtitleVariantSelection: selectedSubtitleVariantSelection,
    subtitleLanguageOrder: selectedSubtitleLanguageOrder
  });
  const normalizedSelectedVariantSelection = normalizeSubtitleVariantSelectionMap(selectedSubtitleVariantSelection || {});
  const hasExplicitSelectedVariantSelection = Object.keys(normalizedSelectedVariantSelection).length > 0;
  const subtitleTrackIds = resolvedSubtitleSelection.subtitleTrackIds.length > 0
    ? resolvedSubtitleSelection.subtitleTrackIds
    : (hasExplicitSelectedVariantSelection
      ? []
      : normalizeTrackIdSequence(selectedSubtitleTrackIds, { dedupe: false }));
  const selectedSubtitleSet = new Set(normalizeTrackIdList(subtitleTrackIds).map((id) => String(id)));
  const selectedSubtitleTracks = (Array.isArray(title?.subtitleTracks) ? title.subtitleTracks : []).filter((track) => {
    const id = normalizeTrackId(track?.id);
    return id !== null && selectedSubtitleSet.has(String(id));
  });

  const subtitleBurnTrackId = normalizeTrackIdList(
    selectedSubtitleTracks.filter((track) => Boolean(track?.subtitlePreviewBurnIn || track?.burnIn)).map((track) => track?.id)
  )[0] || null;
  const subtitleDefaultTrackId = normalizeTrackIdList(
    selectedSubtitleTracks.filter((track) => Boolean(track?.subtitlePreviewDefaultTrack || track?.defaultTrack)).map((track) => track?.id)
  )[0] || null;

  const baseArgs = [
    '-i',
    inputPath || '<encode-input>',
    '-o',
    String(commandOutputPath || '').trim() || '<encode-output>'
  ];

  if (mappedTitleId !== null) {
    baseArgs.push('-t', String(mappedTitleId));
  }

  if (preset) {
    baseArgs.push('-Z', preset);
  }

  const filteredExtra = removeSelectionArgs(splitArgs(extraArgs));
  const overrideArgs = [
    '-a',
    normalizeTrackIdList(selectedAudioTrackIds).join(',') || 'none',
    '-s',
    subtitleTrackIds.length > 0 ? subtitleTrackIds.join(',') : 'none'
  ];

  if (subtitleBurnTrackId !== null) {
    overrideArgs.push(`--subtitle-burned=${subtitleBurnTrackId}`);
  }
  if (subtitleDefaultTrackId !== null) {
    overrideArgs.push(`--subtitle-default=${subtitleDefaultTrackId}`);
  }
  if (resolvedSubtitleSelection.subtitleForcedTrackIndexes.length > 0) {
    overrideArgs.push(`--subtitle-forced=${resolvedSubtitleSelection.subtitleForcedTrackIndexes.join(',')}`);
  }

  const finalArgs = [...baseArgs, ...filteredExtra, ...overrideArgs];
  return `${handBrakeCmd} ${finalArgs.map((arg) => shellQuote(arg)).join(' ')}`;
}

function toLang2(value) {
  const raw = String(value || '').trim().toLowerCase();
  if (!raw) {
    return 'und';
  }
  const map = {
    en: 'en',
    eng: 'en',
    de: 'de',
    deu: 'de',
    ger: 'de',
    tr: 'tr',
    tur: 'tr',
    fr: 'fr',
    fra: 'fr',
    fre: 'fr',
    es: 'es',
    spa: 'es',
    it: 'it',
    ita: 'it'
  };
  if (map[raw]) {
    return map[raw];
  }
  if (raw.length === 2) {
    return raw;
  }
  if (raw.length >= 3) {
    return raw.slice(0, 2);
  }
  return raw;
}

function simplifyCodec(type, value, hint = null) {
  const raw = String(value || '').trim();
  const hintRaw = String(hint || '').trim();
  const lower = raw.toLowerCase();
  const merged = `${raw} ${hintRaw}`.toLowerCase();
  if (!raw) {
    return '-';
  }

  if (type === 'subtitle') {
    if (merged.includes('pgs')) {
      return 'PGS';
    }
    return raw.toUpperCase();
  }

  if (merged.includes('dts-hd ma') || merged.includes('dts hd ma')) {
    return 'DTS-HD MA';
  }
  if (merged.includes('dts-hd hra') || merged.includes('dts hd hra')) {
    return 'DTS-HD HRA';
  }
  if (merged.includes('dts-hd') || merged.includes('dts hd')) {
    return 'DTS-HD';
  }
  if (merged.includes('dts') || merged.includes('dca')) {
    return 'DTS';
  }
  if (merged.includes('truehd')) {
    return 'TRUEHD';
  }
  if (merged.includes('e-ac-3') || merged.includes('eac3') || merged.includes('dd+')) {
    return 'E-AC-3';
  }
  if (merged.includes('ac-3') || merged.includes('ac3') || merged.includes('dolby digital')) {
    return 'AC-3';
  }

  const numeric = Number(raw);
  if (Number.isFinite(numeric)) {
    if (numeric === 262144) {
      return 'DTS-HD';
    }
    if (numeric === 131072) {
      return 'DTS';
    }
  }

  return raw.toUpperCase();
}

function extractAudioVariant(hint) {
  const raw = String(hint || '').trim();
  if (!raw) {
    return '';
  }

  const paren = raw.match(/\(([^)]+)\)/);
  if (!paren) {
    return '';
  }

  const parts = paren[1]
    .split(',')
    .map((item) => item.trim())
    .filter(Boolean);

  const extras = parts.filter((item) => {
    const lower = item.toLowerCase();
    if (lower.includes('dts') || lower.includes('ac3') || lower.includes('e-ac3') || lower.includes('eac3')) {
      return false;
    }
    if (/\d+(?:\.\d+)?\s*ch/i.test(item)) {
      return false;
    }
    if (/\d+\s*kbps/i.test(lower)) {
      return false;
    }
    return true;
  });

  return extras.join(', ');
}

function channelCount(rawValue) {
  const raw = String(rawValue || '').trim().toLowerCase();
  if (!raw) {
    return null;
  }

  if (raw.includes('7.1')) {
    return 8;
  }
  if (raw.includes('5.1')) {
    return 6;
  }
  if (raw.includes('stereo') || raw.includes('2.0') || raw.includes('downmix')) {
    return 2;
  }
  if (raw.includes('mono') || raw.includes('1.0')) {
    return 1;
  }

  const numeric = Number(raw);
  if (Number.isFinite(numeric) && numeric > 0) {
    if (Math.abs(numeric - 7.1) < 0.2) {
      return 8;
    }
    if (Math.abs(numeric - 5.1) < 0.2) {
      return 6;
    }
    return Math.trunc(numeric);
  }

  const match = raw.match(/(\d+)\s*ch/);
  if (match) {
    const value = Number(match[1]);
    return Number.isFinite(value) && value > 0 ? Math.trunc(value) : null;
  }

  return null;
}

function audioChannelLabel(rawValue) {
  const raw = String(rawValue || '').trim().toLowerCase();
  const count = channelCount(rawValue);

  if (raw.includes('7.1') || count === 8) {
    return 'Surround 7.1';
  }
  if (raw.includes('5.1') || count === 6) {
    return 'Surround 5.1';
  }
  if (raw.includes('stereo') || raw.includes('2.0') || raw.includes('downmix') || count === 2) {
    return 'Stereo';
  }
  if (count === 1) {
    return 'Mono';
  }
  return '';
}

const DEFAULT_AUDIO_FALLBACK_PREVIEW = 'av_aac';

function mapTrackToCopyCodec(track) {
  const raw = [
    track?.codecToken,
    track?.format,
    track?.codecName,
    track?.description,
    track?.title
  ]
    .map((value) => String(value || '').trim().toLowerCase())
    .filter(Boolean)
    .join(' ');

  if (!raw) {
    return null;
  }
  if (raw.includes('e-ac-3') || raw.includes('eac3') || raw.includes('dd+')) {
    return 'eac3';
  }
  if (raw.includes('ac-3') || raw.includes('ac3') || raw.includes('dolby digital')) {
    return 'ac3';
  }
  if (raw.includes('truehd')) {
    return 'truehd';
  }
  if (raw.includes('dts-hd') || raw.includes('dtshd')) {
    return 'dtshd';
  }
  if (raw.includes('dca') || raw.includes('dts')) {
    return 'dts';
  }
  if (raw.includes('aac')) {
    return 'aac';
  }
  if (raw.includes('flac')) {
    return 'flac';
  }
  if (raw.includes('mp3') || raw.includes('mpeg audio')) {
    return 'mp3';
  }
  if (raw.includes('opus')) {
    return 'opus';
  }
  if (raw.includes('pcm') || raw.includes('lpcm')) {
    return 'lpcm';
  }
  return null;
}

function resolveAudioEncoderPreviewLabel(track, encoderToken, copyMask, fallbackEncoder) {
  const normalizedToken = String(encoderToken || '').trim().toLowerCase();
  if (!normalizedToken || normalizedToken === 'preset-default') {
    return 'Preset-Default (HandBrake)';
  }

  if (normalizedToken.startsWith('copy')) {
    const sourceCodec = mapTrackToCopyCodec(track);
    const explicitCopyCodec = normalizedToken.includes(':')
      ? normalizedToken.split(':').slice(1).join(':').trim().toLowerCase()
      : null;
    const normalizedCopyMask = Array.isArray(copyMask)
      ? copyMask.map((item) => String(item || '').trim().toLowerCase()).filter(Boolean)
      : [];

    let canCopy = false;
    let effectiveCodec = sourceCodec;
    if (explicitCopyCodec) {
      canCopy = Boolean(sourceCodec && sourceCodec === explicitCopyCodec);
    } else if (sourceCodec && normalizedCopyMask.length > 0) {
      canCopy = normalizedCopyMask.includes(sourceCodec);
      // DTS-HD MA contains an embedded DTS core. When dtshd is not in the copy
      // mask but dts is, HandBrake will extract and copy the DTS core layer.
      if (!canCopy && sourceCodec === 'dtshd' && normalizedCopyMask.includes('dts')) {
        canCopy = true;
        effectiveCodec = 'dts';
      }
    }

    if (canCopy) {
      return `Copy (${effectiveCodec || track?.format || 'Quelle'})`;
    }

    const fallback = String(fallbackEncoder || DEFAULT_AUDIO_FALLBACK_PREVIEW).trim().toLowerCase() || DEFAULT_AUDIO_FALLBACK_PREVIEW;
    return `Fallback Transcode (${fallback})`;
  }

  return `Transcode (${normalizedToken})`;
}

function parseAudioSelectorFromArgs(extraArgsString, baseSelector) {
  const base = baseSelector && typeof baseSelector === 'object' ? baseSelector : {};
  const args = String(extraArgsString || '').trim();
  if (!args) {
    return base;
  }

  // Tokenize: split on whitespace but respect quoted strings
  const tokens = [];
  let current = '';
  let inQuote = null;
  for (let i = 0; i < args.length; i++) {
    const ch = args[i];
    if (inQuote) {
      if (ch === inQuote) {
        inQuote = null;
      } else {
        current += ch;
      }
    } else if (ch === '"' || ch === "'") {
      inQuote = ch;
    } else if (ch === ' ' || ch === '\t') {
      if (current) {
        tokens.push(current);
        current = '';
      }
    } else {
      current += ch;
    }
  }
  if (current) {
    tokens.push(current);
  }

  const result = { ...base };

  const getNext = (i) => (i + 1 < tokens.length ? tokens[i + 1] : null);

  for (let i = 0; i < tokens.length; i++) {
    const token = tokens[i];

    // Support both --flag=value and --flag value
    const eqIdx = token.indexOf('=');
    const flag = eqIdx !== -1 ? token.slice(0, eqIdx) : token;
    const inlineVal = eqIdx !== -1 ? token.slice(eqIdx + 1) : null;

    const getValue = () => {
      if (inlineVal !== null) return inlineVal;
      const next = getNext(i);
      if (next && !next.startsWith('-')) {
        i++;
        return next;
      }
      return null;
    };

    if (flag === '--aencoder') {
      const val = getValue();
      if (val) {
        result.encoders = val.split(',').map((s) => s.trim()).filter(Boolean);
        result.encoderSource = 'args';
      }
    } else if (flag === '--audio-copy-mask') {
      const val = getValue();
      if (val) {
        result.copyMask = val.split(',').map((s) => s.trim()).filter(Boolean);
      }
    } else if (flag === '--audio-fallback') {
      const val = getValue();
      if (val) {
        result.fallbackEncoder = val.trim();
      }
    }
  }

  return result;
}

function buildAudioActionPreviewSummary(track, selectedIndex, audioSelector) {
  const selector = audioSelector && typeof audioSelector === 'object' ? audioSelector : {};
  const availableEncoders = Array.isArray(selector.encoders) ? selector.encoders : [];
  let encoderPlan = [];

  if (selector.encoderSource === 'args' && availableEncoders.length > 0) {
    const safeIndex = Number.isFinite(selectedIndex) && selectedIndex >= 0 ? selectedIndex : 0;
    encoderPlan = [availableEncoders[Math.min(safeIndex, availableEncoders.length - 1)]];
  } else if (availableEncoders.length > 0) {
    encoderPlan = [...availableEncoders];
  } else {
    encoderPlan = ['preset-default'];
  }

  const labels = encoderPlan
    .map((token) => resolveAudioEncoderPreviewLabel(track, token, selector.copyMask, selector.fallbackEncoder))
    .filter(Boolean);

  return labels.join(' + ') || 'Übernehmen';
}

function TrackList({
  title,
  tracks,
  type = 'generic',
  allowSelection = false,
  selectedTrackIds = [],
  selectedSubtitleVariantSelection = {},
  selectedSubtitleLanguageOrder = [],
  onToggleTrack = null,
  onToggleSubtitleVariant = null,
  audioSelector = null
}) {
  const orderedTracks = (Array.isArray(tracks) ? [...tracks] : [])
    .sort((a, b) => {
      const aId = normalizeTrackId(a?.id) ?? Number.MAX_SAFE_INTEGER;
      const bId = normalizeTrackId(b?.id) ?? Number.MAX_SAFE_INTEGER;
      if (aId !== bId) {
        return aId - bId;
      }
      return String(a?.language || '').localeCompare(String(b?.language || ''));
    });

  if (type === 'subtitle') {
    const selectedVariants = normalizeSubtitleVariantSelectionMap(selectedSubtitleVariantSelection || {});
    const sortedSubtitleTracks = orderedTracks
      .filter((track) => !isDuplicateSubtitleTrack(track));

    const renderVariantRow = ({
      key,
      checked,
      disabled,
      onChange,
      text,
      confidence = null,
      indent = false
    }) => {
      const confidenceColor = confidence === 'high'
        ? '#2e7d32'
        : confidence === 'medium'
          ? '#f57c00'
          : '#c62828';

      return (
        <div key={key} className="media-track-item" style={indent ? { paddingLeft: '1.4rem' } : null}>
          <label className="readonly-check-row">
            <input
              type="checkbox"
              checked={checked}
              onChange={onChange}
              readOnly={disabled}
              disabled={disabled}
            />
            <span>
              {text}
              {confidence ? <span style={{ color: confidenceColor, marginLeft: '0.35rem' }}>●</span> : null}
            </span>
          </label>
        </div>
      );
    };
    const resolveSubtitleActionInfo = (track, selected) => {
      const base = String(track?.subtitlePreviewSummary || track?.subtitleActionSummary || '').trim();
      if (allowSelection) {
        return selected ? 'Übernehmen' : 'Nicht übernommen';
      }
      return base || (selected ? 'Übernehmen' : 'Nicht übernommen');
    };

    return (
      <div>
        <h4>{title}</h4>
        {!tracks || tracks.length === 0 ? (
          <p>Keine Einträge.</p>
        ) : (
          <div className="media-track-list">
            {sortedSubtitleTracks.map((track) => {
              const burned = isBurnedSubtitleTrack(track);
              const language = normalizeSubtitleLanguage(track?.language || track?.languageLabel || 'und');
              const displayLanguage = toLang2(track?.language || track?.languageLabel || 'und');
              const displayCodec = simplifyCodec('subtitle', track?.format, track?.description || track?.title);
              const flags = resolveSubtitleTrackVariantFlags(track);
              const confidenceRaw = String(track?.sourceConfidence || '').trim().toLowerCase();
              const confidence = confidenceRaw === 'high' || confidenceRaw === 'medium' || confidenceRaw === 'low'
                ? confidenceRaw
                : 'low';
              const languageSelection = selectedVariants[language] || { full: false, forced: false };

              if (flags.isForcedOnly) {
                const checked = allowSelection ? Boolean(languageSelection.forced) : Boolean(track?.selectedForEncode);
                const disabled = !allowSelection || burned;
                const actionInfo = resolveSubtitleActionInfo(track, checked);
                return (
                  <div key={`${title}-${track.id}-forced-only-group`} className="media-track-item">
                    {renderVariantRow({
                      key: `${title}-${track.id}-forced-only`,
                      checked,
                      disabled,
                      onChange: (event) => {
                        if (disabled || typeof onToggleSubtitleVariant !== 'function') {
                          return;
                        }
                        onToggleSubtitleVariant(language, 'forced', event.target.checked);
                      },
                      text: `#${track.id} | ${displayLanguage} | Automatische Übersetzungen (${displayCodec})`,
                      confidence
                    })}
                    <small className="track-action-note">Untertitel: {actionInfo}</small>
                  </div>
                );
              }

              if (flags.fullHasForced) {
                const checkedFull = allowSelection ? Boolean(languageSelection.full) : Boolean(track?.selectedForEncode);
                const checkedForced = allowSelection ? Boolean(languageSelection.forced) : false;
                const disabled = !allowSelection || burned;
                const actionInfo = resolveSubtitleActionInfo(track, checkedFull || checkedForced);
                return (
                  <div key={`${title}-${track.id}-combo`} className="media-track-item">
                    <div className="media-track-item">
                      <small className="track-action-note">{`#${track.id} | ${displayLanguage}`}</small>
                    </div>
                    {renderVariantRow({
                      key: `${title}-${track.id}-combo-full`,
                      checked: checkedFull,
                      disabled,
                      onChange: (event) => {
                        if (disabled || typeof onToggleSubtitleVariant !== 'function') {
                          return;
                        }
                        onToggleSubtitleVariant(language, 'full', event.target.checked);
                      },
                      text: `Komplette Untertitel (${displayCodec})`,
                      indent: true
                    })}
                    {renderVariantRow({
                      key: `${title}-${track.id}-combo-forced`,
                      checked: checkedForced,
                      disabled,
                      onChange: (event) => {
                        if (disabled || typeof onToggleSubtitleVariant !== 'function') {
                          return;
                        }
                        onToggleSubtitleVariant(language, 'forced', event.target.checked);
                      },
                      text: `Automatische Übersetzungen (${displayCodec})`,
                      confidence,
                      indent: true
                    })}
                    <small className="track-action-note">Untertitel: {actionInfo}</small>
                  </div>
                );
              }

              const checked = allowSelection ? Boolean(languageSelection.full) : Boolean(track?.selectedForEncode);
              const disabled = !allowSelection || burned;
              const actionInfo = resolveSubtitleActionInfo(track, checked);
              return (
                <div key={`${title}-${track.id}-full-group`} className="media-track-item">
                  {renderVariantRow({
                    key: `${title}-${track.id}-full`,
                    checked,
                    disabled,
                    onChange: (event) => {
                      if (disabled || typeof onToggleSubtitleVariant !== 'function') {
                        return;
                      }
                      onToggleSubtitleVariant(language, 'full', event.target.checked);
                    },
                    text: `#${track.id} | ${displayLanguage} | Komplette Untertitel (${displayCodec})`
                  })}
                  <small className="track-action-note">Untertitel: {actionInfo}</small>
                </div>
              );
            })}
          </div>
        )}
        <div className="subtitle-footnote-list">
          <small className="track-action-note">
            Automatische Übersetzungen werden nur eingeblendet, wenn im Film eine andere Sprache gesprochen wird. Komplette Untertitel zeigen alle Dialoge an. Blu-ray Untertitel liegen meist als PGS (Bildformat) vor.
          </small>
          <small className="track-action-note">
            Confidence-Index (nur für Automatische Übersetzungen): <span style={{ color: '#2e7d32' }}>●</span> high = explizites Track-Signal erkannt, <span style={{ color: '#f57c00' }}>●</span> medium = "forced" im Titel erkannt, <span style={{ color: '#c62828' }}>●</span> low = heuristische Einschätzung.
          </small>
        </div>
      </div>

    );
  }

  const selectedIds = normalizeTrackIdList(selectedTrackIds);
  const checkedTrackOrder = orderedTracks
    .map((track) => normalizeTrackId(track?.id))
    .filter((trackId, index) => {
      if (trackId === null) {
        return false;
      }
      if (allowSelection) {
        return selectedIds.includes(trackId);
      }
      const track = orderedTracks[index];
      return Boolean(track?.selectedForEncode);
    });

  return (
    <div>
      <h4>{title}</h4>
      {!tracks || tracks.length === 0 ? (
        <p>Keine Einträge.</p>
      ) : (
        <div className="media-track-list">
          {orderedTracks.map((track) => {
            const trackId = normalizeTrackId(track.id);
            const checked = allowSelection
              ? (trackId !== null && selectedIds.includes(trackId))
              : Boolean(track.selectedForEncode);
            const selectedIndex = trackId !== null
              ? checkedTrackOrder.indexOf(trackId)
              : -1;
            const actionInfo = type === 'audio'
              ? (checked
                ? (() => {
                  // When encoder comes from user preset extraArgs, always recompute
                  // (server-stored summary reflects a different/default preset)
                  if (audioSelector?.encoderSource === 'args') {
                    return buildAudioActionPreviewSummary(track, selectedIndex, audioSelector);
                  }
                  const base = String(track.encodePreviewSummary || track.encodeActionSummary || '').trim();
                  const staleUnselectedSummary = /^nicht übernommen$/i.test(base);
                  if (staleUnselectedSummary) {
                    return buildAudioActionPreviewSummary(track, selectedIndex, audioSelector);
                  }
                  return base || buildAudioActionPreviewSummary(track, selectedIndex, audioSelector);
                })()
                : 'Nicht übernommen')
              : null;
            const displayLanguage = toLang2(track.language || track.languageLabel || 'und');
            const displayHint = track.description || track.title;
            const displayCodec = simplifyCodec(type, track.format, displayHint);
            const displayChannelCount = channelCount(track.channels);
            const displayAudioTitle = audioChannelLabel(track.channels);
            const audioVariant = type === 'audio' ? extractAudioVariant(displayHint) : '';
            const disabled = !allowSelection;

            let displayText = `#${track.id} | ${displayLanguage} | ${displayCodec}`;
            if (type === 'audio') {
              if (displayChannelCount !== null) {
                displayText += ` | ${displayChannelCount}ch`;
              }
              if (displayAudioTitle) {
                displayText += ` | ${displayAudioTitle}`;
              }
              if (audioVariant) {
                displayText += ` | ${audioVariant}`;
              }
            }
            return (
              <div key={`${title}-${track.id}`} className="media-track-item">
                <label className="readonly-check-row">
                  <input
                    type="checkbox"
                    checked={checked}
                    onChange={(event) => {
                      if (disabled || typeof onToggleTrack !== 'function' || trackId === null) {
                        return;
                      }
                      onToggleTrack(trackId, event.target.checked);
                    }}
                    readOnly={disabled}
                    disabled={disabled}
                  />
                  <span>
                    {displayText}
                  </span>
                </label>
                {actionInfo ? <small className="track-action-note">Encode: {actionInfo}</small> : null}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

function normalizeTitleId(value) {
  const parsed = Number(value);
  if (!Number.isFinite(parsed) || parsed <= 0) {
    return null;
  }
  return Math.trunc(parsed);
}

function normalizeScriptId(value) {
  const parsed = Number(value);
  if (!Number.isFinite(parsed) || parsed <= 0) {
    return null;
  }
  return Math.trunc(parsed);
}

function normalizeChainId(value) {
  const parsed = Number(value);
  if (!Number.isFinite(parsed) || parsed <= 0) {
    return null;
  }
  return Math.trunc(parsed);
}

function normalizeScriptIdList(values) {
  const list = Array.isArray(values) ? values : [];
  const seen = new Set();
  const output = [];
  for (const value of list) {
    const normalized = normalizeScriptId(value);
    if (normalized === null) {
      continue;
    }
    const key = String(normalized);
    if (seen.has(key)) {
      continue;
    }
    seen.add(key);
    output.push(normalized);
  }
  return output;
}

export default function MediaInfoReviewPanel({
  review,
  presetDisplayValue = '',
  commandOutputPath = null,
  selectedEncodeTitleId = null,
  allowTitleSelection = false,
  onSelectEncodeTitle = null,
  allowTrackSelection = false,
  trackSelectionByTitle = {},
  onTrackSelectionChange = null,
  onSubtitleVariantSelectionChange = null,
  availableScripts = [],
  availableChains = [],
  preEncodeItems = [],
  postEncodeItems = [],
  allowEncodeItemSelection = false,
  onAddPreEncodeItem = null,
  onChangePreEncodeItem = null,
  onRemovePreEncodeItem = null,
  onReorderPreEncodeItem = null,
  onAddPostEncodeItem = null,
  onChangePostEncodeItem = null,
  onRemovePostEncodeItem = null,
  onReorderPostEncodeItem = null,
  userPresets = [],
  selectedUserPresetId = null,
  onUserPresetChange = null,
  selectedHandBrakePreset = ''
}) {
  if (!review) {
    return <p>Keine Mediainfo-Daten vorhanden.</p>;
  }

  const titles = review.titles || [];
  const currentSelectedId = normalizeTitleId(selectedEncodeTitleId) || normalizeTitleId(review.encodeInputTitleId);
  const encodeInputTitle = titles.find((item) => item.id === currentSelectedId) || null;
  const processedFiles = Number(review.processedFiles || titles.length || 0);
  const totalFiles = Number(review.totalFiles || titles.length || 0);
  const playlistRecommendation = review.playlistRecommendation || null;
  const rawPreset = String(review.selectors?.preset || '').trim();
  const presetLabel = String(presetDisplayValue || rawPreset).trim() || '(kein Preset)';

  // User preset resolution
  const normalizedUserPresets = Array.isArray(userPresets) ? userPresets : [];
  const selectedUserPreset = selectedUserPresetId
    ? normalizedUserPresets.find((p) => Number(p.id) === Number(selectedUserPresetId)) || null
    : null;
  const hasUserPresets = normalizedUserPresets.length > 0;
  const allowUserPresetSelection = hasUserPresets && typeof onUserPresetChange === 'function' && allowEncodeItemSelection;
  const isUserPresetActive = Boolean(selectedUserPreset);
  const trimmedHandBrakePreset = String(selectedHandBrakePreset || '').trim();
  const isHandBrakePresetActive = !isUserPresetActive && Boolean(trimmedHandBrakePreset);
  const effectivePresetOverride = isUserPresetActive
    ? { handbrakePreset: selectedUserPreset.handbrakePreset || '', extraArgs: selectedUserPreset.extraArgs || '' }
    : (isHandBrakePresetActive ? { handbrakePreset: trimmedHandBrakePreset, extraArgs: '' } : null);
  const effectiveAudioSelector = effectivePresetOverride?.extraArgs
    ? parseAudioSelectorFromArgs(effectivePresetOverride.extraArgs, review?.selectors?.audio)
    : (review?.selectors?.audio || null);
  const presetSourceLabel = isUserPresetActive
    ? `User-Preset: ${selectedUserPreset?.name || 'Preset'}`
    : (isHandBrakePresetActive ? 'HandBrake-Preset' : '');

  const scriptCatalog = (Array.isArray(availableScripts) ? availableScripts : [])
    .map((item) => ({
      id: normalizeScriptId(item?.id),
      name: String(item?.name || '').trim()
    }))
    .filter((item) => item.id !== null && item.name.length > 0);
  const scriptById = new Map(scriptCatalog.map((item) => [item.id, item]));
  const chainCatalog = (Array.isArray(availableChains) ? availableChains : [])
    .map((item) => ({ id: normalizeChainId(item?.id), name: String(item?.name || '').trim() }))
    .filter((item) => item.id !== null && item.name.length > 0);
  const chainById = new Map(chainCatalog.map((item) => [item.id, item]));

  const makeHandleDrop = (items, onReorder) => (event, targetIndex) => {
    if (!allowEncodeItemSelection || typeof onReorder !== 'function' || items.length < 2) {
      return;
    }
    event.preventDefault();
    const fromIndex = Number(event.dataTransfer?.getData('text/plain'));
    if (!Number.isInteger(fromIndex)) {
      return;
    }
    onReorder(fromIndex, targetIndex);
  };

  const handlePreDrop = makeHandleDrop(preEncodeItems, onReorderPreEncodeItem);
  const handlePostDrop = makeHandleDrop(postEncodeItems, onReorderPostEncodeItem);

  return (
    <div className="media-review-wrap">
      {allowUserPresetSelection && (
        <div className="user-preset-selector" style={{ marginBottom: '0.75rem', padding: '0.75rem', border: '1px solid var(--surface-border, #e0e0e0)', borderRadius: '6px', background: 'var(--surface-ground, #f8f8f8)' }}>
          <label style={{ display: 'block', marginBottom: '0.4rem', fontWeight: 600 }}>
            Encode-Preset auswählen
          </label>
          <Dropdown
            value={selectedUserPresetId ? Number(selectedUserPresetId) : null}
            options={[
              { label: '(Einstellungen-Fallback)', value: null },
              ...normalizedUserPresets.map((p) => ({
                label: `${p.name}${p.mediaType !== 'all' ? ` [${p.mediaType === 'bluray' ? 'Blu-ray' : p.mediaType === 'dvd' ? 'DVD' : 'Sonstiges'}]` : ''}`,
                value: Number(p.id)
              }))
            ]}
            onChange={(e) => onUserPresetChange(e.value)}
            placeholder="(Einstellungen-Fallback)"
            style={{ width: '100%' }}
          />
          {selectedUserPreset && (
            <div style={{ marginTop: '0.4rem', fontSize: '0.8rem', opacity: 0.8 }}>
              {selectedUserPreset.handbrakePreset
                ? <span><strong>-Z</strong> {selectedUserPreset.handbrakePreset}</span>
                : <span style={{ opacity: 0.6 }}>(kein Preset-Name)</span>}
              {selectedUserPreset.extraArgs && (
                <span style={{ marginLeft: '1rem' }}><strong>Args:</strong> {selectedUserPreset.extraArgs}</span>
              )}
              {selectedUserPreset.description && (
                <span style={{ marginLeft: '1rem', opacity: 0.7 }}>{selectedUserPreset.description}</span>
              )}
            </div>
          )}
        </div>
      )}
      <div className="media-review-meta">
        <div>
          <strong>Preset:</strong>{' '}
          {effectivePresetOverride
            ? (effectivePresetOverride.handbrakePreset || '(kein Preset)')
            : presetLabel}
          {effectivePresetOverride && presetSourceLabel && (
            <span style={{ marginLeft: '0.5rem', fontSize: '0.75rem', opacity: 0.7 }}>
              ({presetSourceLabel})
            </span>
          )}
        </div>
        <div>
          <strong>Extra Args:</strong>{' '}
          {effectivePresetOverride
            ? (effectivePresetOverride.extraArgs || '(keine)')
            : (review.selectors?.extraArgs || '(keine)')}
          {effectivePresetOverride && isUserPresetActive && !selectedUserPreset?.extraArgs && (
            <span style={{ marginLeft: '0.5rem', fontSize: '0.75rem', opacity: 0.7 }}>(aus User-Preset)</span>
          )}
        </div>
        <div><strong>Preset-Profil:</strong> {effectivePresetOverride ? (isUserPresetActive ? 'user-preset' : 'handbrake-preset') : (review.selectors?.presetProfileSource || '-')}</div>
        <div><strong>MIN_LENGTH_MINUTES:</strong> {review.minLengthMinutes}</div>
        <div><strong>Encode Input:</strong> {encodeInputTitle?.fileName || '-'}</div>
        <div><strong>Audio Auswahl:</strong> {effectiveAudioSelector?.mode || '-'}</div>
        <div><strong>Audio Encoder:</strong> {(effectiveAudioSelector?.encoders || []).join(', ') || 'Preset-Default'}</div>
        <div><strong>Audio Copy-Mask:</strong> {(effectiveAudioSelector?.copyMask || []).join(', ') || '-'}</div>
        <div><strong>Audio Fallback:</strong> {effectiveAudioSelector?.fallbackEncoder || '-'}</div>
        <div><strong>Subtitle Auswahl:</strong> {review.selectors?.subtitle?.mode || '-'}</div>
        <div><strong>Subtitle Flags:</strong> {review.selectors?.subtitle?.burnBehavior === 'first' ? 'burned(first)' : '-'}</div>
      </div>

      {review.partial ? (
        <small>Zwischenstand: {processedFiles}/{totalFiles} Datei(en) analysiert.</small>
      ) : null}

      {playlistRecommendation ? (
        <div className="playlist-recommendation-box">
          <small>
            <strong>Empfehlung:</strong> {playlistRecommendation.playlistFile || '-'}
            {playlistRecommendation.reviewTitleId ? ` (Titel #${playlistRecommendation.reviewTitleId})` : ''}
          </small>
          {playlistRecommendation.reason ? <small>{playlistRecommendation.reason}</small> : null}
        </div>
      ) : null}

      {Array.isArray(review.notes) && review.notes.length > 0 ? (
        <div className="media-review-notes">
          {review.notes.map((note, idx) => (
            <small key={`${idx}-${note}`}>{note}</small>
          ))}
        </div>
      ) : null}

      {/* Pre-Encode Items (scripts + chains unified) */}
      {(allowEncodeItemSelection || preEncodeItems.length > 0) ? (
        <div className="post-script-box">
          <h4>Pre-Encode Ausführungen (optional)</h4>
          {scriptCatalog.length === 0 && chainCatalog.length === 0 ? (
            <small>Keine Skripte oder Ketten konfiguriert. In den Settings anlegen.</small>
          ) : null}
          {preEncodeItems.length === 0 ? (
            <small>Keine Pre-Encode Ausführungen ausgewählt.</small>
          ) : null}
          {preEncodeItems.map((item, rowIndex) => {
            const isScript = item.type === 'script';
            const canDrag = allowEncodeItemSelection && preEncodeItems.length > 1;
            const scriptObj = isScript ? (scriptById.get(normalizeScriptId(item.id)) || null) : null;
            const chainObj = !isScript ? (chainById.get(Number(item.id)) || null) : null;
            const name = isScript
              ? (scriptObj?.name || `Skript #${item.id}`)
              : (chainObj?.name || `Kette #${item.id}`);
            const usedScriptIds = new Set(
              preEncodeItems
                .filter((it, i) => it.type === 'script' && i !== rowIndex)
                .map((it) => normalizeScriptId(it.id))
                .filter((id) => id !== null)
                .map((id) => String(id))
            );
            const usedChainIds = new Set(
              preEncodeItems
                .filter((it, i) => it.type === 'chain' && i !== rowIndex)
                .map((it) => normalizeChainId(it.id))
                .filter((id) => id !== null)
                .map((id) => String(id))
            );
            const scriptOptions = scriptCatalog.map((s) => ({
              label: s.name,
              value: s.id,
              disabled: usedScriptIds.has(String(s.id))
            }));
            const chainOptions = chainCatalog.map((c) => ({
              label: c.name,
              value: c.id,
              disabled: usedChainIds.has(String(c.id))
            }));
            return (
              <div
                key={`pre-item-${rowIndex}-${item.type}-${item.id}`}
                className={`post-script-row${allowEncodeItemSelection ? ' editable' : ''}`}
                onDragOver={(event) => {
                  if (!canDrag) return;
                  event.preventDefault();
                  if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';
                }}
                onDrop={(event) => handlePreDrop(event, rowIndex)}
              >
                {allowEncodeItemSelection ? (
                  <>
                    <span
                      className={`post-script-drag-handle pi pi-bars${canDrag ? '' : ' disabled'}`}
                      title={canDrag ? 'Ziehen zum Umordnen' : 'Mindestens zwei Einträge zum Umordnen'}
                      draggable={canDrag}
                      onDragStart={(event) => {
                        if (!canDrag) return;
                        event.dataTransfer.effectAllowed = 'move';
                        event.dataTransfer.setData('text/plain', String(rowIndex));
                      }}
                    />
                    <i className={`post-script-type-icon pi ${isScript ? 'pi-code' : 'pi-link'}`} title={isScript ? 'Skript' : 'Kette'} />
                    {isScript ? (
                      <Dropdown
                        value={normalizeScriptId(item.id)}
                        options={scriptOptions}
                        optionLabel="label"
                        optionValue="value"
                        optionDisabled="disabled"
                        onChange={(event) => onChangePreEncodeItem?.(rowIndex, 'script', event.value)}
                        className="full-width"
                      />
                    ) : (
                      <Dropdown
                        value={normalizeChainId(item.id)}
                        options={chainOptions}
                        optionLabel="label"
                        optionValue="value"
                        optionDisabled="disabled"
                        onChange={(event) => onChangePreEncodeItem?.(rowIndex, 'chain', event.value)}
                        className="full-width"
                      />
                    )}
                    <Button icon="pi pi-times" severity="danger" outlined onClick={() => onRemovePreEncodeItem?.(rowIndex)} />
                  </>
                ) : (
                  <small><i className={`pi ${isScript ? 'pi-code' : 'pi-link'}`} /> {`${rowIndex + 1}. ${name}`}</small>
                )}
              </div>
            );
          })}
          {allowEncodeItemSelection ? (
            <div className="encode-item-add-row">
              {scriptCatalog.length > preEncodeItems.filter((i) => i.type === 'script').length ? (
                <Button
                  label="Skript hinzufügen"
                  icon="pi pi-code"
                  severity="secondary"
                  outlined
                  onClick={() => onAddPreEncodeItem?.('script')}
                />
              ) : null}
              {chainCatalog.length > preEncodeItems.filter((i) => i.type === 'chain').length ? (
                <Button
                  label="Kette hinzufügen"
                  icon="pi pi-link"
                  severity="secondary"
                  outlined
                  onClick={() => onAddPreEncodeItem?.('chain')}
                />
              ) : null}
            </div>
          ) : null}
          <small>Ausführung vor dem Encoding, strikt nacheinander. Bei Fehler wird der Encode abgebrochen.</small>
        </div>
      ) : null}

      {/* Post-Encode Items (scripts + chains unified) */}
      <div className="post-script-box">
        <h4>Post-Encode Ausführungen (optional)</h4>
        {scriptCatalog.length === 0 && chainCatalog.length === 0 ? (
          <small>Keine Skripte oder Ketten konfiguriert. In den Settings anlegen.</small>
        ) : null}
        {postEncodeItems.length === 0 ? (
          <small>Keine Post-Encode Ausführungen ausgewählt.</small>
        ) : null}
        {postEncodeItems.map((item, rowIndex) => {
          const isScript = item.type === 'script';
          const canDrag = allowEncodeItemSelection && postEncodeItems.length > 1;
          const scriptObj = isScript ? (scriptById.get(normalizeScriptId(item.id)) || null) : null;
          const chainObj = !isScript ? (chainById.get(Number(item.id)) || null) : null;
          const name = isScript
            ? (scriptObj?.name || `Skript #${item.id}`)
            : (chainObj?.name || `Kette #${item.id}`);
          const usedScriptIds = new Set(
            postEncodeItems
              .filter((it, i) => it.type === 'script' && i !== rowIndex)
              .map((it) => normalizeScriptId(it.id))
              .filter((id) => id !== null)
              .map((id) => String(id))
          );
          const usedChainIds = new Set(
            postEncodeItems
              .filter((it, i) => it.type === 'chain' && i !== rowIndex)
              .map((it) => normalizeChainId(it.id))
              .filter((id) => id !== null)
              .map((id) => String(id))
          );
          const scriptOptions = scriptCatalog.map((s) => ({
            label: s.name,
            value: s.id,
            disabled: usedScriptIds.has(String(s.id))
          }));
          const chainOptions = chainCatalog.map((c) => ({
            label: c.name,
            value: c.id,
            disabled: usedChainIds.has(String(c.id))
          }));
          return (
            <div
              key={`post-item-${rowIndex}-${item.type}-${item.id}`}
              className={`post-script-row${allowEncodeItemSelection ? ' editable' : ''}`}
              onDragOver={(event) => {
                if (!canDrag) return;
                event.preventDefault();
                if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';
              }}
              onDrop={(event) => handlePostDrop(event, rowIndex)}
            >
              {allowEncodeItemSelection ? (
                <>
                  <span
                    className={`post-script-drag-handle pi pi-bars${canDrag ? '' : ' disabled'}`}
                    title={canDrag ? 'Ziehen zum Umordnen' : 'Mindestens zwei Einträge zum Umordnen'}
                    draggable={canDrag}
                    onDragStart={(event) => {
                      if (!canDrag) return;
                      event.dataTransfer.effectAllowed = 'move';
                      event.dataTransfer.setData('text/plain', String(rowIndex));
                    }}
                  />
                  <i className={`post-script-type-icon pi ${isScript ? 'pi-code' : 'pi-link'}`} title={isScript ? 'Skript' : 'Kette'} />
                  {isScript ? (
                    <Dropdown
                      value={normalizeScriptId(item.id)}
                      options={scriptOptions}
                      optionLabel="label"
                      optionValue="value"
                      optionDisabled="disabled"
                      onChange={(event) => onChangePostEncodeItem?.(rowIndex, 'script', event.value)}
                      className="full-width"
                    />
                  ) : (
                    <Dropdown
                      value={normalizeChainId(item.id)}
                      options={chainOptions}
                      optionLabel="label"
                      optionValue="value"
                      optionDisabled="disabled"
                      onChange={(event) => onChangePostEncodeItem?.(rowIndex, 'chain', event.value)}
                      className="full-width"
                    />
                  )}
                  <Button icon="pi pi-times" severity="danger" outlined onClick={() => onRemovePostEncodeItem?.(rowIndex)} />
                </>
              ) : (
                <small><i className={`pi ${isScript ? 'pi-code' : 'pi-link'}`} /> {`${rowIndex + 1}. ${name}`}</small>
              )}
            </div>
          );
        })}
        {allowEncodeItemSelection ? (
          <div className="encode-item-add-row">
            {scriptCatalog.length > postEncodeItems.filter((i) => i.type === 'script').length ? (
              <Button
                label="Skript hinzufügen"
                icon="pi pi-code"
                severity="secondary"
                outlined
                onClick={() => onAddPostEncodeItem?.('script')}
              />
            ) : null}
            {chainCatalog.length > postEncodeItems.filter((i) => i.type === 'chain').length ? (
              <Button
                label="Kette hinzufügen"
                icon="pi pi-link"
                severity="secondary"
                outlined
                onClick={() => onAddPostEncodeItem?.('chain')}
              />
            ) : null}
          </div>
        ) : null}
        <small>Ausführung nach erfolgreichem Encode, strikt nacheinander (Drag-and-Drop möglich).</small>
      </div>

      <h4>Titel</h4>
      <div className="media-title-list">
        {titles.length === 0 ? (
          <p>Keine Titel analysiert.</p>
        ) : titles.map((title) => {
          const titleEligible = title?.eligibleForEncode !== false;
          const titleChecked = allowTitleSelection
            ? (currentSelectedId !== null
              ? currentSelectedId === normalizeTitleId(title.id)
              : Boolean(title.selectedForEncode))
            : Boolean(title.selectedForEncode);
          const titleSelectionEntry = trackSelectionByTitle?.[title.id] || trackSelectionByTitle?.[String(title.id)] || {};
          const subtitleTracks = Array.isArray(title.subtitleTracks) ? title.subtitleTracks : [];
          const selectableSubtitleTrackIds = subtitleTracks
            .filter((track) => !isBurnedSubtitleTrack(track) && !isDuplicateSubtitleTrack(track))
            .map((track) => normalizeTrackId(track?.id))
            .filter((id) => id !== null);
          const selectableSubtitleTrackIdSet = new Set(selectableSubtitleTrackIds.map((id) => String(id)));
          const sortedSelectableSubtitleTracks = subtitleTracks
            .filter((track) => !isBurnedSubtitleTrack(track) && !isDuplicateSubtitleTrack(track))
            .slice()
            .sort((a, b) => (normalizeTrackId(a?.id) || Number.MAX_SAFE_INTEGER) - (normalizeTrackId(b?.id) || Number.MAX_SAFE_INTEGER));
          const defaultAudioTrackIds = (Array.isArray(title.audioTracks) ? title.audioTracks : [])
            .filter((track) => Boolean(track?.selectedByRule))
            .map((track) => normalizeTrackId(track?.id))
            .filter((id) => id !== null);
          const defaultSubtitleTrackIds = subtitleTracks
            .filter((track) => Boolean(track?.selectedByRule) && !isBurnedSubtitleTrack(track) && !isDuplicateSubtitleTrack(track))
            .map((track) => normalizeTrackId(track?.id))
            .filter((id) => id !== null);
          const defaultSubtitleVariantSelection = {};
          const defaultSubtitleLanguageOrder = [];
          const subtitleLanguageOrderSeen = new Set();
          for (const track of sortedSelectableSubtitleTracks) {
            const language = normalizeSubtitleLanguage(track?.language || track?.languageLabel || 'und');
            if (!subtitleLanguageOrderSeen.has(language)) {
              subtitleLanguageOrderSeen.add(language);
              defaultSubtitleLanguageOrder.push(language);
            }
            if (!Boolean(track?.selectedByRule)) {
              continue;
            }
            if (!defaultSubtitleVariantSelection[language]) {
              defaultSubtitleVariantSelection[language] = { full: false, forced: false };
            }
            const flags = resolveSubtitleTrackVariantFlags(track);
            if (flags.isForcedOnly) {
              defaultSubtitleVariantSelection[language].forced = true;
            } else {
              defaultSubtitleVariantSelection[language].full = true;
            }
          }
          const selectedAudioTrackIds = normalizeTrackIdList(
            Array.isArray(titleSelectionEntry?.audioTrackIds)
              ? titleSelectionEntry.audioTrackIds
              : defaultAudioTrackIds
          );
          const selectedSubtitleTrackIds = normalizeTrackIdList(
            Array.isArray(titleSelectionEntry?.subtitleTrackIds)
              ? titleSelectionEntry.subtitleTrackIds
              : defaultSubtitleTrackIds
          ).filter((id) => selectableSubtitleTrackIdSet.has(String(id)));
          const selectedSubtitleVariantSelection = normalizeSubtitleVariantSelectionMap(
            titleSelectionEntry?.subtitleVariantSelection || defaultSubtitleVariantSelection
          );
          const selectedSubtitleLanguageOrder = normalizeSubtitleLanguageOrder(
            Array.isArray(titleSelectionEntry?.subtitleLanguageOrder)
              ? titleSelectionEntry.subtitleLanguageOrder
              : defaultSubtitleLanguageOrder
          );
          const allowTrackSelectionForTitle = Boolean(
            allowTrackSelection
            && allowTitleSelection
            && titleChecked
          );

          return (
            <div key={title.id} className="media-title-block">
              <label className="readonly-check-row">
                <input
                  type="checkbox"
                  checked={titleChecked}
                  onChange={() => {
                    if (!allowTitleSelection || typeof onSelectEncodeTitle !== 'function') {
                      return;
                    }
                    onSelectEncodeTitle(normalizeTitleId(title.id));
                  }}
                  readOnly={!allowTitleSelection}
                  disabled={!allowTitleSelection}
                />
                <span>
                  #{title.id} | {title.fileName} | {formatDuration(title.durationMinutes)} | {formatBytes(title.sizeBytes)}
                  {title.encodeInput ? ' | Encode-Input' : ''}
                </span>
              </label>

              {title.playlistFile || title.playlistEvaluationLabel || title.playlistSegmentCommand ? (
                <div className="playlist-info-box">
                  <small>
                    <strong>Playlist:</strong> {title.playlistFile || '-'}
                    {title.playlistRecommended ? ' | empfohlen' : ''}
                  </small>
                  {title.playlistEvaluationLabel ? (
                    <small><strong>Bewertung:</strong> {title.playlistEvaluationLabel}</small>
                  ) : null}
                  {title.playlistSegmentCommand ? (
                    <small><strong>Analyse-Command:</strong> {title.playlistSegmentCommand}</small>
                  ) : null}
                  {Array.isArray(title.playlistSegmentFiles) && title.playlistSegmentFiles.length > 0 ? (
                    <details className="playlist-segment-toggle">
                      <summary>Segment-Dateien anzeigen ({title.playlistSegmentFiles.length})</summary>
                      <pre className="playlist-segment-output">{title.playlistSegmentFiles.join('\n')}</pre>
                    </details>
                  ) : (
                    <small>Segment-Ausgabe: keine m2ts-Einträge gefunden.</small>
                  )}
                </div>
              ) : null}

              <div className="media-track-grid">
                <TrackList
                  title={`Tonspuren (Titel #${title.id})`}
                  tracks={title.audioTracks || []}
                  type="audio"
                  allowSelection={allowTrackSelectionForTitle}
                  selectedTrackIds={selectedAudioTrackIds}
                  audioSelector={effectiveAudioSelector}
                  onToggleTrack={(trackId, checked) => {
                    if (!allowTrackSelectionForTitle || typeof onTrackSelectionChange !== 'function') {
                      return;
                    }
                    onTrackSelectionChange(title.id, 'audio', trackId, checked);
                  }}
                />
                <TrackList
                  title={`Subtitles (Titel #${title.id})`}
                  tracks={allowTrackSelectionForTitle
                    ? subtitleTracks.filter((track) => !isBurnedSubtitleTrack(track))
                    : subtitleTracks}
                  type="subtitle"
                  allowSelection={allowTrackSelectionForTitle}
                  selectedTrackIds={selectedSubtitleTrackIds}
                  selectedSubtitleVariantSelection={selectedSubtitleVariantSelection}
                  selectedSubtitleLanguageOrder={selectedSubtitleLanguageOrder}
                  onToggleSubtitleVariant={(language, variant, checked) => {
                    if (!allowTrackSelectionForTitle || typeof onSubtitleVariantSelectionChange !== 'function') {
                      return;
                    }
                    onSubtitleVariantSelectionChange(title.id, language, variant, checked);
                  }}
                />
              </div>
              {titleChecked ? (() => {
                const commandPreview = buildHandBrakeCommandPreview({
                  review,
                  title,
                  selectedAudioTrackIds,
                  selectedSubtitleTrackIds,
                  selectedSubtitleVariantSelection,
                  selectedSubtitleLanguageOrder,
                  commandOutputPath,
                  presetOverride: effectivePresetOverride
                });
                return (
                  <div className="handbrake-command-preview">
                    <small><strong>Finaler HandBrakeCLI-Befehl (Preview):</strong></small>
                    <pre>{commandPreview}</pre>
                  </div>
                );
              })() : null}
            </div>
          );
        })}
      </div>
    </div>
  );
}
