#!/bin/sh
SERVICE="comfyui.service"
DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
FILE="$DIR/$SERVICE"

detect_comfyui_dir() {
	for d in \
		"$HOME/AI/ComfyUI" \
		"$HOME/ComfyUI" \
		"/media/$USER/"*/AI/ComfyUI \
		"/media/$USER/"*/ComfyUI \
		"/mnt/"*/AI/ComfyUI \
		"/mnt/"*/ComfyUI
	do
		if [ -f "$d/main.py" ] && [ -d "$d/comfy" ]; then
			echo "$d"
			return 0
		fi
	done
	return 1
}

write_patch() {
	# Self-contained CPU-fallback patch, written to PATCH at install time.
	cat > "$1" <<- 'PYEOF'
	#!/usr/bin/env python3
	"""Idempotently add a CPU fallback to pack.py's pass-1 rotation.

	If the patch is already present (marker comment found), does nothing.
	Otherwise replaces the GPU-only pass-1 rotation block with a try/except
	that retries the same math on CPU when the GPU allocation OOMs.
	"""
	import sys
	from pathlib import Path

	MARK = "# Rotate ALL charts at once: (N,Vmax,A) can exceed VRAM on big meshes"

	OLD = '''    u0, u1 = uvs_pad[..., 0], uvs_pad[..., 1]                            # (N,Vmax)
    BIG = 1e30
    mlo = torch.where(vmask, torch.zeros_like(u0), u0.new_full((), BIG))
    mhi = torch.where(vmask, torch.zeros_like(u0), u0.new_full((), -BIG))
    xr = torch.addcmul(u0[:, :, None] * cos_a, u1[:, :, None], sin_a, value=-1)   # (N,Vmax,A)
    yr = torch.addcmul(u0[:, :, None] * sin_a, u1[:, :, None], cos_a)
    xsp = (xr + mhi[:, :, None]).amax(1) - (xr + mlo[:, :, None]).amin(1)         # (N,A) masked span
    ysp = (yr + mhi[:, :, None]).amax(1) - (yr + mlo[:, :, None]).amin(1)
    ti = (xsp * ysp).argmin(1)                                          # (N,) best angle per chart
    cc, ss = cos_a[ti][:, None], sin_a[ti][:, None]                     # (N,1)
    rx = torch.addcmul(u0 * cc, u1, ss, value=-1)                      # (N,Vmax)
    ry = torch.addcmul(u0 * ss, u1, cc)
    rxmin = (rx + mlo).amin(1)                                          # (N,)
    rxmax = (rx + mhi).amax(1)
    rymin = (ry + mlo).amin(1)
    rymax = (ry + mhi).amax(1)
	'''

	NEW = '''    u0, u1 = uvs_pad[..., 0], uvs_pad[..., 1]                            # (N,Vmax)
    BIG = 1e30
    mlo = torch.where(vmask, torch.zeros_like(u0), u0.new_full((), BIG))
    mhi = torch.where(vmask, torch.zeros_like(u0), u0.new_full((), -BIG))
    # Rotate ALL charts at once: (N,Vmax,A) can exceed VRAM on big meshes (8.7M faces).
    # Retry the whole pass-1 rotation on CPU if the GPU allocation OOMs.
    try:
        xr = torch.addcmul(u0[:, :, None] * cos_a, u1[:, :, None], sin_a, value=-1)   # (N,Vmax,A)
        yr = torch.addcmul(u0[:, :, None] * sin_a, u1[:, :, None], cos_a)
        xsp = (xr + mhi[:, :, None]).amax(1) - (xr + mlo[:, :, None]).amin(1)         # (N,A) masked span
        ysp = (yr + mhi[:, :, None]).amax(1) - (yr + mlo[:, :, None]).amin(1)
        ti = (xsp * ysp).argmin(1)                                          # (N,) best angle per chart
        cc, ss = cos_a[ti][:, None], sin_a[ti][:, None]                     # (N,1)
        rx = torch.addcmul(u0 * cc, u1, ss, value=-1)                      # (N,Vmax)
        ry = torch.addcmul(u0 * ss, u1, cc)
        rxmin = (rx + mlo).amin(1)                                          # (N,)
        rxmax = (rx + mhi).amax(1)
        rymin = (ry + mlo).amin(1)
        rymax = (ry + mhi).amax(1)
    except torch.cuda.OutOfMemoryError:
        # Fallback: run the identical rotation math on CPU, then move results back.
        print(f"[pack.py] pass-1 rotation OOM on GPU, retrying on CPU "
              f"(N={n}, Vmax={vmax}, A={ang.numel()})", file=__import__("sys").stderr)
        u0c, u1c, mloc, mhic, ca, sa = (u0.cpu(), u1.cpu(), mlo.cpu(), mhi.cpu(),
                                        cos_a.cpu(), sin_a.cpu())
        xr = torch.addcmul(u0c[:, :, None] * ca, u1c[:, :, None], sa, value=-1)
        yr = torch.addcmul(u0c[:, :, None] * sa, u1c[:, :, None], ca)
        xsp = (xr + mhic[:, :, None]).amax(1) - (xr + mloc[:, :, None]).amin(1)
        ysp = (yr + mhic[:, :, None]).amax(1) - (yr + mloc[:, :, None]).amin(1)
        ti = (xsp * ysp).argmin(1)
        cc, ss = ca[ti][:, None], sa[ti][:, None]
        rx = torch.addcmul(u0c * cc, u1c, ss, value=-1)
        ry = torch.addcmul(u0c * ss, u1c, cc)
        rxmin = (rx + mloc).amin(1)
        rxmax = (rx + mhic).amax(1)
        rymin = (ry + mloc).amin(1)
        rymax = (ry + mhic).amax(1)
        # Move derived (small) results back to the GPU device.
        device = comfy.model_management.get_torch_device()
        ti = ti.to(device)
        rxmin, rxmax, rymin, rymax = (rxmin.to(device), rxmax.to(device),
                                      rymin.to(device), rymax.to(device))
        rx, ry = rx.to(device), ry.to(device)
	'''


	def main() -> int:
	    args = [a for a in sys.argv[1:] if not a.startswith("--")]
	    revert = "--revert" in sys.argv
	    checkout = "--checkout" in sys.argv
	    target = args[0] if args else \
	        "/media/kano/JOERG_1TB_N/AI/ComfyUI/comfy_extras/mesh3d/uv_unwrap/pack.py"
	    p = Path(target)
	    if checkout:
	        import subprocess
	        r = subprocess.run(["git", "checkout", "--", str(p)], capture_output=True, text=True)
	        print(r.stdout.strip() or "pack.py restored via git checkout.")
	        return r.returncode
	    src = p.read_text()
	    if revert:
	        if MARK not in src:
	            print("pack.py not patched; nothing to revert.")
	            return 0
	        if NEW not in src:
	            print("ERROR: could not locate the patch block in pack.py.", file=sys.stderr)
	            return 1
	        p.write_text(src.replace(NEW, OLD, 1))
	        print("pack.py reverted.")
	        return 0
	    if MARK in src:
	        print("pack.py already patched; nothing to do.")
	        return 0
	    if OLD not in src:
	        print("ERROR: could not locate the pass-1 rotation block in pack.py.", file=sys.stderr)
	        return 1
	    p.write_text(src.replace(OLD, NEW, 1))
	    print("pack.py patched with CPU fallback.")
	    return 0


	if __name__ == "__main__":
	    raise SystemExit(main())
	PYEOF
}

