#!/bin/bash
# WillyCont · lanzador de Mac (WillyCont.app/Contents/MacOS/WillyCont)
# - Si el servidor de WillyCont ya está en marcha (<DATOS>/servidor.json + /api/sistema/estado + su pid es node), solo abre la ventana.
# - Si no, arranca Resources/runtime/bin/node Resources/app/tools/arrancar.mjs en segundo plano
#   (registro en <DATOS>/registro.txt), espera a que responda (máx. 30 s) y abre la ventana:
#   Chrome o Edge en modo aplicación; si no hay, el navegador por defecto.
# - Si no arranca, enseña la línea «MOTIVO:» (en español) que deja arrancar.mjs en el registro.
# - Comprueba antes que el node del paquete funciona en este Mac (Intel / chip Apple) y lo explica si no.
# - Deja en <DATOS> una copia de «Cerrar WillyCont» y «Restablecer contraseña» (sin la marca de descarga),
#   para que no se pierdan al borrar la carpeta descargada.
# Opciones: --cerrar (para el servidor) · --sin-ventana (pruebas: no abre ventanas ni diálogos; el servidor recibe
#           WILLYCONT_SIN_VENTANAS=1 y no escribe en los Documentos reales)
# Variables: WILLYCONT_DATOS (carpeta de datos), WILLYCONT_PUERTO (puerto inicial)
CONTENTS="$(cd "$(dirname "$0")/.." && pwd)"
RES="$CONTENTS/Resources"
NODE="$RES/runtime/bin/node"
SCRIPT="$RES/app/tools/arrancar.mjs"
DATOS="${WILLYCONT_DATOS:-$HOME/WillyCont}"
[ -n "$WILLYCONT_DATOS" ] && export WILLYCONT_DATOS="$DATOS"
SIN_VENTANA=0; CERRAR=0
for a in "$@"; do
  case "$a" in
    --sin-ventana) SIN_VENTANA=1 ;;
    --cerrar) CERRAR=1 ;;
  esac
done
[ "$SIN_VENTANA" = 1 ] && export WILLYCONT_SIN_VENTANAS=1

aviso() {
  if [ "$SIN_VENTANA" = 1 ]; then echo "$1" >&2; return; fi
  local t="${1//\\/\\\\}"; t="${t//\"/\\\"}"
  /usr/bin/osascript -e "display dialog \"$t\" with title \"WillyCont\" buttons {\"Vale\"} default button 1 with icon caution" >/dev/null 2>&1
}

leer() { # leer puerto|pid
  [ -f "$DATOS/servidor.json" ] || return 0
  /usr/bin/sed -n "s/.*\"$1\" *: *\([0-9][0-9]*\).*/\1/p" "$DATOS/servidor.json" | head -n 1
}

leer_nonce() {
  [ -f "$DATOS/servidor.json" ] || return 0
  /usr/bin/sed -n 's/.*"nonce" *: *"\([A-Za-z0-9_-]*\)".*/\1/p' "$DATOS/servidor.json" | head -n 1
}

# ¿responde WillyCont en ese puerto? Con un reto: el servidor devuelve HMAC-SHA256(nonce de servidor.json, reto),
# que un programa que se haga pasar por WillyCont no sabe calcular.
responde() { # responde <puerto>
  [ -n "$1" ] || return 1
  local reto nonce r esperado
  reto="$(/usr/bin/od -An -N16 -tx1 /dev/urandom | tr -d ' \n')"
  nonce="$(leer_nonce)"
  r="$(/usr/bin/curl -s -m 2 --noproxy '*' "http://127.0.0.1:$1/api/sistema/estado?reto=$reto" 2>/dev/null)"
  echo "$r" | grep -q '"nombre":"WillyCont"' || return 1
  [ -n "$nonce" ] || return 0 # servidor de una versión anterior (sin nonce): basta con que su pid sea node
  esperado="$(printf '%s' "$reto" | /usr/bin/openssl dgst -sha256 -hmac "$nonce" 2>/dev/null | /usr/bin/awk '{print $NF}')"
  [ -n "$esperado" ] && echo "$r" | grep -q "\"prueba\":\"$esperado\""
}

