#!/usr/bin/env bash
DW_THEME_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
if [[ -f "$DW_THEME_DIR/Theme.sh" ]]; then source "$DW_THEME_DIR/Theme.sh"
elif [[ -f "$DW_THEME_DIR/../share/dw-pdf/Theme.sh" ]]; then source "$DW_THEME_DIR/../share/dw-pdf/Theme.sh"
else C_RESET=""; C_BOLD=""; C_INFO=""; C_GOLD=""; C_OK=""; C_WARN=""; C_DANGER=""; C_MUTED=""; fi
# DW PDF-Rechtsklick (dw-pdf) fuer Linux 1.1.1
# PDFs zusammenfuegen, teilen, verkleinern und mit Texterkennung versehen; richtet Kontextmenue-Eintraege
# fuer Nautilus, Nemo und Dolphin ein. Ausgabe immer als neue Datei, das Original bleibt unveraendert.
# Keine Netzwerkverbindung, keine Telemetrie.
# MIT-Lizenz, (c) 2026 Michael Bergfeld, DoMeZos-Ware
set -u
export LC_ALL=C.UTF-8 2>/dev/null || export LC_ALL=C
VERSION="1.1.1"
SELF="$(readlink -f -- "${BASH_SOURCE[0]}" 2>/dev/null || printf '%s' "${BASH_SOURCE[0]}")"
OUTDIR=""; XRANGE=""; XLEVEL=""; XLANG=""
DRYRUN=0
MENU_MARK="dw-pdf-menue"

usage() {
cat <<'EOF'
DW PDF-Rechtsklick 1.1.1 - PDFs zusammenfügen, teilen, verkleinern, Texterkennung

Aufruf:
  dw-pdf zusammenfuegen DATEI1.pdf DATEI2.pdf [...]   Dateien in dieser Reihenfolge zusammenfügen
  dw-pdf teilen DATEI.pdf                             jede Seite als eigene Datei
  dw-pdf teilen DATEI.pdf 1-3,7                       nur diese Seiten als neue Datei
  dw-pdf verkleinern DATEI.pdf [niedrig|mittel|hoch]  Dateigröße verringern (Standard: mittel)
  dw-pdf ocr DATEI.pdf [Sprache]                      durchsuchbaren Text ergänzen (Standard: deu+eng)
  dw-pdf menue-installieren [--nur nautilus,nemo,dolphin]   Kontextmenü-Einträge einrichten
  dw-pdf menue-entfernen                              Kontextmenü-Einträge wieder entfernen
  dw-pdf menue-hinweis                                Anleitung für Thunar und andere Dateimanager
  dw-pdf --version | --hilfe

Optionen:
  --ausgabe ORDNER   Ergebnisse in diesen Ordner schreiben (Standard: neben das Original)
  --seiten BEREICH   Seitenbereich für "teilen" (wie das 2. Argument)
  --stufe STUFE      Stärke für "verkleinern": niedrig = beste Qualität, hoch = kleinste Datei
  --sprache CODE     Sprache(n) für "ocr", zum Beispiel deu, eng oder deu+eng
  --dry-run          Aktionen nur planen, keine PDF erstellen oder Menüs verändern
  --                 danach folgen nur noch Dateinamen (für Namen, die mit - beginnen)

Sicherheit:
  - Ergebnisse sind immer neue Dateien (Endung _zusammengefuegt, _seite-001, _verkleinert, _ocr).
    Bestehende Dateien werden nie überschrieben, das Original bleibt unverändert.
  - Passwortgeschützte oder verschlüsselte PDFs werden nicht bearbeitet (kein Entschlüsseln).
  - Kein Netzwerk, keine Telemetrie. Dateinamen werden nie von einer Shell ausgewertet.

Beendigungscodes: 0 in Ordnung, 1 Fehler, 2 falscher Aufruf, 3 benötigtes Programm fehlt,
                  4 nichts erzeugt (Verkleinern brachte keine kleinere Datei)
EOF
}

have() { command -v "$1" >/dev/null 2>&1; }
err() { printf '%s✗ %s%s\n' "$C_DANGER" "$*" "$C_RESET" >&2; }

install_hint() {
  printf '%s  Installation der benötigten Programme:%s\n' "$C_MUTED" "$C_RESET" >&2
  printf '%s    Debian/Ubuntu/Mint:  sudo apt install qpdf ghostscript ocrmypdf tesseract-ocr-deu%s\n' "$C_MUTED" "$C_RESET" >&2
  printf '%s    Fedora:              sudo dnf install qpdf ghostscript ocrmypdf tesseract-langpack-deu%s\n' "$C_MUTED" "$C_RESET" >&2
  printf '%s    Arch:                sudo pacman -S qpdf ghostscript ocrmypdf tesseract-data-deu%s\n' "$C_MUTED" "$C_RESET" >&2
  printf '%s    openSUSE:            sudo zypper install qpdf ghostscript ocrmypdf tesseract-ocr-traineddata-german%s\n' "$C_MUTED" "$C_RESET" >&2
}

ok_line() {
  printf '%s%sdw-pdf %s%s » %s%s%s %s✓%s\n' "$C_INFO" "$C_BOLD" "$1" "$C_RESET" "$C_GOLD" "$2" "$C_RESET" "$C_OK" "$C_RESET"
}

