#!/bin/sh
# Assistente do PartyCall para Linux (instalador ONLINE, x86_64).
#
# O pacote deb, rpm ou tar.gz traz SÓ este assistente (alguns KB). Quando o
# usuário o abre, ele baixa o runtime (Electron + auxiliares, ~100 MB) do
# servidor do PartyCall e o instala no diretório DO USUÁRIO:
#
#   ~/.local/share/partycall/runtime/<versão>/   o runtime
#   ~/.local/share/partycall/atual               link para a versão em uso (troca atômica)
#   ~/.local/share/partycall/anterior            link para a versão de antes (reversão)
#   ~/.local/share/partycall/instalacao.env      o que foi instalado (versão, SHA-256, origem)
#
# Nunca roda como root, nunca baixa no postinst/%post e nunca escreve em /opt
# ou /usr: o gerenciador de pacotes controla o assistente; o runtime tem ciclo
# próprio, por usuário.
#
# Uso:  partycall-setup [instalar|atualizar|reparar|reverter|remover|status]
#                       [--sem-interface] [--sim] [--progresso] [--apagar-dados]
#                       [--origem URL] [--abrir-depois [argumentos...]]
#   sem argumento: abre o assistente (janela zenity; sem zenity, num terminal).
#   --progresso: eventos "FASE x", "PROGRESSO lidos total", "PRONTO versão",
#                "ERRO mensagem" na saída padrão (é o que a casca lê ao atualizar).
#
# Só POSIX sh + curl|wget, tar, xz, sha256sum, coreutils. Mensagens em pt-BR.

ASSISTENTE_VERSAO="1.40.0"
ORIGEM_PADRAO="https://201.76.56.201.sslip.io"
GLIBC_MINIMA="2.35"

DADOS="${XDG_DATA_HOME:-$HOME/.local/share}/partycall"
RUNTIME_DIR="$DADOS/runtime"
BAIXADOS="$DADOS/baixados"
ATUAL="$DADOS/atual"
ANTERIOR="$DADOS/anterior"
REGISTRO="$DADOS/instalacao.env"
CONFIG_USUARIO="${XDG_CONFIG_HOME:-$HOME/.config}/PartyCall"
APLICATIVOS="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
ICONES="${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor/512x512/apps"
MARCA_DESKTOP="X-PartyCall-Assistente=true"

AQUI="$(dirname "$(readlink -f "$0")")"
ORIGEM="${PARTYCALL_ORIGEM:-$ORIGEM_PADRAO}"
ACAO=""
SEM_INTERFACE=0
SIM=0
PROGRESSO=0
APAGAR_DADOS=0
ABRIR_DEPOIS=0
TMP=""

# ---------------------------------------------------------------- utilitários

emitir() { printf '%s\n' "$*"; }
falhar() { emitir "ERRO $*"; destravar 2>/dev/null; exit 1; }
tem() { command -v "$1" >/dev/null 2>&1; }

limpar_tmp() { [ -n "$TMP" ] && [ -d "$TMP" ] && rm -rf "$TMP"; TMP=""; destravar 2>/dev/null; }
trap 'limpar_tmp' EXIT
trap 'limpar_tmp; exit 130' INT TERM
trap 'limpar_tmp; exit 141' PIPE

mb() { awk -v b="$1" 'BEGIN { printf "%.0f MB", b / 1048576 }'; }

