#!/usr/bin/env bash
# ============================================================================
# actualizar-craft — Reempaqueta para Quirinux las apps *Craft de ArtCraft
#
# Solo actúa si el pool del repositorio está desactualizado:
#   1. Consulta la última release en https://github.com/storytold/<app>
#      (sigue la redirección de /releases/latest: sin API, sin token).
#   2. Compara con la versión más alta de craft-<app> que ya hay en el pool
#      (POOL/craft-<app>/*.deb), en la carpeta actual y, si se indica -r,
#      también en Packages.gz.
#   3. Solo si la release es más nueva: descarga el .deb original
#      (linux-x86_64), verifica su SHA-256 con SHA256SUMS.txt y lo reempaqueta
#      (Version X.Y.Z+q2, Maintainer tuyo, Breaks/Replaces, icono en pixmaps,
#      md5sums, Installed-Size, xz). El .deb nuevo se guarda en la carpeta
#      desde la que ejecutas el script (no en el pool).
#   4. Si actualiza pdfcraft, regenera craft-printcraft (transición) también
#      en la carpeta actual.
#
# Uso:  actualizar-craft [opciones]      (ver --ayuda)
# Requisitos: bash 4+, curl, gzip, dpkg-deb (dpkg >= 1.19), sha256sum, md5sum, flock
# ============================================================================

set -uo pipefail
export LC_ALL=C

# ---------------------------- Configuración ---------------------------------
ORG="${CRAFT_ORG:-storytold}"                 # organización en GitHub
REV="${CRAFT_REV:-+q2}"                       # sufijo de versión Quirinux
MANTENEDOR="${CRAFT_MANTENEDOR:-Charlie Martinez <cmartinez@quirinux.org>}"
ARCH_UP="${CRAFT_ARCH_UP:-x86_64}"            # arquitectura del .deb original
REPO="${CRAFT_REPO:-}"                        # p. ej. https://repo.quirinux.org
SUITE="${CRAFT_SUITE:-quirinux2}"             # distribución del repo apt
COMPONENTE="${CRAFT_COMPONENTE:-main}"
ARCH_DEB="${CRAFT_ARCH_DEB:-amd64}"           # arquitectura en el índice apt
POOL_DEFECTO="/media/charlie/ARCHIVOS/Repo.quirinux.org/pool/main/c"

APPS_TODAS=(cadcraft deckcraft designcraft effectcraft figcraft filmcraft
            gridcraft lightcraft pdfcraft photocraft soundcraft vectorcraft wordcraft)

# Descripción corta en español (sin ñ ni tildes, como en tus paquetes actuales)
declare -A DESC=(
  [cadcraft]="Programa de diseno asistido por computadora (CAD), similar a AutoCAD."
  [deckcraft]="Editor de presentaciones, similar a PowerPoint."
  [designcraft]="Programa de maquetacion editorial, similar a InDesign."
  [effectcraft]="Compositor de efectos visuales y motion graphics, similar a After Effects."
  [figcraft]="Herramienta de diseno de interfaces y prototipos, similar a Figma."
  [filmcraft]="Editor de video, similar a Premiere Pro."
  [gridcraft]="Hoja de calculo, similar a Excel."
  [lightcraft]="Revelador de fotos RAW y gestor de fototeca, similar a Lightroom."
  [pdfcraft]="Editor de PDF, similar a Acrobat."
  [photocraft]="Editor de imagenes, similar a Photoshop."
  [soundcraft]="Estacion de trabajo de audio digital (DAW), similar a Pro Tools."
  [vectorcraft]="Programa de ilustracion vectorial, similar a Illustrator."
  [wordcraft]="Procesador de textos, similar a Word."
)

# Sección Debian (la misma que tienen hoy tus paquetes)
declare -A SECCION=(
  [cadcraft]=graphics [deckcraft]=editors [designcraft]=graphics
  [effectcraft]=graphics [figcraft]=graphics [filmcraft]=video
  [gridcraft]=misc [lightcraft]=graphics [pdfcraft]=graphics
  [photocraft]=graphics [soundcraft]=sound [vectorcraft]=graphics [wordcraft]=graphics
)

# Nombres antiguos a los que debe reemplazar el paquete (Breaks/Replaces).
declare -A ANTERIORES=(
  [pdfcraft]="craft-printcraft printcraft"
)