# ---------------------------------------------------------------- Dateinamen und Pfade
# Pfad so aendern, dass er nie als Option oder Seitenbereich gelesen wird
safe_path() { case "$1" in /*|./*) printf '%s' "$1";; *) printf './%s' "$1";; esac; }

bytelen() { ( LC_ALL=C; s="$1"; printf '%d' "${#s}" ); }

# Namensbestandteil ohne .pdf, auf sinnvolle Laenge gekuerzt
stem_of() {
  local base="${1##*/}" stem
  case "${base,,}" in *.pdf) stem="${base%.*}";; *) stem="$base";; esac
  [ -n "$stem" ] || stem="dokument"
  while [ "$(bytelen "$stem")" -gt 180 ]; do stem="${stem%?}"; done
  printf '%s' "$stem"
}

dir_of() { local d="${1%/*}"; [ "$d" = "$1" ] && d="."; [ -n "$d" ] || d="/"; printf '%s' "$d"; }

# freien Ausgabenamen finden: ordner stem suffix -> Pfad
free_name() {
  local dir="$1" stem="$2" suffix="$3" n=1 cand
  cand="$dir/${stem}${suffix}.pdf"
  while [ -e "$cand" ] || [ -L "$cand" ]; do
    n=$((n+1)); [ "$n" -gt 999 ] && return 1
    cand="$dir/${stem}${suffix}-${n}.pdf"
  done
  printf '%s' "$cand"
}

TMPS=()
# shellcheck disable=SC2329
cleanup() { local f; for f in "${TMPS[@]:-}"; do [ -n "$f" ] && rm -f -- "$f"; done; }
trap cleanup EXIT

# temporaere Datei im Zielordner anlegen (gleiches Dateisystem), Name in TMPF
make_tmp() {
  TMPF="$(mktemp -p "$1" ".dw-pdf-XXXXXX.tmp" 2>/dev/null)" || {
    err "Kein Schreibzugriff auf den Ordner: $1"
    err "Tipp: --ausgabe ORDNER wählt einen anderen Zielordner."
    return 1
  }
  TMPS+=("$TMPF" "$TMPF.err" "$TMPF.log")
  return 0
}

# fertige temporaere Datei unter endgueltigem Namen veroeffentlichen, ohne etwas zu ueberschreiben
# publish TMP ORDNER STEM SUFFIX -> gibt Pfad in PUBLISHED
publish() {
  local tmp="$1" dir="$2" stem="$3" suffix="$4" cand tries=0
  while [ "$tries" -lt 50 ]; do
    tries=$((tries+1))
    cand="$(free_name "$dir" "$stem" "$suffix")" || { err "Kein freier Dateiname für das Ergebnis in $dir."; return 1; }
    if ln -- "$tmp" "$cand" 2>/dev/null; then
      rm -f -- "$tmp"; PUBLISHED="$cand"; return 0
    fi
    if [ ! -e "$cand" ] && [ ! -L "$cand" ]; then
      # Dateisystem ohne harte Links (z. B. FAT): mv -n ueberschreibt ebenfalls nie
      mv -n -- "$tmp" "$cand" 2>/dev/null
      if [ ! -e "$tmp" ] && [ -f "$cand" ]; then PUBLISHED="$cand"; return 0; fi
    fi
  done
  err "Ergebnis konnte nicht gespeichert werden."
  return 1
}

# ---------------------------------------------------------------- PDF-Pruefungen
npages() { # npages DATEI -> Seitenzahl (leer bei Fehler)
  local f; f="$(safe_path "$1")"; local n=""
  if have qpdf; then n="$(qpdf --show-npages "$f" 2>/dev/null)"
  elif have pdfinfo; then n="$(pdfinfo "$f" 2>/dev/null | awk '/^Pages:/{print $2; exit}')"; fi
  case "$n" in ''|*[!0-9]*) return 1;; esac
  [ "$n" -ge 1 ] || return 1
  printf '%s' "$n"
}

# check_input DATEI: pruefen, ob eine bearbeitbare PDF vorliegt; setzt NPAGES
check_input() {
  local f="$1" rc
  NPAGES=""
  if [ ! -e "$f" ]; then err "Datei nicht gefunden: $f"; return 1; fi
  if [ ! -f "$f" ]; then err "Keine normale Datei: $f"; return 1; fi
  if [ ! -r "$f" ]; then err "Datei nicht lesbar: $f"; return 1; fi
  if ! head -c 1024 -- "$f" 2>/dev/null | grep -aq '%PDF-'; then err "Keine PDF-Datei: $f"; return 1; fi
  if have qpdf; then
    qpdf --requires-password "$(safe_path "$f")" >/dev/null 2>&1; rc=$?
    case "$rc" in
      0) err "Passwortgeschützte PDF, wird nicht bearbeitet: $f"; return 1;;
      3) err "Verschlüsselte PDF, wird nicht bearbeitet (dw-pdf entschlüsselt keine PDFs): $f"; return 1;;
    esac
  elif have pdfinfo && pdfinfo "$(safe_path "$f")" 2>/dev/null | grep -q '^Encrypted:[[:space:]]*yes'; then
    err "Verschlüsselte PDF, wird nicht bearbeitet (dw-pdf entschlüsselt keine PDFs): $f"; return 1
  fi
  NPAGES="$(npages "$f")" || { err "PDF beschädigt oder nicht lesbar (keine Seiten gefunden): $f"; return 1; }
  return 0
}