# 0 se $1 >= $2 (versões separadas por ponto)
versao_ge() { [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n 1)" = "$2" ]; }

ler_registro() { # chave
	[ -f "$REGISTRO" ] || return 0
	sed -n "s/^$1=//p" "$REGISTRO" | head -n 1
}

# Tamanho de um arquivo em bytes (GNU e BSD).
tamanho_de() { stat -c %s "$1" 2>/dev/null || stat -f %z "$1" 2>/dev/null || echo 0; }

# O assistente veio de um pacote (deb/rpm) ou de um tar.gz solto?
empacotado() { case "$AQUI" in /usr/*) return 0 ;; *) return 1 ;; esac; }

# Algum processo está rodando um executável de dentro de $DADOS/runtime?
em_execucao() {
	for _p in /proc/[0-9]*/exe; do
		case "$(readlink "$_p" 2>/dev/null)" in "$RUNTIME_DIR"/*) return 0 ;; esac
	done
	return 1
}

# Uma instância por vez mexendo em ~/.local/share/partycall (duplo clique, casca e
# terminal ao mesmo tempo corromperiam a extração). mkdir é atômico; o pid dentro
# permite reconhecer trava velha de um processo que morreu.
LOCK="$DADOS/.lock"
TRAVADO=0
travar() {
	mkdir -p "$DADOS" 2>/dev/null
	if ! mkdir "$LOCK" 2>/dev/null; then
		_dono="$(cat "$LOCK/pid" 2>/dev/null)"
		if [ -n "$_dono" ] && kill -0 "$_dono" 2>/dev/null; then
			falhar "Outra instalação do PartyCall está em andamento (processo $_dono). Espere ela terminar."
		fi
		rm -rf "$LOCK"; mkdir "$LOCK" 2>/dev/null || falhar "Não consegui travar $DADOS."
	fi
	echo "$$" > "$LOCK/pid"
	TRAVADO=1
}
destravar() { [ "$TRAVADO" = 1 ] && rm -rf "$LOCK"; TRAVADO=0; }

# ------------------------------------------------------------ verificações

verificar_ambiente() {
	[ "$(id -u)" != "0" ] || falhar "Não rode o assistente como administrador: o PartyCall é instalado na pasta do seu usuário."
	[ "$(uname -m)" = "x86_64" ] || falhar "Este computador é $(uname -m). O PartyCall para Linux só existe para x86_64 (64 bits Intel/AMD); nenhum outro pacote é oferecido no lugar."
	GLIBC="$(getconf GNU_LIBC_VERSION 2>/dev/null | awk '{print $2}')"
	[ -n "$GLIBC" ] || falhar "Não encontrei a glibc: o PartyCall precisa de uma distribuição com glibc (Ubuntu, Debian, Fedora e derivadas), não de musl (Alpine)."
	versao_ge "$GLIBC" "$GLIBC_MINIMA" || falhar "A glibc deste sistema é $GLIBC; o PartyCall precisa da $GLIBC_MINIMA ou mais nova (Ubuntu 22.04, Debian 12, Fedora 36 ou mais novos)."
	tem tar || falhar "Falta o comando tar."
	tem xz || falhar "Falta o xz (pacote xz-utils no Debian e Ubuntu, xz no Fedora)."
	tem sha256sum || falhar "Falta o sha256sum (pacote coreutils)."
	if ! tem curl && ! tem wget; then falhar "Falta curl ou wget para baixar o PartyCall."; fi
	case "$ORIGEM" in
		https://*) ;;
		http://127.0.0.1*|http://localhost*) ;;
		*) falhar "A origem do download precisa ser HTTPS: $ORIGEM" ;;
	esac
}

# ------------------------------------------------------------------- rede

baixar_texto() { # url
	if tem curl; then
		curl -fsSL --connect-timeout 20 --max-time 60 "$1"
	else
		wget -q -O - --timeout=30 "$1"
	fi
}

# Baixa $1 para $2 com retomada. Emite PROGRESSO enquanto baixa.
baixar_arquivo() { # url destino total
	_url="$1"; _dest="$2"; _total="$3"
	if [ "$(tamanho_de "$_dest")" = "$_total" ]; then
		emitir "PROGRESSO $_total $_total"
		return 0
	fi
	if tem curl; then
		curl -fL --retry 3 --connect-timeout 20 -C - -o "$_dest" "$_url" 2>/dev/null &
	else
		wget -c -q -O "$_dest" "$_url" &
	fi
	_pid=$!
	trap 'kill "$_pid" 2>/dev/null; limpar_tmp; exit 141' PIPE
	trap 'kill "$_pid" 2>/dev/null; limpar_tmp; exit 130' INT TERM
	while kill -0 "$_pid" 2>/dev/null; do
		emitir "PROGRESSO $(tamanho_de "$_dest") $_total"
		sleep 0.4
	done
	wait "$_pid"; _rc=$?
	trap 'limpar_tmp; exit 141' PIPE
	trap 'limpar_tmp; exit 130' INT TERM
	emitir "PROGRESSO $(tamanho_de "$_dest") $_total"
	return $_rc
}

# ---------------------------------------------------- núcleo: instalar/atualizar

# Lê o descritor do runtime no servidor para as variáveis RT_*.
descritor_do_runtime() {
	emitir "FASE verificando"
	_txt="$(baixar_texto "$ORIGEM/api/distribuicao/linux/x64/runtime?fmt=txt")" \
		|| falhar "Não consegui falar com o servidor do PartyCall ($ORIGEM). Confira a internet e o proxy e tente de novo."
	RT_VERSAO="$(printf '%s\n' "$_txt" | sed -n 's/^versao=//p' | head -n 1)"
	RT_URL="$(printf '%s\n' "$_txt" | sed -n 's/^url=//p' | head -n 1)"
	RT_TAMANHO="$(printf '%s\n' "$_txt" | sed -n 's/^tamanho=//p' | head -n 1)"
	RT_INSTALADO="$(printf '%s\n' "$_txt" | sed -n 's/^instalado=//p' | head -n 1)"
	RT_SHA="$(printf '%s\n' "$_txt" | sed -n 's/^sha256=//p' | head -n 1)"
	RT_ARQUIVO="$(printf '%s\n' "$_txt" | sed -n 's/^arquivo=//p' | head -n 1)"
	# Só aceita o que tem a forma esperada. A URL é montada com a ORIGEM daqui:
	# o servidor não escolhe outro host.
	case "$RT_VERSAO" in [0-9]*.[0-9]*) ;; *) falhar "O servidor não tem o runtime do Linux publicado (versão inválida)." ;; esac
	case "$RT_URL" in /distribuicao/arquivos/PartyCall-*) ;; *) falhar "Resposta do servidor com endereço inesperado." ;; esac
	case "$RT_ARQUIVO" in PartyCall-*-linux-x64-runtime.tar.xz) ;; *) falhar "Resposta do servidor com arquivo inesperado: $RT_ARQUIVO" ;; esac
	printf '%s' "$RT_SHA" | grep -Eq '^[0-9a-f]{64}$' || falhar "Resposta do servidor sem SHA-256 válido."
	case "$RT_TAMANHO" in ''|*[!0-9]*) falhar "Resposta do servidor sem tamanho válido." ;; esac
	case "$RT_INSTALADO" in *[!0-9]*) RT_INSTALADO="" ;; esac
	[ -n "$RT_INSTALADO" ] || RT_INSTALADO=$((RT_TAMANHO * 33 / 10))
}

verificar_espaco() {
	mkdir -p "$DADOS" || falhar "Não consegui criar $DADOS."
	# Durante a instalação coexistem o .tar.xz e a pasta extraída; depois resta só a pasta.
	_precisa=$((RT_TAMANHO + RT_INSTALADO))
	_livre_kb="$(df -Pk "$DADOS" | awk 'NR==2 {print $4}')"
	_livre=$((_livre_kb * 1024))
	if [ "$_livre" -lt "$_precisa" ]; then
		falhar "Pouco espaço em disco: preciso de $(mb "$_precisa") livres em $DADOS (download $(mb "$RT_TAMANHO") mais $(mb "$RT_INSTALADO") instalado) e há $(mb "$_livre")."
	fi
}

dicas_de_pacote() {
	if tem apt-get; then
		emitir "DICA Instale as bibliotecas com: sudo apt install libgtk-3-0 libnss3 libasound2 libgbm1 libdrm2 libxkbcommon0 libatk-bridge2.0-0 libcups2 libxtst6 (no Ubuntu 24.04 use libgtk-3-0t64 e libasound2t64)."
	elif tem dnf; then
		emitir "DICA Instale as bibliotecas com: sudo dnf install gtk3 nss alsa-lib mesa-libgbm libdrm libxkbcommon at-spi2-core cups-libs libXtst"
	fi
}

nucleo_instalar() { # modo: instalar|atualizar|reparar
	_modo="$1"
	verificar_ambiente
	travar
	descritor_do_runtime
	_instalada="$(ler_registro VERSAO)"
	_sha_instalado="$(ler_registro SHA256)"
	if [ "$_modo" = "atualizar" ] && [ "$_instalada" = "$RT_VERSAO" ] && [ "$_sha_instalado" = "$RT_SHA" ] && [ -x "$ATUAL/partycall" ]; then
		emitir "JA_ATUALIZADO $RT_VERSAO"
		return 0
	fi
	verificar_espaco
	mkdir -p "$BAIXADOS" "$RUNTIME_DIR" || falhar "Não consegui criar as pastas em $DADOS."
	_parcial="$BAIXADOS/$RT_ARQUIVO.parcial"
	emitir "FASE baixando"
	baixar_arquivo "$ORIGEM$RT_URL" "$_parcial" "$RT_TAMANHO" \
		|| falhar "O download foi interrompido. Rode o assistente de novo: ele continua de onde parou."
	emitir "FASE conferindo"
	_hash="$(sha256sum "$_parcial" | awk '{print $1}')"
	if [ "$_hash" != "$RT_SHA" ]; then
		rm -f "$_parcial"   # arquivo nosso, corrompido ou trocado: não serve nem para retomar
		falhar "O arquivo baixado não confere com o SHA-256 que o servidor publicou. Nada foi instalado; tente de novo."
	fi
	emitir "FASE extraindo"
	_novo="$RUNTIME_DIR/$RT_VERSAO.novo"
	rm -rf "$_novo"
	mkdir -p "$_novo"
	if ! tar -xJf "$_parcial" -C "$_novo"; then
		rm -rf "$_novo"
		falhar "Não consegui extrair o pacote (disco cheio ou arquivo ruim). Nada foi instalado."
	fi
	[ -x "$_novo/partycall" ] || { rm -rf "$_novo"; falhar "O pacote não contém o PartyCall. Nada foi instalado."; }
	grep -q "\"versao\": \"$RT_VERSAO\"" "$_novo/PARTYCALL-RUNTIME.json" 2>/dev/null \
		|| { rm -rf "$_novo"; falhar "A versão dentro do pacote não é a anunciada ($RT_VERSAO). Nada foi instalado."; }
	if tem ldd; then
		_faltam="$(ldd "$_novo/partycall" 2>/dev/null | awk '/not found/ {print $1}' | sort -u | tr '\n' ' ')"
		if [ -n "$_faltam" ]; then
			rm -rf "$_novo"
			emitir "FALTAM $_faltam"
			dicas_de_pacote
			falhar "Faltam bibliotecas do sistema para o PartyCall abrir: $_faltam"
		fi
	fi
	# Auxiliares nativos (captura, áudio, atalhos): se faltar biblioteca neles o PartyCall
	# abre e funciona, mas a live cai no modo compatível do navegador. Avisa, não bloqueia.
	if tem ldd; then
		_faltam_aux=""
		for _aux in captura-tela audio-janela atalhos; do
			_f="$(ldd "$_novo/resources/app/$_aux" 2>/dev/null | awk '/not found/ {print $1}' | sort -u | tr '\n' ' ')"
			[ -n "$_f" ] && _faltam_aux="$_faltam_aux$_aux: $_f"
		done
		[ -n "$_faltam_aux" ] && emitir "AVISO Faltam bibliotecas dos recursos nativos ($_faltam_aux). O PartyCall funciona, mas a captura nativa de tela ou som fica no modo compatível. No Debian e Ubuntu: sudo apt install libpipewire-0.3-0; no Fedora: sudo dnf install pipewire-libs."
	fi
	# O runtime precisa executar neste sistema e dizer a versão do Electron que o
	# pacote declara ANTES de virar o "atual". ELECTRON_RUN_AS_NODE roda o binário
	# como Node: sem tela, sem sandbox, mas já exige o carregador e as bibliotecas.
	_eletron_decl="$(sed -n 's/.*"electron": *"\([^"]*\)".*/\1/p' "$_novo/PARTYCALL-RUNTIME.json" | head -n 1)"
	_eletron_real="$(ELECTRON_RUN_AS_NODE=1 "$_novo/partycall" -p 'process.versions.electron' 2>/dev/null | head -n 1)"
	if [ -z "$_eletron_real" ] || [ "$_eletron_real" != "$_eletron_decl" ]; then
		rm -rf "$_novo"
		falhar "O runtime baixado não executa neste sistema (Electron esperado $_eletron_decl, obtido '${_eletron_real:-nada}'). A versão que você já tinha foi mantida."
	fi
	emitir "FASE ativando"
	_destino="$RUNTIME_DIR/$RT_VERSAO"
	if [ -d "$_destino" ]; then mv "$_destino" "$_destino.antigo.$$"; fi
	mv "$_novo" "$_destino"
	_velho=""
	[ -L "$ATUAL" ] && _velho="$(readlink "$ATUAL")"
	ln -sfn "runtime/$RT_VERSAO" "$DADOS/atual.novo"
	mv -T "$DADOS/atual.novo" "$ATUAL"
	if [ -n "$_velho" ] && [ "$_velho" != "runtime/$RT_VERSAO" ]; then
		ln -sfn "$_velho" "$DADOS/anterior.novo"
		mv -T "$DADOS/anterior.novo" "$ANTERIOR"
	fi
	rm -rf "$_destino.antigo.$$"
	gravar_registro
	podar_versoes
	rm -f "$_parcial"
	integrar_no_menu
	emitir "PRONTO $RT_VERSAO"
}

gravar_registro() {
	_ant=""
	[ -L "$ANTERIOR" ] && _ant="$(basename "$(readlink "$ANTERIOR")")"
	{
		echo "VERSAO=$RT_VERSAO"
		echo "ARQUITETURA=x64"
		echo "SHA256=$RT_SHA"
		echo "ORIGEM=$ORIGEM"
		echo "MODO=assistente"
		echo "ANTERIOR=$_ant"
		echo "INSTALADO_EM=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
	} > "$REGISTRO.novo" && mv "$REGISTRO.novo" "$REGISTRO"
}

# Guarda a versão em uso e a anterior (para reverter); apaga as mais velhas.
podar_versoes() {
	_em_uso="$(basename "$(readlink "$ATUAL" 2>/dev/null)")"
	_ant="$(basename "$(readlink "$ANTERIOR" 2>/dev/null)")"
	for d in "$RUNTIME_DIR"/*; do
		[ -d "$d" ] || continue
		n="$(basename "$d")"
		case "$n" in *.novo|*.antigo.*) rm -rf "$d"; continue ;; esac
		if [ "$n" != "$_em_uso" ] && [ "$n" != "$_ant" ]; then rm -rf "$d"; fi
	done
}

# Menu e partycall:// no nível do usuário. Pacote deb/rpm já instala o .desktop
# do sistema; só o tar.gz precisa disto.
integrar_no_menu() {
	empacotado && return 0
	mkdir -p "$DADOS/bin" "$APLICATIVOS" "$ICONES" || return 0
	cp -f "$AQUI/partycall-setup" "$AQUI/partycall" "$DADOS/bin/" 2>/dev/null
	chmod 755 "$DADOS/bin/partycall-setup" "$DADOS/bin/partycall" 2>/dev/null
	[ -f "$ATUAL/resources/app/icone.png" ] && cp -f "$ATUAL/resources/app/icone.png" "$ICONES/partycall.png"
	cat > "$APLICATIVOS/partycall.desktop.novo" <<EOF2
[Desktop Entry]
Type=Application
Name=PartyCall
Comment=Voz, tela e chat entre amigos
Exec="$DADOS/bin/partycall" %U
Icon=partycall
Terminal=false
Categories=Network;Chat;AudioVideo;
MimeType=x-scheme-handler/partycall;
StartupWMClass=PartyCall
Actions=Reparar;Remover;
$MARCA_DESKTOP

[Desktop Action Reparar]
Name=Atualizar ou reparar o PartyCall
Exec="$DADOS/bin/partycall-setup" reparar

[Desktop Action Remover]
Name=Remover o PartyCall deste usuário
Exec="$DADOS/bin/partycall-setup" remover
EOF2
	mv "$APLICATIVOS/partycall.desktop.novo" "$APLICATIVOS/partycall.desktop"
	if tem update-desktop-database; then update-desktop-database "$APLICATIVOS" >/dev/null 2>&1; fi
	if tem xdg-mime; then xdg-mime default partycall.desktop x-scheme-handler/partycall >/dev/null 2>&1; fi
	return 0
}

nucleo_reverter() {
	travar
	if [ ! -L "$ANTERIOR" ] || [ ! -d "$DADOS/$(readlink "$ANTERIOR")" ]; then falhar "Não há versão anterior guardada."; fi
	_para="$(readlink "$ANTERIOR")"
	_de="$(readlink "$ATUAL")"
	ln -sfn "$_para" "$DADOS/atual.novo" && mv -T "$DADOS/atual.novo" "$ATUAL"
	ln -sfn "$_de" "$DADOS/anterior.novo" && mv -T "$DADOS/anterior.novo" "$ANTERIOR"
	_v="$(basename "$_para")"
	{
		echo "VERSAO=$_v"; echo "ARQUITETURA=x64"; echo "SHA256="; echo "ORIGEM=$(ler_registro ORIGEM)"
		echo "MODO=assistente"; echo "ANTERIOR=$(basename "$_de")"; echo "INSTALADO_EM=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
	} > "$REGISTRO.novo" && mv "$REGISTRO.novo" "$REGISTRO"
	emitir "PRONTO $_v"
}

# remover: nunca mexe fora de $DADOS, do .desktop/ícone que o próprio assistente
# gravou e, SÓ com --apagar-dados, da configuração do usuário. Replays e
# qualquer arquivo seu em ~/Vídeos ficam sempre.
remover_seguro() { # diretório que precisa estar sob $HOME
	case "$1" in "$HOME"/?*) rm -rf "$1" ;; *) falhar "Recusei apagar fora da sua pasta: $1" ;; esac
}

nucleo_remover() {
	travar
	if em_execucao; then
		falhar "O PartyCall está aberto. Feche-o (menu da bandeja, Sair) e rode de novo."
	fi
	[ -d "$DADOS" ] && remover_seguro "$DADOS"
	if grep -q "$MARCA_DESKTOP" "$APLICATIVOS/partycall.desktop" 2>/dev/null; then
		rm -f "$APLICATIVOS/partycall.desktop" "$ICONES/partycall.png"
		if tem update-desktop-database; then update-desktop-database "$APLICATIVOS" >/dev/null 2>&1; fi
	fi
	if [ "$APAGAR_DADOS" = 1 ] && [ -d "$CONFIG_USUARIO" ]; then
		remover_seguro "$CONFIG_USUARIO"
		emitir "PRONTO removido-com-dados"
	else
		emitir "PRONTO removido"
	fi
}

nucleo_status() {
	if [ -x "$ATUAL/partycall" ]; then
		echo "instalado=sim"
		echo "versao=$(ler_registro VERSAO)"
		echo "anterior=$(ler_registro ANTERIOR)"
		echo "origem=$(ler_registro ORIGEM)"
		echo "pasta=$DADOS"
	else
		echo "instalado=nao"
		echo "pasta=$DADOS"
	fi
	echo "assistente=$ASSISTENTE_VERSAO"
	echo "configuracao=$CONFIG_USUARIO"
}

# ------------------------------------------------------------------ interfaces

GUI=""
escolher_interface() {
	GUI=""
	if [ "$SEM_INTERFACE" = 1 ] || [ "$PROGRESSO" = 1 ]; then return; fi
	if [ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ] && tem zenity; then GUI="zenity"; fi
}

# Sem zenity e sem terminal à vista (clique no menu): reabre dentro de um terminal,
# com os MESMOS argumentos. Só no modo interativo; --sem-interface e --progresso
# (scripts, a casca) nunca abrem janela.
reabrir_em_terminal() {
	[ "$SEM_INTERFACE" = 1 ] && return 0
	[ "$PROGRESSO" = 1 ] && return 0
	[ -t 0 ] && return 0
	[ -n "${PARTYCALL_EM_TERMINAL:-}" ] && return 0
	[ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ] || return 0
	for t in x-terminal-emulator gnome-terminal konsole xfce4-terminal mate-terminal xterm; do
		if tem "$t"; then
			export PARTYCALL_EM_TERMINAL=1
			eval "set -- $ARGS_ORIGINAIS"
			case "$t" in
				gnome-terminal|mate-terminal) exec "$t" -- "$0" "$@" ;;
				*) exec "$t" -e "$0" "$@" ;;
			esac
		fi
	done
	return 0
}

pergunta() { # texto -> 0 sim, 1 não
	[ "$SIM" = 1 ] && return 0
	if [ "$GUI" = zenity ]; then
		zenity --question --no-markup --width=480 --title="PartyCall" --text="$1" --ok-label="Continuar" --cancel-label="Cancelar"
		return $?
	fi
	printf '%s\n[s/N] ' "$1" >&2
	read -r _r || return 1
	case "$_r" in s|S|sim|Sim|SIM|y|Y) return 0 ;; *) return 1 ;; esac
}

aviso() { # texto
	if [ "$GUI" = zenity ]; then zenity --info --no-markup --width=480 --title="PartyCall" --text="$1" 2>/dev/null
	else printf '%s\n' "$1" >&2; fi
}

aviso_erro() {
	if [ "$GUI" = zenity ]; then zenity --error --no-markup --width=480 --title="PartyCall" --text="$1" 2>/dev/null
	else printf 'ERRO: %s\n' "$1" >&2; fi
}

texto_da_fase() {
	case "$1" in
		verificando) echo "Verificando o servidor do PartyCall..." ;;
		baixando) echo "Baixando o PartyCall..." ;;
		conferindo) echo "Conferindo o arquivo baixado (SHA-256)..." ;;
		extraindo) echo "Instalando..." ;;
		ativando) echo "Finalizando..." ;;
		*) echo "$1" ;;
	esac
}

# Consome os eventos do núcleo e desenha. Guarda o texto do erro em $TMP/erro.
consumir_eventos() {
	_fase=""
	while IFS= read -r linha; do
		case "$linha" in
			FASE*) _fase="${linha#FASE }" ;;
			ERRO*) printf '%s\n' "${linha#ERRO }" > "$TMP/erro" ;;
			AVISO*) printf '%s\n' "${linha#AVISO }" > "$TMP/aviso" ;;
		esac
		if [ "$GUI" = zenity ]; then
			case "$linha" in
				FASE*) echo "# $(texto_da_fase "$_fase")" ;;
				PROGRESSO*)
					set -- $linha
					if [ "${3:-0}" -gt 0 ] 2>/dev/null; then
						echo "# Baixando o PartyCall: $(mb "$2") de $(mb "$3")"
						echo $(($2 * 100 / $3))
					fi ;;
			esac
		else
			case "$linha" in
				FASE*) printf '\n%s\n' "$(texto_da_fase "$_fase")" >&2 ;;
				PROGRESSO*)
					set -- $linha
					if [ "${3:-0}" -gt 0 ] 2>/dev/null; then
						_p=$(($2 * 100 / $3)); _c=$((_p / 4))
						printf '\r[%-25s] %3d%%  %s de %s ' "$(printf '%*s' "$_c" '' | tr ' ' '#')" "$_p" "$(mb "$2")" "$(mb "$3")" >&2
					fi ;;
				FALTAM*|DICA*|AVISO*|JA_ATUALIZADO*|PRONTO*) printf '%s\n' "$linha" >&2 ;;
			esac
		fi
	done
}

# Roda um núcleo com a interface escolhida e devolve o status dele.
rodar_nucleo() { # núcleo args...
	[ -n "$TMP" ] || TMP="$(mktemp -d)"
	if [ "$PROGRESSO" = 1 ]; then "$@"; return $?; fi
	if [ "$GUI" = zenity ]; then
		( "$@"; _r=$?; destravar; echo $_r > "$TMP/rc" ) 2>"$TMP/stderr" | consumir_eventos | zenity --progress --auto-close --auto-kill \
			--width=480 --title="PartyCall" --text="Preparando..." --percentage=0 2>/dev/null
	else
		( "$@"; _r=$?; destravar; echo $_r > "$TMP/rc" ) 2>"$TMP/stderr" | consumir_eventos
		printf '\n' >&2
	fi
	_rc="$(cat "$TMP/rc" 2>/dev/null || echo 130)"
	if [ "$_rc" != 0 ] && [ -s "$TMP/erro" ]; then aviso_erro "$(cat "$TMP/erro")"
	elif [ "$_rc" = 130 ]; then aviso "Instalação cancelada. O que já foi baixado fica guardado: rode o assistente de novo para continuar."; fi
	return "$_rc"
}

# ------------------------------------------------------------------ comandos

resumo_instalacao() { # imprime o texto da pergunta
	verificar_ambiente
	descritor_do_runtime >/dev/null
	verificar_espaco
	printf 'O PartyCall será instalado na SUA pasta (%s), sem administrador.\n\nDownload agora: %s (versão %s)\nEspaço no disco depois de instalar: %s\nVocê precisa de internet até o fim. O assistente confere o SHA-256 do arquivo antes de instalar.\n\nSuas configurações ficam em %s e não são tocadas.' \
		"$DADOS" "$(mb "$RT_TAMANHO")" "$RT_VERSAO" "$(mb "$RT_INSTALADO")" "$CONFIG_USUARIO"
}

cmd_instalar() { # modo
	_modo="$1"
	if [ "$PROGRESSO" != 1 ] && [ "$_modo" != atualizar ]; then
		_txt="$( (resumo_instalacao) 2>&1 )" || { aviso_erro "$(printf '%s' "$_txt" | sed 's/^ERRO //')"; return 1; }
		pergunta "$_txt" || { aviso "Nada foi instalado."; return 0; }
	fi
	rodar_nucleo nucleo_instalar "$_modo"
	_rc=$?
	if [ "$_rc" = 0 ] && [ "$PROGRESSO" != 1 ] && [ "$ABRIR_DEPOIS" != 1 ]; then
		_extra=""
		[ -s "$TMP/aviso" ] && _extra="

Atenção: $(cat "$TMP/aviso")"
		aviso "PartyCall instalado. Procure PartyCall no menu de aplicativos.$_extra"
	fi
	return "$_rc"
}

cmd_remover() {
	_msg="Remover o PartyCall deste usuário?

Será apagado: $DADOS (o programa baixado)."
	if [ "$APAGAR_DADOS" = 1 ]; then
		_msg="$_msg
Também será apagada a sua configuração e sessão em $CONFIG_USUARIO."
	else
		_msg="$_msg
Sua configuração e sessão em $CONFIG_USUARIO ficam guardadas (reinstalar volta como estava). Vídeos de replay nunca são apagados."
	fi
	if empacotado; then _msg="$_msg

O pacote do assistente (deb ou rpm) continua instalado: remova pelo gerenciador de pacotes."; fi
	pergunta "$_msg" || { aviso "Nada foi removido."; return 0; }
	rodar_nucleo nucleo_remover
}

menu_principal() {
	if [ ! -x "$ATUAL/partycall" ]; then cmd_instalar instalar; return $?; fi
	if [ "$GUI" = zenity ]; then
		_esc="$(zenity --list --radiolist --width=480 --height=300 --title="PartyCall" \
			--text="O PartyCall está instalado (versão $(ler_registro VERSAO)). O que deseja fazer?" \
			--column="" --column="Ação" TRUE "Abrir o PartyCall" FALSE "Atualizar ou reparar" FALSE "Voltar à versão anterior" FALSE "Remover do meu usuário" 2>/dev/null)" || return 0
	else
		printf 'PartyCall %s instalado.\n1) Abrir  2) Atualizar ou reparar  3) Voltar à versão anterior  4) Remover\nEscolha: ' "$(ler_registro VERSAO)" >&2
		read -r _n
		case "$_n" in 1) _esc="Abrir o PartyCall" ;; 2) _esc="Atualizar ou reparar" ;; 3) _esc="Voltar à versão anterior" ;; 4) _esc="Remover do meu usuário" ;; *) return 0 ;; esac
	fi
	case "$_esc" in
		"Abrir o PartyCall") exec "$AQUI/partycall" ;;
		"Atualizar ou reparar") cmd_instalar reparar ;;
		"Voltar à versão anterior") rodar_nucleo nucleo_reverter ;;
		"Remover do meu usuário") cmd_remover ;;
	esac
}

# ------------------------------------------------------------------ argumentos

# Guarda os argumentos entre aspas, para reabrir no terminal sem perder nenhum.
ARGS_ORIGINAIS=""
for _a in "$@"; do
	ARGS_ORIGINAIS="$ARGS_ORIGINAIS '$(printf '%s' "$_a" | sed "s/'/'\\\\''/g")'"
done

while [ $# -gt 0 ]; do
	case "$1" in
		instalar|atualizar|reparar|reverter|remover|status) ACAO="$1" ;;
		--sem-interface) SEM_INTERFACE=1 ;;
		--sim) SIM=1 ;;
		--progresso) PROGRESSO=1 ;;
		--apagar-dados) APAGAR_DADOS=1 ;;
		--origem) shift; ORIGEM="$1" ;;
		--abrir-depois) ABRIR_DEPOIS=1; shift; break ;;
		--version|--versao) echo "partycall-setup $ASSISTENTE_VERSAO"; exit 0 ;;
		-h|--help|--ajuda) sed -n '2,24p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
		*) echo "Argumento desconhecido: $1 (use --ajuda)" >&2; exit 2 ;;
	esac
	shift
done
ORIGEM="${ORIGEM%/}"

escolher_interface
[ -n "$GUI" ] || reabrir_em_terminal

case "$ACAO" in
	status) nucleo_status ;;
	instalar) cmd_instalar instalar ;;
	atualizar) cmd_instalar atualizar ;;
	reparar) cmd_instalar reparar ;;
	reverter) rodar_nucleo nucleo_reverter ;;
	remover) cmd_remover ;;
	"") menu_principal ;;
esac
_rc=$?
if [ "$_rc" = 0 ] && [ "$ABRIR_DEPOIS" = 1 ] && [ -x "$ATUAL/partycall" ]; then
	exec "$AQUI/partycall" "$@"
fi
exit "$_rc"