# ------------------------------- Opciones -----------------------------------
DIR="$PWD"                                   # salida de los .deb nuevos
POOL="${CRAFT_POOL:-$POOL_DEFECTO}"           # pool donde se buscan versiones actuales
CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/actualizar-craft"
SOLO_COMPROBAR=0 FORZAR=0 LIMPIAR=0 CONSERVAR=0 VERIFICAR=1
APPS=()

ayuda() {
cat <<EOF
Uso: ${0##*/} [opciones]

  -a, --apps a,b,c      Solo estas apps (por defecto: todas)
  -d, --directorio DIR  Carpeta donde se guardan los .deb nuevos
                        (por defecto: la carpeta actual)
  -p, --pool DIR        Pool del repo donde se busca lo ya publicado
                        (por defecto: $POOL); solo lectura
  -n, --comprobar       Solo comprueba qué hay de nuevo; no descarga ni crea nada
  -f, --forzar          Reconstruye aunque el pool ya tenga la última versión
  -l, --limpiar         Borra los .deb antiguos de cada app en la carpeta de
                        salida tras generar el nuevo (nunca toca el pool)
  -k, --conservar       Conserva los .deb originales descargados en la caché
  -r, --repo URL        Lee también las versiones publicadas en tu repo apt
                        (p. ej. https://repo.quirinux.org)
  -s, --suite NOMBRE    Distribución del repo (por defecto: $SUITE)
  -c, --cache DIR       Caché de descargas (por defecto: $CACHE)
      --sin-verificar   No exige SHA256SUMS.txt (no recomendado)
  -h, --ayuda           Esta ayuda

Variables: CRAFT_REV (+q2), CRAFT_MANTENEDOR, CRAFT_ORG, CRAFT_ARCH_UP,
           CRAFT_REPO, CRAFT_SUITE, CRAFT_POOL.
Apps: ${APPS_TODAS[*]}
EOF
}

while (($#)); do
  case $1 in
    -a|--apps)       IFS=, read -r -a APPS <<<"${2:?falta valor}"; shift 2 ;;
    -d|--directorio) DIR=${2:?falta valor}; shift 2 ;;
    -p|--pool)       POOL=${2:?falta valor}; shift 2 ;;
    -c|--cache)      CACHE=${2:?falta valor}; shift 2 ;;
    -r|--repo)       REPO=${2:?falta valor}; shift 2 ;;
    -s|--suite)      SUITE=${2:?falta valor}; shift 2 ;;
    -n|--comprobar)  SOLO_COMPROBAR=1; shift ;;
    -f|--forzar)     FORZAR=1; shift ;;
    -l|--limpiar)    LIMPIAR=1; shift ;;
    -k|--conservar)  CONSERVAR=1; shift ;;
    --sin-verificar) VERIFICAR=0; shift ;;
    -h|--ayuda|--help) ayuda; exit 0 ;;
    *) echo "Opción desconocida: $1" >&2; ayuda >&2; exit 2 ;;
  esac