target_dir() { # Zielordner fuer eine Eingabedatei
  if [ -n "$OUTDIR" ]; then printf '%s' "$OUTDIR"; else dir_of "$1"; fi
}

need_tools() { # need_tools Befehl...
  local c miss=0
  for c in "$@"; do have "$c" || { err "Benötigtes Programm fehlt: $c"; miss=1; }; done
  [ "$miss" -eq 0 ] || { install_hint; return 3; }
  return 0
}

human() { awk -v b="$1" 'BEGIN{ split("Byte KB MB GB",u," "); i=1; while(b>=1024 && i<4){b/=1024;i++}; if(i==1) printf "%d %s",b,u[i]; else { x=sprintf("%.1f",b); sub(/\./,",",x); printf "%s %s",x,u[i] } }'; }
fsize() { stat -c %s -- "$1" 2>/dev/null; }

gui_ok() { [ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ] && have zenity; }

confirm_merge_order() {
  local i=1 f
  local -a items=()
  if gui_ok; then
    for f in "$@"; do
      items+=("$i" "${f##*/}")
      i=$((i + 1))
    done
    if ! zenity --list --title="Reihenfolge der PDFs prüfen" \
      --text="Die Seiten werden in dieser Reihenfolge zusammengefügt:" \
      --column="Nr." --column="Datei" "${items[@]}" >/dev/null 2>&1; then
      [ -t 0 ] && return 1
    fi
  elif [ -t 0 ]; then
    err "Reihenfolge der PDFs:"
    i=1
    for f in "$@"; do err "  $i. $f"; i=$((i + 1)); done
  fi
  return 0
}

with_progress() { # TEXT CMD...
  local text="$1"; shift
  if gui_ok; then
    ( printf '%s\n' "10"; printf '# %s\n' "$text"; "$@" >/dev/null; local rc=$?; printf '%s\n' "100"; exit "$rc" ) | zenity --progress --auto-close --no-cancel --text="$text" 2>/dev/null
    return "${PIPESTATUS[0]}"
  fi
  "$@"
}

# Fortschritt anhand von "Page N"-Zeilen (Ghostscript) bzw. Logzeilen (ocrmypdf)
page_progress() { # PAGES LOGFILE PID
  local pages="$1" log="$2" pid="$3" n=0 pct=1
  while kill -0 "$pid" 2>/dev/null; do
    n="$(grep -cE '^Page |OCR on|[Pp]age [0-9]' "$log" 2>/dev/null || printf '0')"
    n="${n:-0}"
    if [ "$pages" -gt 0 ] 2>/dev/null; then
      pct=$(( n * 99 / pages )); [ "$pct" -lt 1 ] && pct=1; [ "$pct" -gt 99 ] && pct=99
    fi
    printf '%s\n' "$pct"
    printf '# Seite %s von %s…\n' "$n" "$pages"
    sleep 0.25
  done
  printf '%s\n' "100"
}

run_with_page_progress() { # TITLE PAGES ERRFILE CMD...
  local title="$1" pages="$2" errf="$3"; shift 3
  local pid zpid rc
  if ! gui_ok; then
    "$@" >"$errf" 2>&1
    return $?
  fi
  : >"$errf"
  "$@" >"$errf" 2>&1 &
  pid=$!
  page_progress "$pages" "$errf" "$pid" | zenity --progress --auto-close --no-cancel --text="$title" 2>/dev/null &
  zpid=$!
  wait "$pid"; rc=$?
  wait "$zpid" 2>/dev/null || true
  return "$rc"
}

# ---------------------------------------------------------------- Aktionen
do_merge() { # Dateien...
  local f total=0 first="" rc=0
  [ "$#" -ge 2 ] || { err "Zum Zusammenfügen werden mindestens zwei PDF-Dateien benötigt."; return 2; }
  confirm_merge_order "$@" || { err "Zusammenfügen abgebrochen."; return 1; }
  if have qpdf; then :; elif have pdfunite; then :; else need_tools qpdf; return 3; fi
  local -a in=()
  for f in "$@"; do
    check_input "$f" || return 1
    total=$((total + NPAGES)); in+=("$(safe_path "$f")")
    [ -z "$first" ] && first="$f"
  done
  local dir stem; dir="$(target_dir "$first")"; stem="$(stem_of "$first")"
  make_tmp "$dir" || return 1
  if have qpdf; then
    qpdf --empty --pages "${in[@]}" -- "$TMPF" 2>"$TMPF.err"; rc=$?
    [ "$rc" -eq 3 ] && rc=0
    [ "$rc" -eq 0 ] || { sed 's/^/  /' "$TMPF.err" >&2; rm -f -- "$TMPF.err"; err "Zusammenfügen fehlgeschlagen."; return 1; }
    rm -f -- "$TMPF.err"
  else
    pdfunite "${in[@]}" "$TMPF" 2>/dev/null || { err "Zusammenfügen mit pdfunite fehlgeschlagen."; return 1; }
  fi
  local got; got="$(npages "$TMPF")" || { err "Ergebnis ist keine lesbare PDF."; return 1; }
  if [ "$got" != "$total" ]; then err "Seitenzahl stimmt nicht (erwartet $total, erhalten $got); Ergebnis verworfen."; return 1; fi
  publish "$TMPF" "$dir" "$stem" "_zusammengefuegt" || return 1
  ok_line "zusammenfuegen" "${PUBLISHED##*/} ($(human "$(fsize "$PUBLISHED")"), $got Seiten aus $# Dateien)"
}