# ¿el proceso que apuntó servidor.json sigue vivo y es node? (si el Mac se apagó sin cerrar WillyCont, servidor.json
# se queda viejo y otro programa podría estar en ese puerto)
es_nuestro() {
  [ -n "$1" ] || return 1
  kill -0 "$1" 2>/dev/null || return 1
  /bin/ps -p "$1" -o comm= 2>/dev/null | grep -qi 'node'
}

vivo() { # escribe el puerto si WillyCont ya está en marcha
  local p pid
  p="$(leer puerto)"; pid="$(leer pid)"
  es_nuestro "$pid" && responde "$p" && echo "$p"
}

motivo() { # la última línea «MOTIVO:» del último arranque
  [ -f "$DATOS/registro.txt" ] || return 0
  /usr/bin/awk '/===== WillyCont arrancando/{m=""} /MOTIVO: /{sub(/.*MOTIVO: /,""); m=$0} END{print m}' "$DATOS/registro.txt"
}

abrir_ventana() {
  local url="http://127.0.0.1:$1/"
  [ "$SIN_VENTANA" = 1 ] && return 0
  local nav=""
  for c in "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
           "$HOME/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
           "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" \
           "$HOME/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"; do
    if [ -x "$c" ]; then nav="$c"; break; fi
  done
  if [ -n "$nav" ]; then
    nohup "$nav" --app="$url" --user-data-dir="$DATOS/ventana" --no-first-run --no-default-browser-check --window-size=1360,900 >/dev/null 2>&1 &
    disown 2>/dev/null
  else
    /usr/bin/open "$url"
  fi
}

cerrar() {
  local puerto pid
  puerto="$(leer puerto)"; pid="$(leer pid)"
  if ! responde "$puerto"; then
    [ "$SIN_VENTANA" = 1 ] || /usr/bin/osascript -e 'display notification "WillyCont ya estaba cerrado." with title "WillyCont"' >/dev/null 2>&1
    return 0
  fi
  touch "$DATOS/parar"
  for _ in $(seq 1 32); do
    responde "$puerto" || break
    sleep 0.25
  done
  if responde "$puerto" && es_nuestro "$pid"; then kill "$pid" 2>/dev/null; sleep 1; fi
  rm -f "$DATOS/parar"
  [ "$SIN_VENTANA" = 1 ] || /usr/bin/osascript -e 'display notification "WillyCont se ha cerrado." with title "WillyCont"' >/dev/null 2>&1
  return 0
}

# el node del paquete tiene que poder ejecutarse en este Mac (Intel o chip Apple)
comprobar_node() {
  "$NODE" -e 0 >/dev/null 2>&1 && return 0
  local mac paquete
  mac="$(/usr/bin/uname -m)"
  paquete="$(/usr/bin/file -b "$NODE" 2>/dev/null)"
  if [ "$mac" = "x86_64" ] && echo "$paquete" | grep -q 'arm64'; then
    aviso "Has descargado WillyCont para Mac con chip Apple, pero tu Mac tiene procesador Intel. Descarga la versión «Mac con Intel» en cont.willypost.com y sustituye esta."
  elif [ "$mac" = "arm64" ] && echo "$paquete" | grep -q 'x86_64'; then
    aviso "Este WillyCont es para Mac con Intel y tu Mac tiene chip Apple. Descarga la versión «Mac con chip Apple» en cont.willypost.com y sustituye esta."
  else
    aviso "macOS no deja arrancar una parte de WillyCont. Ve a Ajustes del Sistema → Privacidad y seguridad; si aparece un aviso de WillyCont o de «node», pulsa «Abrir igualmente» y vuelve a abrir WillyCont."
  fi
  exit 2
}