install() {
	MNT="${1:-$(detect_comfyui_dir)}"
	[ -n "$MNT" ] || { echo "ComfyUI directory not found. Specify it as an argument: $0 install /path/to/ComfyUI" >&2; exit 1; }
	PATCH="$MNT/patch_pack.py"
	write_patch "$PATCH"
	chmod +x "$PATCH"

	mkdir -p "$DIR"
	cat > "$FILE" <<- EOF
	[Unit]
	Description=ComfyUI
	After=graphical-session.target
	Requires=graphical-session.target

	[Service]
	ExecStart=/bin/sh -c '\
	  MNT="$MNT" && \
	  cd "\$MNT" && \
	  OPTIONS="--enable-manager" && \
	  OPTIONS="\$OPTIONS --preview-method auto" && \
	  OPTIONS="\$OPTIONS --listen" && \
	  git checkout -- comfy_extras/mesh3d/uv_unwrap/pack.py && \
	  git pull --rebase && \
	  if [ -d venv-linux ]; then python3 -m venv --upgrade venv-linux; else python3 -m venv venv-linux; fi && \
	  . venv-linux/bin/activate && \
	  pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu130 && \
	  pip install -r requirements.txt && \
	  pip install -r manager_requirements.txt && \
	  pip install matrix-nio && \
	  find custom_nodes -mindepth 1 -maxdepth 1 -type d | while read n; do \
	    [ -d "\$n" ] && (cd "\$n" && { [ -d .git ] && git pull --rebase; }; [ -r requirements.txt ] && pip install -r requirements.txt) || true; \
	  done && \
	  python3 "$PATCH" && \
	  exec python main.py \$OPTIONS'
	ExecStop=/bin/sh -c 'cd "$MNT" && git checkout -- comfy_extras/mesh3d/uv_unwrap/pack.py'
	Restart=on-failure
	RestartSec=10s

	[Install]
	WantedBy=default.target
	EOF
	systemctl --user daemon-reload
	systemctl --user enable --now "$SERVICE"
	echo "installed and started $SERVICE"
}

uninstall() {
	systemctl --user disable --now "$SERVICE" 2>/dev/null || true
	rm -f "$FILE"
	systemctl --user daemon-reload
	echo "removed $SERVICE"
}

usage() {
	echo "usage: ${0##*/} [-h] [-u] [-l] [-p PATH]"
	echo "  -h  show this help"
	echo "  -u  disable and remove the systemd service"
	echo "  -l  show the service log"
	echo "  -p  path to ComfyUI directory (auto-detected if omitted)"
}

PATH_ARG=""
while getopts hulp: opt; do
	case $opt in
		h) usage; exit ;;
		u) uninstall; exit ;;
		l) journalctl --user -u "$SERVICE" -f; exit ;;
		p) PATH_ARG="$OPTARG" ;;
	esac
done

install "$PATH_ARG"