valid_ranges() { # valid_ranges BEREICH SEITENZAHL
  local r="$1" max="$2" part a b
  [[ "$r" =~ ^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$ ]] || return 1
  local IFS=','
  for part in $r; do
    a="${part%%-*}"; b="${part##*-}"
    [ "$((10#$a))" -ge 1 ] && [ "$((10#$b))" -ge "$((10#$a))" ] && [ "$((10#$b))" -le "$max" ] || return 1
  done
  return 0
}
count_ranges() { # Anzahl Seiten im Bereich
  local r="$1" part a b n=0
  local IFS=','
  for part in $r; do a="${part%%-*}"; b="${part##*-}"; n=$((n + 10#$b - 10#$a + 1)); done
  printf '%d' "$n"
}

do_split() { # DATEI [BEREICH]
  local f="$1" range="${2:-}" rc=0
  need_tools qpdf || return $?
  check_input "$f" || return 1
  local dir stem in; dir="$(target_dir "$f")"; stem="$(stem_of "$f")"; in="$(safe_path "$f")"
  if [ -n "$range" ]; then
    if ! valid_ranges "$range" "$NPAGES"; then
      err "Ungültiger Seitenbereich \"$range\" für $f (die Datei hat $NPAGES Seiten). Beispiel: 1-3,7"; return 2
    fi
    make_tmp "$dir" || return 1
    qpdf --empty --pages "$in" "$range" -- "$TMPF" 2>"$TMPF.err"; rc=$?
    [ "$rc" -eq 3 ] && rc=0
    [ "$rc" -eq 0 ] || { sed 's/^/  /' "$TMPF.err" >&2; rm -f -- "$TMPF.err"; err "Seitenauswahl fehlgeschlagen."; return 1; }
    rm -f -- "$TMPF.err"
    local want got; want="$(count_ranges "$range")"; got="$(npages "$TMPF")" || { err "Ergebnis ist keine lesbare PDF."; return 1; }
    [ "$got" = "$want" ] || { err "Seitenzahl stimmt nicht (erwartet $want, erhalten $got); Ergebnis verworfen."; return 1; }
    local tag="${range//,/_}"; [ "${#tag}" -gt 40 ] && tag="auswahl"
    publish "$TMPF" "$dir" "$stem" "_seiten_${tag}" || return 1
    ok_line "teilen" "${PUBLISHED##*/} ($(human "$(fsize "$PUBLISHED")"), $got Seiten)"
    return 0
  fi
  if [ "$NPAGES" -lt 2 ]; then err "Die Datei hat nur eine Seite, es gibt nichts zu teilen: $f"; return 1; fi
  local width=3 p made=0; [ "${#NPAGES}" -gt 3 ] && width="${#NPAGES}"
  for (( p=1; p<=NPAGES; p++ )); do
    make_tmp "$dir" || return 1
    qpdf --empty --pages "$in" "$p" -- "$TMPF" 2>"$TMPF.err"; rc=$?
    [ "$rc" -eq 3 ] && rc=0
    if [ "$rc" -ne 0 ]; then sed 's/^/  /' "$TMPF.err" >&2; rm -f -- "$TMPF.err" "$TMPF"; err "Seite $p konnte nicht gespeichert werden."; return 1; fi
    rm -f -- "$TMPF.err"
    publish "$TMPF" "$dir" "$stem" "$(printf '_seite-%0*d' "$width" "$p")" || return 1
    made=$((made+1))
  done
  ok_line "teilen" "$made Dateien (${stem}_seite-$(printf '%0*d' "$width" 1).pdf …) in $dir"
}

do_shrink() { # DATEI [STUFE]
  local f="$1" level="${2:-mittel}" setting
  case "$level" in
    niedrig) setting="/printer";;
    mittel)  setting="/ebook";;
    hoch)    setting="/screen";;
    *) err "Unbekannte Stufe \"$level\". Erlaubt: niedrig (beste Qualität), mittel, hoch (kleinste Datei)."; return 2;;
  esac
  need_tools gs || return $?
  check_input "$f" || return 1
  local dir stem in; dir="$(target_dir "$f")"; stem="$(stem_of "$f")"; in="$(safe_path "$f")"
  make_tmp "$dir" || return 1
  if ! run_with_page_progress "PDF verkleinern" "$NPAGES" "$TMPF.err" \
        gs -dNOPAUSE -dBATCH -dSAFER -sDEVICE=pdfwrite -dCompatibilityLevel=1.5 "-dPDFSETTINGS=$setting" \
          -dDetectDuplicateImages=true -dCompressFonts=true "-sOutputFile=$TMPF" -f "$in"; then
    sed 's/^/  /' "$TMPF.err" >&2; rm -f -- "$TMPF.err"; err "Verkleinern fehlgeschlagen: $f"; return 1
  fi
  rm -f -- "$TMPF.err"
  local got; got="$(npages "$TMPF")" || { err "Ergebnis ist keine lesbare PDF; verworfen."; return 1; }
  [ "$got" = "$NPAGES" ] || { err "Seitenzahl stimmt nicht (vorher $NPAGES, nachher $got); Ergebnis verworfen."; return 1; }
  local a b; a="$(fsize "$f")"; b="$(fsize "$TMPF")"
  if [ -z "$a" ] || [ -z "$b" ] || [ "$b" -ge "$a" ]; then
    printf 'Keine Verkleinerung möglich: %s (vorher %s, nachher %s). Es wurde keine Datei angelegt.\n' "$f" "$(human "${a:-0}")" "$(human "${b:-0}")"
    rm -f -- "$TMPF"
    return 4
  fi
  publish "$TMPF" "$dir" "$stem" "_verkleinert" || return 1
  local pct=$(( (a - b) * 100 / a ))
  ok_line "verkleinern" "${PUBLISHED##*/} ($(human "$b"))"
  printf 'Vorher: %s → Nachher: %s (%s−%s%%%s)\n' "$(human "$a")" "$(human "$b")" "$C_GOLD" "$pct" "$C_RESET"
  if gui_ok; then
    zenity --notification --text="PDF verkleinert: von $(human "$a") auf $(human "$b") (-${pct}%)" 2>/dev/null || true
  fi
}