# copias de «Cerrar WillyCont» y «Restablecer contraseña» en la carpeta de datos (escritas de nuevo: sin marca de descarga)
copiar_ayudas() {
  [ -d "$RES/ayudas" ] || return 0
  local f d
  for f in "$RES/ayudas/"*.command; do
    [ -f "$f" ] || continue
    d="$DATOS/$(basename "$f")"
    if ! cmp -s "$f" "$d" 2>/dev/null; then cat "$f" > "$d" 2>/dev/null && chmod 755 "$d" 2>/dev/null; fi
  done
}

if [ "$CERRAR" = 1 ]; then cerrar; exit $?; fi

if [ ! -x "$NODE" ] || [ ! -f "$SCRIPT" ]; then
  aviso "Falta parte del programa. Descomprime el archivo .zip entero, arrastra WillyCont a Aplicaciones y ábrelo desde allí."
  exit 2
fi
mkdir -p "$DATOS"
comprobar_node
[ "$SIN_VENTANA" = 1 ] || copiar_ayudas

# abierto sin moverlo a Aplicaciones: macOS lo ejecuta desde una carpeta temporal que cambia cada vez
case "$CONTENTS" in
  */AppTranslocation/*) aviso "Para que WillyCont funcione bien (también con Claude), arrástralo a la carpeta Aplicaciones y ábrelo desde allí. Esta vez se abre igualmente." ;;
esac

# una sola copia arrancando a la vez
CERROJO="$DATOS/.lanzador.lock"
for _ in $(seq 1 120); do
  if mkdir "$CERROJO" 2>/dev/null; then trap 'rmdir "$CERROJO" 2>/dev/null' EXIT; break; fi
  # si el cerrojo es de hace más de 2 minutos, es de un arranque que se quedó a medias
  if [ -n "$(find "$CERROJO" -maxdepth 0 -mmin +2 2>/dev/null)" ]; then rmdir "$CERROJO" 2>/dev/null; continue; fi
  sleep 0.25
done

PUERTO="$(vivo)"
if [ -n "$PUERTO" ]; then [ "$SIN_VENTANA" = 1 ] && echo "$PUERTO"; abrir_ventana "$PUERTO"; exit 0; fi

cd "$RES/app" || exit 2
nohup "$NODE" --no-warnings "$SCRIPT" </dev/null >/dev/null 2>&1 &
HIJO=$!

for _ in $(seq 1 120); do
  if ! kill -0 "$HIJO" 2>/dev/null; then
    wait "$HIJO" 2>/dev/null; CODIGO=$?
    if [ "$CODIGO" = 3 ] || [ "$CODIGO" = 0 ]; then
      # ya había otro WillyCont con estos datos (por ejemplo, lo abrió Claude) y el nuevo se ha retirado
      # (cerrojo <DATOS>/servidor.lock): usamos ese
      for _ in $(seq 1 120); do
        PUERTO="$(vivo)"
        if [ -n "$PUERTO" ]; then [ "$SIN_VENTANA" = 1 ] && echo "$PUERTO"; abrir_ventana "$PUERTO"; exit 0; fi
        sleep 0.25
      done
    fi
    M="$(motivo)"
    [ -n "$M" ] || M="Reinicia el Mac y vuelve a abrirlo. Si sigue igual, manda a la academia el archivo registro.txt de la carpeta WillyCont de tu usuario."
    aviso "WillyCont no ha podido arrancar. $M"
    exit 1
  fi
  PUERTO="$(leer puerto)"; PID="$(leer pid)"
  if [ "$PID" = "$HIJO" ] && responde "$PUERTO"; then
    disown "$HIJO" 2>/dev/null
    [ "$SIN_VENTANA" = 1 ] && echo "$PUERTO"
    abrir_ventana "$PUERTO"
    exit 0
  fi
  sleep 0.25
done
kill "$HIJO" 2>/dev/null
aviso "WillyCont tarda demasiado en arrancar (más de 30 segundos). Cierra otros programas y vuelve a intentarlo; si sigue igual, reinicia el Mac."
exit 1
