/**
 * Shared yt-dlp stderr parsing. Used by both download.ts and probe.ts so the
 * error-extraction logic lives in one place (audit M1).
 */

/** Trim yt-dlp's noisy stderr down to the most useful trailing error line. */
export function cleanError(stderr: string): string {
  const lines = stderr
    .split('\n')
    .map((l) => l.trim())
    .filter(Boolean)
  const errLine = [...lines].reverse().find((l) => /^error/i.test(l))
  return (errLine ?? lines[lines.length - 1] ?? '').replace(/^error:\s*/i, '').trim()
}

// Lines that carry no diagnostic value as post-processing context: per-tick
// download progress and the "Deleting original file" cleanup bookkeeping yt-dlp
// prints between merge steps.
const CONTEXT_NOISE = /^\[download\]|^Deleting original file/i
// A single context line, capped so a long output path can't bloat the message.
const CONTEXT_LINE_MAX = 200

/**
 * Richer error for the DOWNLOAD failure path (probe/indexer/updater keep the
 * concise cleanError line).
 *
 * yt-dlp collapses an ffmpeg post-processing failure to a bare
 * "ERROR: Postprocessing: Conversion failed!" that names neither the step that
 * died nor the reason — and without `--verbose` the underlying ffmpeg detail is
 * never emitted at all. What yt-dlp DOES print is a tagged progress line per
 * post-processor ([Merger], [ModifyChapters], [ExtractAudio], …; its phase tags
 * like [download]/[info] are lowercase). So for that generic wrapper we append
 * the last couple of non-noise trailing lines, which name the failing step (e.g.
 * the SponsorBlock re-encode) and surface any ffmpeg diagnostic when one is
 * present. Self-explanatory errors (a private video, a 403) are returned
 * unchanged.
 */
export function describeDownloadError(stderr: string): string {
  const headline = cleanError(stderr)
  if (!headline || !/conversion failed|postprocessing/i.test(headline)) return headline
  const lines = stderr
    .split('\n')
    .map((l) => l.trim())
    .filter(Boolean)
  const context: string[] = []
  for (const line of [...lines].reverse()) {
    if (context.length >= 2) break
    if (CONTEXT_NOISE.test(line) || /^error/i.test(line)) continue
    // Skip anything already conveyed by the headline (avoids echoing it back).
    if (headline.includes(line) || line.includes(headline)) continue
    const capped = line.length > CONTEXT_LINE_MAX ? `${line.slice(0, CONTEXT_LINE_MAX - 1)}…` : line
    if (!context.includes(capped)) context.unshift(capped)
  }
  return context.length > 0 ? `${headline} — ${context.join(' · ')}` : headline
}

/**
 * Full non-noise stderr, for the persisted error log's "Show details" expander
 * (Diagnostics card) — everything describeDownloadError condenses to one line,
 * kept in full (progress ticks and delete-bookkeeping still stripped) so a bug
 * report carries the real yt-dlp/ffmpeg transcript instead of a paraphrase.
 * Returns undefined when there's nothing beyond a single line — the headline
 * the caller already shows — so a plain, self-explanatory error doesn't grow a
 * details toggle with nothing new in it.
 */
export function stderrDetail(stderr: string): string | undefined {
  const lines = stderr
    .split('\n')
    .map((l) => l.trim())
    .filter((l) => l && !CONTEXT_NOISE.test(l))
  return lines.length > 1 ? lines.join('\n') : undefined
}