do_ocr() { # DATEI [SPRACHE]
  local f="$1" lang="${2:-deu+eng}"
  [[ "$lang" =~ ^[a-z]{3}(_[a-z]+)?(\+[a-z]{3}(_[a-z]+)?)*$ ]] || { err "Ungültiger Sprachcode \"$lang\". Beispiele: deu, eng, deu+eng"; return 2; }
  if ! have ocrmypdf; then
    err "Für die Texterkennung wird ocrmypdf benötigt (mit Tesseract und der Sprachdatei)."; install_hint; return 3
  fi
  if have tesseract; then
    local l missing=""
    local -a langs=()
    IFS='+' read -r -a langs <<<"$lang"
    for l in "${langs[@]}"; do tesseract --list-langs 2>/dev/null | grep -qx -- "$l" || missing+=" $l"; done
    if [ -n "$missing" ]; then
      printf '%s⚠ Tesseract-Sprachdatei fehlt:%s%s\n' "$C_WARN" "$missing" "$C_RESET" >&2
      install_hint
      return 3
    fi
  fi
  check_input "$f" || return 1
  local dir stem in; dir="$(target_dir "$f")"; stem="$(stem_of "$f")"; in="$(safe_path "$f")"
  make_tmp "$dir" || return 1
  # Die Temp-Datei existiert schon (leer); ocrmypdf ueberschreibt sie. --skip-text laesst Seiten mit Text unveraendert.
  if ! run_with_page_progress "Texterkennung" "$NPAGES" "$TMPF.log" \
        ocrmypdf --skip-text -l "$lang" --output-type pdf -- "$in" "$TMPF"; then
    tail -n 8 "$TMPF.log" | sed 's/^/  /' >&2; rm -f -- "$TMPF.log"; err "Texterkennung fehlgeschlagen: $f"; return 1
  fi
  rm -f -- "$TMPF.log"
  local got; got="$(npages "$TMPF")" || { err "Ergebnis ist keine lesbare PDF; verworfen."; return 1; }
  [ "$got" = "$NPAGES" ] || { err "Seitenzahl stimmt nicht (vorher $NPAGES, nachher $got); Ergebnis verworfen."; return 1; }
  publish "$TMPF" "$dir" "$stem" "_ocr" || return 1
  ok_line "ocr" "${PUBLISHED##*/} ($(human "$(fsize "$PUBLISHED")"), Sprache: $lang)"
}

# ---------------------------------------------------------------- Kontextmenue-Modus
notify_user() { # Titel Text
  local title="$1" text="$2"
  if [ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ]; then
    if have zenity; then zenity --info --no-markup --title="$title" --text="$text" 2>/dev/null && return 0; fi
    if have kdialog; then kdialog --title "$title" --msgbox "$text" 2>/dev/null && return 0; fi
    if have notify-send; then notify-send "$title" "$text" 2>/dev/null && return 0; fi
  fi
  printf '%s\n%s\n' "$title" "$text" >&2
}

run_menu() { # AKTION DATEI...
  local action="$1"; shift
  local log; log="$(mktemp)" || exit 1; TMPS+=("$log")
  local rc=0 f nok=0 nfail=0 r
  [ "$#" -ge 1 ] || { notify_user "DW PDF" "Keine Dateien ausgewählt."; return 2; }
  case "$action" in
    zusammenfuegen)
      if [ "$#" -lt 2 ]; then notify_user "DW PDF" "Bitte mindestens zwei PDFs auswählen (die Reihenfolge entspricht der Anzeige im Dateimanager)."; return 2; fi
      do_merge "$@" >"$log" 2>&1; rc=$?;;
    teilen|verkleinern|ocr)
      for f in "$@"; do
        case "$action" in teilen) do_split "$f";; verkleinern) do_shrink "$f" "${XLEVEL:-mittel}";; ocr) do_ocr "$f" "${XLANG:-deu+eng}";; esac >>"$log" 2>&1
        r=$?
        if [ "$r" -eq 0 ]; then nok=$((nok+1)); else nfail=$((nfail+1)); [ "$rc" -eq 0 ] && rc=$r; fi
      done;;
    *) notify_user "DW PDF" "Unbekannte Aktion: $action"; return 2;;
  esac
  local text; text="$(head -c 3000 "$log")"
  [ -n "$text" ] || text="Fertig."
  if [ "$rc" -eq 0 ]; then notify_user "DW PDF: fertig" "$text"; else notify_user "DW PDF: Problem" "$text"; fi
  return "$rc"
}