done
((${#APPS[@]})) || APPS=("${APPS_TODAS[@]}")

# ------------------------------- Utilidades ---------------------------------
if [[ -t 2 ]]; then R=$'\e[31m' G=$'\e[32m' Y=$'\e[33m' B=$'\e[1m' N=$'\e[0m'; else R= G= Y= B= N=; fi
log()  { printf '%s\n' "$*" >&2; }
ok()   { printf '%s✔%s %s\n' "$G" "$N" "$*" >&2; }
aviso(){ printf '%s!%s %s\n' "$Y" "$N" "$*" >&2; }
fallo(){ printf '%s✘%s %s\n' "$R" "$N" "$*" >&2; }

for c in curl gzip dpkg-deb dpkg sha256sum md5sum flock du awk sed; do
  command -v "$c" >/dev/null 2>&1 || { fallo "Falta el programa: $c"; exit 1; }
done

if ((SOLO_COMPROBAR)); then mkdir -p "$CACHE" || exit 1; else
  mkdir -p "$DIR" "$CACHE/orig" "$CACHE/work" || exit 1
fi
DIR=$(cd "$DIR" && pwd) || exit 1
[[ -d $POOL ]] || aviso "No existe el pool: $POOL (solo se mirará la carpeta de salida)"


exec 9>"$CACHE/.lock"
flock -n 9 || { fallo "Ya hay otra instancia de actualizar-craft en marcha."; exit 1; }

TMP_ACTUAL=""
limpiar_tmp() { [[ -n $TMP_ACTUAL && -d $TMP_ACTUAL ]] && rm -rf "$TMP_ACTUAL"; }
trap limpiar_tmp EXIT
trap 'log; aviso "Interrumpido."; exit 130' INT TERM

# Carpeta del pool para un paquete: POOL/craft-<nombre>
carpeta_pool() { printf '%s/craft-%s\n' "$POOL" "$1"; }

# Última tag publicada: sigue /releases/latest hasta /releases/tag/<tag>
version_remota() {
  local url
  url=$(curl -sSIL --retry 3 --retry-delay 2 -m 40 -o /dev/null \
        -w '%{url_effective}' "https://github.com/$ORG/$1/releases/latest") || return 1
  case $url in
    */releases/tag/*) printf '%s\n' "${url##*/}" ;;
    *) return 1 ;;
  esac
}

# Índice Packages del repo apt (se descarga una sola vez por ejecución)
INDICE_REPO=""
cargar_indice_repo() {
  [[ -n $REPO ]] || return 0
  local base="${REPO%/}/dists/$SUITE/$COMPONENTE/binary-$ARCH_DEB" gz
  gz="$CACHE/Packages.$$.gz"
  if curl -fsSL --retry 3 --retry-delay 2 -m 90 -o "$gz" "$base/Packages.gz" \
     && INDICE_REPO=$(gzip -dc "$gz" 2>/dev/null) && [[ -n $INDICE_REPO ]]; then
    rm -f "$gz"; return 0
  fi
  rm -f "$gz"
  fallo "No pude leer el índice del repo: $base/Packages.gz"
  return 1
}

# Versiones de craft-<app> publicadas en el repo (una por línea)
versiones_repo() {
  [[ -n $INDICE_REPO ]] || return 0
  awk -v p="craft-$1" 'BEGIN{RS=""}
    { pk=""; ve=""
      n=split($0, l, "\n")
      for (i=1;i<=n;i++) {
        if (l[i] ~ /^Package: /) pk=substr(l[i],10)
        if (l[i] ~ /^Version: /) ve=substr(l[i],10)
      }
      if (pk==p && ve!="") print ve }' <<<"$INDICE_REPO"
}

# Versión más alta de craft-<app> que ya existe: pool, carpeta de salida y/o repo apt
version_local() {
  local f v best="" carpeta
  carpeta=$(carpeta_pool "$1")
  while IFS= read -r v; do
    [[ -n $v ]] || continue
    if [[ -z $best ]] || dpkg --compare-versions "$v" gt "$best"; then best=$v; fi
  done < <(
    for f in "$carpeta"/craft-"$1"_*.deb "$DIR"/craft-"$1"_*.deb; do
      [[ -e $f ]] && dpkg-deb -f "$f" Version 2>/dev/null
    done
    versiones_repo "$1"
  )
  printf '%s\n' "$best"
}

# Descarga (con reanudación) y verificación SHA-256
descargar_original() {
  local app=$1 tag=$2 asset=$3 destino=$4
  local base="https://github.com/$ORG/$app/releases/download/$tag"
  local part="$destino.part" sums esperado real rc prog=(-sS)
  [[ -t 2 ]] && prog=(--progress-bar)

  if ((VERIFICAR)); then
    sums=$(curl -fsSL --retry 3 -m 60 "$base/SHA256SUMS.txt") \
      || { fallo "$app: no pude bajar SHA256SUMS.txt"; return 1; }
    esperado=$(awk -v f="$asset" '$2==f || $2=="*"f {print $1; exit}' <<<"$sums")
    [[ -n $esperado ]] || { fallo "$app: $asset no figura en SHA256SUMS.txt"; return 1; }
  fi

  if [[ -s $destino ]] && ((VERIFICAR)); then
    real=$(sha256sum "$destino" | cut -d' ' -f1)
    [[ $real == "$esperado" ]] && { log "  (original ya en caché)"; return 0; }
    rm -f "$destino"
  fi

  for intento in 1 2; do
    curl -fL "${prog[@]}" --retry 3 --retry-delay 3 -C - -o "$part" "$base/$asset"
    rc=$?
    if ((rc == 33)); then rm -f "$part"; continue; fi        # rango inválido: reintenta limpio
    ((rc == 0)) || { fallo "$app: falló la descarga (curl $rc)"; return 1; }
    if ((VERIFICAR)); then
      real=$(sha256sum "$part" | cut -d' ' -f1)
      if [[ $real != "$esperado" ]]; then
        rm -f "$part"
        ((intento == 1)) && { aviso "$app: SHA-256 incorrecto, reintentando..."; continue; }
        fallo "$app: SHA-256 no coincide tras reintentar"; return 1
      fi
    fi
    mv -f "$part" "$destino" && return 0
  done
  return 1
}

# Reempaqueta el .deb original con los cambios de Quirinux
reempaquetar() {
  local app=$1 orig=$2 pkg control ver_up ver_base ver_new arch maint prio secc desc
  local campo val ant old size desktop icono out hecho=0 t
  TMP_ACTUAL=$(mktemp -d "$CACHE/work/$app.XXXXXX") || return 1
  pkg="$TMP_ACTUAL/pkg"

  dpkg-deb -R "$orig" "$pkg" || { fallo "$app: no pude extraer el .deb original"; return 1; }

  ver_up=$(dpkg-deb -f "$orig" Version)
  ver_base=${ver_up%-*}                       # 0.8.0-1 -> 0.8.0
  ver_new="${ver_base}${REV}"
  arch=$(dpkg-deb -f "$orig" Architecture)
  maint=$(dpkg-deb -f "$orig" Maintainer)
  prio=$(dpkg-deb -f "$orig" Priority); prio=${prio:-optional}
  secc=${SECCION[$app]:-$(dpkg-deb -f "$orig" Section)}
  desc=${DESC[$app]:-$(dpkg-deb -f "$orig" Description | head -n1)}
  [[ $(dpkg-deb -f "$orig" Package) == "$app" ]] \
    || aviso "$app: el paquete original se llama '$(dpkg-deb -f "$orig" Package)'"

  # --- icono en pixmaps y .desktop apuntando a él ---
  for t in 256x256 128x128 512x512 64x64; do
    icono="$pkg/usr/share/icons/hicolor/$t/apps/ai.storyteller.$app.png"
    [[ -f $icono ]] && break || icono=""
  done
  if [[ -n $icono ]]; then
    install -D -m 0644 "$icono" "$pkg/usr/share/pixmaps/craft-$app.png"
    for desktop in "$pkg"/usr/share/applications/*.desktop; do
      [[ -f $desktop ]] || continue
      sed -i "s|^Icon=.*|Icon=/usr/share/pixmaps/craft-$app.png|" "$desktop"
      hecho=1
    done
  fi
  ((hecho)) || aviso "$app: no encontré icono o .desktop que adaptar"

  # --- Breaks / Replaces ---
  ant=""
  for old in ${ANTERIORES[$app]:-$app}; do ant+="${ant:+, }$old (<< $ver_new)"; done

  # --- md5sums e Installed-Size ---
  ( cd "$pkg" && find . -type f ! -path './DEBIAN/*' -printf '%P\0' | sort -z \
      | xargs -0 -r md5sum > DEBIAN/md5sums )
  size=$(du -sk --exclude=DEBIAN "$pkg" | cut -f1)

  # --- control nuevo (mismo orden que tus paquetes) ---
  control="$pkg/DEBIAN/control"
  {
    echo "Package: craft-$app"
    echo "Version: $ver_new"
    echo "Priority: $prio"
    echo "Section: $secc"
    echo "Architecture: $arch"
    echo "Maintainer: $MANTENEDOR"
    [[ -n $maint ]] && echo "Original-Maintainer: $maint"
    for campo in Pre-Depends Depends Recommends Suggests; do
      val=$(dpkg-deb -f "$orig" "$campo") && [[ -n $val ]] && echo "$campo: $val"
    done
    echo "Breaks: $ant"
    echo "Replaces: $ant"
    echo "Homepage: $(dpkg-deb -f "$orig" Homepage)"
    echo "Description: $desc"
    echo "Installed-Size: $size"
  } > "$control"

  out="$DIR/craft-${app}_${ver_new//[.+]/_}_${arch}.deb"
  dpkg-deb --root-owner-group -Zxz -b "$pkg" "$out.tmp" >/dev/null \
    || { fallo "$app: dpkg-deb no pudo construir el paquete"; rm -f "$out.tmp"; return 1; }
  mv -f "$out.tmp" "$out"
  limpiar_tmp; TMP_ACTUAL=""
  NUEVO_DEB=$out NUEVA_VER=$ver_new
}

# Paquete vacío de transición craft-printcraft -> craft-pdfcraft
crear_transicion_printcraft() {
  local ver=$1 pkg out
  TMP_ACTUAL=$(mktemp -d "$CACHE/work/printcraft.XXXXXX") || return 1
  pkg="$TMP_ACTUAL/pkg"; mkdir -p "$pkg/DEBIAN"
  cat > "$pkg/DEBIAN/control" <<EOF
Package: craft-printcraft
Version: $ver
Priority: optional
Section: oldlibs
Architecture: all
Maintainer: $MANTENEDOR
Depends: craft-pdfcraft (>= $ver)
Homepage: https://getartcraft.com/apps/pdfcraft
Description: Paquete de transicion: craft-printcraft ahora es craft-pdfcraft.
 PrintCraft pasa a llamarse PdfCraft. Este paquete vacio solo instala
 craft-pdfcraft y puede eliminarse sin problemas una vez completada la
 actualizacion.
EOF
  out="$DIR/craft-printcraft_${ver//[.+]/_}_all.deb"
  dpkg-deb --root-owner-group -Zxz -b "$pkg" "$out" >/dev/null \
    || { fallo "printcraft: no pude crear el paquete de transición"; return 1; }
  limpiar_tmp; TMP_ACTUAL=""
  ok "craft-printcraft (transición) → ${out##*/}"
}

