#!/bin/sh
# Copyright (c) 2026 Jiaxing Hu <gahing@gahingwoo.com>
# SPDX-License-Identifier: GPL-2.0
# `charsiu update`. Not meant to be run directly; charsiu dispatches to it.
#
# Bring the source up to date and reinstall from it. The installer already
# knows how to build and where things go, so this does not reimplement any of
# that: it updates the tree and hands over.
#
# THE KERNEL AND THE MODEL ARE NOT TOUCHED. Updating the runtime should not
# reboot the board or spend an hour of somebody's link, so --no-kernel and
# --no-model are not optional here.
set -eu
CHARSIU_HERE=$(dirname "$0")
for _l in ${CHARSIU_LIB:+"$CHARSIU_LIB/charsiu-lib.sh"} \
	  "$CHARSIU_HERE/charsiu-lib.sh" \
	  "$CHARSIU_HERE/../scripts/charsiu-lib.sh" \
	  "$CHARSIU_HERE/../../opt/charsiu/charsiu-lib.sh" \
	  /opt/charsiu/charsiu-lib.sh; do
	[ -r "$_l" ] && { . "$_l"; break; }
done
command -v ini_get >/dev/null 2>&1 \
	|| { echo "$(basename "$0"): charsiu-lib.sh not found" >&2; exit 1; }

# A PACKAGED INSTALL IS UPDATED BY THE PACKAGE MANAGER. This script rebuilds
# from source and copies over /opt/charsiu and /usr/bin, which on a .deb
# install would overwrite files dpkg owns and leave its records describing
# files that are no longer there. Say so and stop.
if command -v dpkg-query >/dev/null 2>&1 \
   && dpkg-query -W -f='${Status}' charsiu 2>/dev/null | grep -q 'install ok installed'; then
	echo "$(basename "$0"): charsiu was installed as a package; update it with" >&2
	echo "  sudo apt update && sudo apt install --only-upgrade charsiu" >&2
	exit 1
fi

# TWO CHANNELS, BECAUSE THIS TREE IS A LABORATORY AND A PRODUCT AT ONCE.
#
# `stable` is what somebody who wants to run a model should get: the runtime,
# and the other modalities with it -- charsiu_vision, charsiu_clip and
# charsiu_whisper are read only and touch no NPU register the decode path does
# not. `dev` adds the PROBES -- npu_gemm_test, charsiu_matmul, bench_batch,
# prefill_control.sh -- and tracks the branch the work happens on, where a
# register can be a literal that is wrong at every width but one for a
# fortnight.
#
# Installing dev on somebody's board without them asking is how a tool stops
# being trusted. The channel is remembered, so `charsiu update` after
# `charsiu update dev` stays on dev, and `charsiu update stable` goes back.
#
# --auto (or -y): NO QUESTIONS. An update on a board that has already run a
# model asks exactly one thing on a good day, "ask it something, to see it
# work?", and somebody updating ten times an afternoon has to answer it ten
# times. With --auto the demo is skipped and whatever else the installer would
# ask -- a missing compiler, on a board that has lost one -- answers yes. The
# kernel and the model are still not touched: --no-kernel and --no-model are
# not questions, they are what this command is. `auto = yes` under [update]
# in config.ini makes it the default.
CHANNEL=""
AUTO=""
_n=$#
while [ "$_n" -gt 0 ]; do
	_a=$1; shift; _n=$((_n - 1))
	case "$_a" in
	dev|stable) CHANNEL=$_a ;;
	--auto|-y)  AUTO=1 ;;
	*)          set -- "$@" "$_a" ;;
	esac
done

SRC=""
for d in ${CHARSIU_SRC:+"$CHARSIU_SRC"} \
	 "${XDG_CACHE_HOME:-$HOME/.cache}/charsiu/src" \
	 "$HOME/charsiu" /opt/charsiu/src; do
	[ -f "$d/scripts/charsiu-install.sh" ] && { SRC="$d"; break; }