# ---------------------------------------------------------------- Menue-Eintraege einrichten
data_home() { printf '%s' "${XDG_DATA_HOME:-$HOME/.local/share}"; }

sh_quote() { local s="${1//\'/\'\\\'\'}"; printf "'%s'" "$s"; }

MENU_ACTIONS=(zusammenfuegen teilen verkleinern ocr)
menu_label() {
  case "$1" in
    zusammenfuegen) echo "PDFs zusammenfügen";;
    teilen) echo "PDF teilen (jede Seite einzeln)";;
    verkleinern) echo "PDF verkleinern";;
    ocr) echo "PDF Texterkennung (OCR)";;
  esac
}
menu_comment() {
  case "$1" in
    zusammenfuegen) echo "Ausgewählte PDFs zu einer neuen Datei zusammenfügen";;
    teilen) echo "Jede Seite als eigene neue PDF speichern";;
    verkleinern) echo "Neue, kleinere Kopie der PDF anlegen";;
    ocr) echo "Neue, durchsuchbare Kopie der PDF anlegen";;
  esac
}

menu_install() {
  local only="nautilus,nemo,dolphin" a x n=0
  while [ $# -gt 0 ]; do
    case "$1" in
      --nur) shift; [ $# -gt 0 ] || { err "--nur benötigt eine Liste."; return 2; }; only="$1";;
      --alle) only="nautilus,nemo,dolphin";;
      *) err "Unbekannte Option: $1"; return 2;;
    esac
    shift
  done
  local -a want=()
  IFS=',' read -r -a want <<<"$only"
  for x in "${want[@]}"; do case "$x" in nautilus|nemo|dolphin) ;; *) err "Unbekannter Dateimanager: $x (erlaubt: nautilus, nemo, dolphin)"; return 2;; esac; done
  local d; d="$(data_home)"
  local bin="$SELF"
  local safe=1
  case "$bin" in *[!A-Za-z0-9/._+-]*) safe=0;; esac
  for x in "${want[@]}"; do
    case "$x" in
      nautilus)
        local nd="$d/nautilus/scripts/DW PDF"
        mkdir -p -- "$nd" || { err "Ordner nicht anlegbar: $nd"; return 1; }
        for a in "${MENU_ACTIONS[@]}"; do
          local nf; nf="$nd/$(menu_label "$a")"
          if [ -e "$nf" ] && ! grep -q "$MENU_MARK" "$nf" 2>/dev/null; then err "Übersprungen (fremde Datei vorhanden): $nf"; continue; fi
          printf '#!/bin/sh\n# %s: von DW PDF-Rechtsklick angelegt, wird mit "dw-pdf menue-entfernen" wieder gelöscht\nexec %s --menue %s -- "$@"\n' "$MENU_MARK" "$(sh_quote "$bin")" "$a" >"$nf" && chmod 0755 "$nf" && n=$((n+1))
        done
        echo "Nautilus: Skripte in $nd (Rechtsklick > Skripte > DW PDF)";;
      nemo)
        if [ "$safe" -eq 0 ]; then err "Nemo: Pfad enthält Sonderzeichen ($bin); Einträge nicht eingerichtet. Installieren Sie dw-pdf in einen Pfad ohne Leer- und Sonderzeichen."; continue; fi
        local md="$d/nemo/actions"
        mkdir -p -- "$md" || { err "Ordner nicht anlegbar: $md"; return 1; }
        for a in "${MENU_ACTIONS[@]}"; do
          local mf="$md/dw-pdf-$a.nemo_action" sel="notnone"
          [ "$a" = zusammenfuegen ] && sel="m"
          if [ -e "$mf" ] && ! grep -q "$MENU_MARK" "$mf" 2>/dev/null; then err "Übersprungen (fremde Datei vorhanden): $mf"; continue; fi
          printf '[Nemo Action]\n# %s: von DW PDF-Rechtsklick angelegt, wird mit "dw-pdf menue-entfernen" wieder gelöscht\nName=%s\nComment=%s\nExec=%s --menue %s -- %%F\nIcon-Name=application-pdf\nSelection=%s\nExtensions=pdf;\nMimetypes=application/pdf;\nQuote=double\n' \
            "$MENU_MARK" "$(menu_label "$a")" "$(menu_comment "$a")" "$bin" "$a" "$sel" >"$mf" && n=$((n+1))
        done
        echo "Nemo: Aktionen in $md (Rechtsklick auf eine PDF; ggf. Nemo neu starten: nemo -q)";;
      dolphin)
        if [ "$safe" -eq 0 ]; then err "Dolphin: Pfad enthält Sonderzeichen ($bin); Einträge nicht eingerichtet. Installieren Sie dw-pdf in einen Pfad ohne Leer- und Sonderzeichen."; continue; fi
        local kd="$d/kio/servicemenus" kf="$d/kio/servicemenus/dw-pdf.desktop"
        mkdir -p -- "$kd" || { err "Ordner nicht anlegbar: $kd"; return 1; }
        if [ -e "$kf" ] && ! grep -q "$MENU_MARK" "$kf" 2>/dev/null; then err "Übersprungen (fremde Datei vorhanden): $kf"; continue; fi
        {
          printf '[Desktop Entry]\n# %s: von DW PDF-Rechtsklick angelegt, wird mit "dw-pdf menue-entfernen" wieder gelöscht\nType=Service\nServiceTypes=KonqPopupMenu/Plugin\nMimeType=application/pdf;\nX-KDE-Submenu=DW PDF\nActions=dwpdfzusammenfuegen;dwpdfteilen;dwpdfverkleinern;dwpdfocr;\n' "$MENU_MARK"
          for a in "${MENU_ACTIONS[@]}"; do
            printf '\n[Desktop Action dwpdf%s]\nName=%s\nIcon=application-pdf\nExec=%s --menue %s -- %%F\n' "$a" "$(menu_label "$a")" "$bin" "$a"
          done
        } >"$kf" && chmod 0755 "$kf" && n=$((n+1))
        echo "Dolphin: Menü in $kf (Rechtsklick auf eine PDF > DW PDF)";;
    esac
  done
  echo "Fertig: $n Menü-Datei(en) eingerichtet. Entfernen mit: dw-pdf menue-entfernen"
  echo "Thunar und andere Dateimanager: dw-pdf menue-hinweis"
}