# Borra los .deb antiguos de un paquete en la carpeta de salida, salvo el recién generado
limpiar_antiguos() {
  local nombre=$1 nuevo=$2 f
  for f in "$DIR"/craft-"$nombre"_*.deb; do
    [[ -e $f && $f != "$nuevo" ]] && { rm -f "$f"; log "  borrado: ${f##*/}"; }
  done
}

# ------------------------------- Proceso ------------------------------------
procesar_app() {
  local app=$1 tag ver_orig candidata local_v asset orig
  if [[ -z ${DESC[$app]:-} ]]; then aviso "$app: no está en la tabla de apps (uso la descripción original)"; fi

  tag=$(version_remota "$app") \
    || { fallo "$app: no pude averiguar la última release en GitHub"; return 1; }
  ver_orig=${tag#v}
  candidata="${ver_orig}${REV}"
  local_v=$(version_local "$app")

  if [[ -n $local_v ]] && ((!FORZAR)) && ! dpkg --compare-versions "$candidata" gt "$local_v"; then
    ok "$app: al día (${local_v})"; return 0
  fi
  if ((SOLO_COMPROBAR)); then
    printf '%s↑%s %s: %s → %s\n' "$B" "$N" "$app" "${local_v:-(no tienes)}" "$candidata" >&2
    return 0
  fi

  log "${B}$app${N}: ${local_v:-sin versión previa} → $candidata"
  asset="${app}-${ver_orig}-linux-${ARCH_UP}.deb"
  orig="$CACHE/orig/$asset"

  descargar_original "$app" "$tag" "$asset" "$orig" || return 1
  NUEVO_DEB="" NUEVA_VER=""
  reempaquetar "$app" "$orig" || return 1
  ok "$app → ${NUEVO_DEB##*/}"

  ((CONSERVAR)) || rm -f "$orig"
  ((LIMPIAR)) && limpiar_antiguos "$app" "$NUEVO_DEB"

  if [[ $app == pdfcraft ]]; then
    crear_transicion_printcraft "$NUEVA_VER" && ((LIMPIAR)) \
      && limpiar_antiguos printcraft "$DIR/craft-printcraft_${NUEVA_VER//[.+]/_}_all.deb"
  fi
  return 0
}

cargar_indice_repo || exit 1

FALLOS=()
for app in "${APPS[@]}"; do
  procesar_app "$app" || FALLOS+=("$app")
done

log
if ((${#FALLOS[@]})); then
  fallo "Fallaron: ${FALLOS[*]}"
  exit 1
fi
ok "Terminado."
exit 0