done
if [ -z "$SRC" ]; then
	echo "charsiu update: no source tree to update." >&2
	echo "  Install again and it will fetch one:" >&2
	echo "  curl -fsSL https://raw.githubusercontent.com/gahingwoo/charsiu/stable/scripts/charsiu-install.sh -o install.sh && sh install.sh" >&2
	exit 1
fi

# charsiu_conf_w, not charsiu_conf: ini_set writes to $CONF, and the readable
# one can be /etc where this has no business writing.
CONF=$(charsiu_conf_w 2>/dev/null || echo "")
if [ -n "$CHANNEL" ]; then
	[ -n "$CONF" ] && ini_set update channel "$CHANNEL"
else
	CHANNEL=$(ini_get update channel 2>/dev/null || echo "")
	: "${CHANNEL:=stable}"
fi
# ONE PLACE WHERE A CHANNEL BECOMES A BRANCH. The installer has the same
# mapping in its bootstrap, and the two drifting apart means `charsiu update`
# and a fresh install land on different code while both say the same word.
case "$CHANNEL" in
dev)    REF=${CHARSIU_DEV_REF:-dev} ;;
stable) REF=stable ;;
*)      echo "charsiu update: no channel called $CHANNEL. Try stable or dev." >&2
	exit 1 ;;
esac

if [ -z "$AUTO" ]; then
	case "$(ini_get update auto 2>/dev/null || echo "")" in
	yes|1|true) AUTO=1 ;;
	esac
fi

was=$(cd "$SRC" && git rev-parse --short HEAD 2>/dev/null || echo "")
echo "  source  $SRC${was:+  ($was)}"
echo "  channel $CHANNEL  ($REF)"
[ -n "$AUTO" ] && echo "  auto    no questions, no demo"

# `git pull` INTO A TREE SOMEBODY EDITED would either fail or merge. This is
# a working copy of ours, not theirs, so --ff-only: if it will not fast forward
# something is going on that a script should not resolve on its own.
if [ -d "$SRC/.git" ] && command -v git >/dev/null 2>&1; then
	# FETCH FIRST, then move to the channel's ref. A plain `git pull`
	# would follow whatever branch the tree happens to be on, which is how
	# somebody who asked for stable stays on dev without being told.
	( cd "$SRC" && git fetch --quiet origin "$REF" ) || {
		echo "charsiu update: could not fetch $REF. Nothing was changed." >&2
		exit 1; }
	if ! ( cd "$SRC" && git checkout --quiet -B "$REF" FETCH_HEAD ); then
		echo "charsiu update: $SRC will not move to $REF." >&2
		echo "  It has local changes. Sort those out, or delete it" >&2
		echo "  and install again." >&2
		exit 1
	fi
else
	# no git, or a tarball checkout: take a fresh tarball over the top
	echo "  fetching a fresh tarball"
	T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
	if command -v curl >/dev/null 2>&1; then
		curl -fsSL "${CHARSIU_SRC_REPO:-https://github.com/gahingwoo/charsiu}/archive/refs/heads/$REF.tar.gz" -o "$T/s.tgz"
	else
		wget -qO "$T/s.tgz" "${CHARSIU_SRC_REPO:-https://github.com/gahingwoo/charsiu}/archive/refs/heads/$REF.tar.gz"
	fi || { echo "charsiu update: the download failed. Nothing was changed." >&2; exit 1; }
	tar -xzf "$T/s.tgz" -C "$SRC" --strip-components=1
fi

now=$(cd "$SRC" && git rev-parse --short HEAD 2>/dev/null || echo "")
if [ -n "$was" ] && [ "$was" = "$now" ]; then
	echo "  already up to date ($now)"
else
	echo "  updated${now:+ to $now}"
fi

# HAND OVER RATHER THAN REBUILD HERE. The installer is the one place that
# knows how to build, where the binaries go, and how to ask for root once.
[ -n "$AUTO" ] && set -- --yes --no-demo "$@"
exec sh "$SRC/scripts/charsiu-install.sh" --no-kernel --no-model \
	--channel "$CHANNEL" "$@"