menu_remove() {
  local d n=0 f; d="$(data_home)"
  for f in "$d/nautilus/scripts/DW PDF"/* "$d"/nemo/actions/dw-pdf-*.nemo_action "$d/kio/servicemenus/dw-pdf.desktop"; do
    [ -f "$f" ] || continue
    if grep -q "$MENU_MARK" "$f" 2>/dev/null; then rm -f -- "$f" && n=$((n+1)); else err "Übersprungen (nicht von dw-pdf angelegt): $f"; fi
  done
  rmdir -- "$d/nautilus/scripts/DW PDF" 2>/dev/null
  echo "Entfernt: $n Menü-Datei(en)."
}

menu_hint() {
cat <<'EOF'
Thunar (Xfce): Kontextmenü-Einträge lassen sich dort nur über "Benutzerdefinierte Aktionen" anlegen:

  1. Thunar öffnen: Bearbeiten > Benutzerdefinierte Aktionen einrichten > Pluszeichen.
  2. Name: PDFs zusammenfügen
     Befehl: dw-pdf --menue zusammenfuegen -- %F
     Reiter "Erscheinungsbedingungen": Dateimuster *.pdf; Haken bei "Sonstige Dateien".
  3. Weitere Einträge mit demselben Muster:
       PDF teilen (jede Seite einzeln):  dw-pdf --menue teilen -- %F
       PDF verkleinern:                  dw-pdf --menue verkleinern -- %F
       PDF Texterkennung (OCR):          dw-pdf --menue ocr -- %F

Steht dw-pdf nicht im PATH, den vollständigen Pfad eintragen (zum Beispiel /home/NAME/.local/bin/dw-pdf).
Andere Dateimanager (PCManFM, Caja ...): ähnliche Funktionen heißen "Benutzerdefinierte Aktionen" oder "Skripte";
Befehl wie oben, mit den ausgewählten Dateien als Argumenten.
EOF
}

# ---------------------------------------------------------------- Hauptprogramm
MENUMODE=""
declare -a MENUARGS=()
declare -a POS=()
ONLYFILES=0
while [ $# -gt 0 ]; do
  if [ "$ONLYFILES" -eq 1 ]; then POS+=("$1"); shift; continue; fi
  case "$1" in
    --) ONLYFILES=1;;
    --ausgabe) shift; [ $# -gt 0 ] || { err "--ausgabe benötigt einen Ordner."; exit 2; }; OUTDIR="${1%/}"; [ -n "$OUTDIR" ] || OUTDIR="/";;
    --seiten) shift; [ $# -gt 0 ] || { err "--seiten benötigt einen Bereich."; exit 2; }; XRANGE="$1";;
    --stufe) shift; [ $# -gt 0 ] || { err "--stufe benötigt einen Wert."; exit 2; }; XLEVEL="$1";;
    --sprache) shift; [ $# -gt 0 ] || { err "--sprache benötigt einen Code."; exit 2; }; XLANG="$1";;
    --dry-run) DRYRUN=1;;
    --nur) shift; [ $# -gt 0 ] || { err "--nur benötigt eine Liste."; exit 2; }; MENUARGS+=(--nur "$1");;
    --alle) MENUARGS+=(--alle);;
    --menue) shift; [ $# -gt 0 ] || { err "--menue benötigt eine Aktion."; exit 2; }; MENUMODE="$1";;
    --version) echo "DW PDF-Rechtsklick $VERSION"; exit 0;;
    -h|--help|--hilfe|hilfe) usage; exit 0;;
    -*) err "Unbekannte Option: $1"; err "Dateinamen, die mit - beginnen, nach -- angeben oder als ./-name.pdf."; usage >&2; exit 2;;
    *) POS+=("$1");;
  esac
  shift
done

if [ -n "$OUTDIR" ] && [ ! -d "$OUTDIR" ]; then err "Zielordner nicht gefunden: $OUTDIR"; exit 2; fi

if [ "$DRYRUN" -eq 1 ]; then
  if [ -n "$MENUMODE" ]; then
    printf '%sPROBELAUF: Kontextmenü %s — es werden keine Dateien geändert.%s\n' "$C_INFO" "$MENUMODE" "$C_RESET"
    exit 0
  fi
  [ "${#POS[@]}" -gt 0 ] || { err "Aktion fehlt."; exit 2; }
  drycmd="${POS[0]}"
  case "$drycmd" in zusammenfuegen|merge|teilen|split|verkleinern|shrink|ocr|menue-installieren|menue-entfernen|menue-hinweis) ;; *) err "Unbekannte Aktion: $drycmd"; exit 2;; esac
  printf '%s%sPROBELAUF: dw-pdf %s%s\n' "$C_INFO" "$C_BOLD" "$drycmd" "$C_RESET"
  case "$drycmd" in
    zusammenfuegen|merge) [ "${#POS[@]}" -ge 3 ] || { err "Mindestens zwei PDF-Dateien benötigt."; exit 2; };;
    teilen|split|verkleinern|shrink|ocr) [ "${#POS[@]}" -ge 2 ] || { err "Eine PDF-Datei fehlt."; exit 2; };;
  esac
  case "$drycmd" in
    verkleinern|shrink) case "${POS[2]:-${XLEVEL:-mittel}}" in niedrig|mittel|hoch) ;; *) err "Unbekannte Verkleinerungsstufe."; exit 2;; esac;;
    ocr) [[ "${POS[2]:-${XLANG:-deu+eng}}" =~ ^[a-z]{3}(_[a-z]+)?(\+[a-z]{3}(_[a-z]+)?)*$ ]] || { err "Ungültiger OCR-Sprachcode."; exit 2; };;
  esac
  for f in "${POS[@]:1}"; do
    case "$drycmd" in menue-*) break;; esac
    case "${f,,}" in *.pdf) ;; *) continue;; esac
    [ -f "$f" ] || { err "Nicht vorhanden: $f"; exit 1; }
    printf '  %s%s%s (%s)\n' "$C_GOLD" "$f" "$C_RESET" "$(human "$(fsize "$f")")"
  done
  case "$drycmd" in
    zusammenfuegen|merge) suffix="_zusammengefuegt";;
    teilen|split) range="${POS[2]:-$XRANGE}"; suffix="_seite-001"; [ -n "$range" ] && suffix="_seiten_${range//,/_}";;
    verkleinern|shrink) suffix="_verkleinert";;
    ocr) suffix="_ocr";;
    *) suffix="";;
  esac
  if [ -n "$suffix" ]; then
    first="${POS[1]}"; destination="$(free_name "$(target_dir "$first")" "$(stem_of "$first")" "$suffix")" || exit 1
    printf '  → %s%s%s (neue Datei; Original bleibt)\n' "$C_GOLD" "$destination" "$C_RESET"
  fi
  printf '%sKeine Eingaben oder Ausgaben verändert.%s\n' "$C_OK" "$C_RESET"
  exit 0
fi

if [ -n "$MENUMODE" ]; then
  run_menu "$MENUMODE" "${POS[@]}"
  exit $?
fi

[ "${#POS[@]}" -ge 1 ] || { usage >&2; exit 2; }
CMD="${POS[0]}"; ARGS=("${POS[@]:1}")
rc=0
case "$CMD" in
  zusammenfuegen|merge)
    [ "${#ARGS[@]}" -ge 2 ] || { err "Zum Zusammenfügen werden mindestens zwei PDF-Dateien benötigt."; exit 2; }
    do_merge "${ARGS[@]}"; exit $?;;
  teilen|split)
    [ "${#ARGS[@]}" -ge 1 ] && [ "${#ARGS[@]}" -le 2 ] || { err "Aufruf: dw-pdf teilen DATEI.pdf [1-3,7]"; exit 2; }
    if [ "${#ARGS[@]}" -eq 2 ]; then XRANGE="${ARGS[1]}"; fi
    do_split "${ARGS[0]}" "$XRANGE"; exit $?;;
  verkleinern|shrink)
    [ "${#ARGS[@]}" -ge 1 ] && [ "${#ARGS[@]}" -le 2 ] || { err "Aufruf: dw-pdf verkleinern DATEI.pdf [niedrig|mittel|hoch]"; exit 2; }
    [ "${#ARGS[@]}" -eq 2 ] && XLEVEL="${ARGS[1]}"
    do_shrink "${ARGS[0]}" "${XLEVEL:-mittel}"; exit $?;;
  ocr)
    [ "${#ARGS[@]}" -ge 1 ] && [ "${#ARGS[@]}" -le 2 ] || { err "Aufruf: dw-pdf ocr DATEI.pdf [Sprache]"; exit 2; }
    [ "${#ARGS[@]}" -eq 2 ] && XLANG="${ARGS[1]}"
    do_ocr "${ARGS[0]}" "${XLANG:-deu+eng}"; exit $?;;
  menue-installieren) menu_install "${MENUARGS[@]}" "${ARGS[@]}"; exit $?;;
  menue-entfernen) menu_remove; exit $?;;
  menue-hinweis) menu_hint; exit 0;;
  *) err "Unbekannter Befehl: $CMD"; usage >&2; exit 2;;
esac
