#!/bin/sh
# wove — natural language sysadmin assistant for SliTaz GNU/Linux
# Usage:
#   wove "installe gimp"      one-shot
#   wove                      interactive REPL
#   wove doctor               health check (deps, schema, engine, model)
#   wove model {list|pull|use|rm|info} [name]
# Env:
#   WOVE_DRY_RUN=1   show resolved command, do not run
#   WOVE_YES=1       skip confirmations (use with care)
#   WOVE_MODEL=...   override active LLM model (name or path)

set -u

# Detect legacy invocation via the tazai symlink
case "$(basename "$0")" in
	tazai)
		echo "Warning: 'tazai' is deprecated, use 'wove' instead. (Will be removed in Wove 1.0)" >&2
		;;
esac

SELF=$(readlink -f "$0" 2>/dev/null || echo "$0")
HERE=$(dirname "$SELF")
ROOT=$(dirname "$HERE")

# Production install wins (covers both real SliTaz boots and the dev
# chroot at ~/.slitaz/x86_64/chroot, where /usr/lib/wove is the truth).
# Local dev tree only kicks in on hosts without a system install — i.e.
# running ./bin/wove from ~/.slitaz/repos/wove on Debian/Linux.
# Override either via WOVE_LIB= / WOVE_SHARE= in the environment.
if [ -r /usr/lib/wove/router.sh ]; then
	WOVE_LIB="${WOVE_LIB:-/usr/lib/wove}"
	WOVE_SHARE="${WOVE_SHARE:-/usr/share/wove}"
elif [ -d "$ROOT/lib" ] && [ -r "$ROOT/lib/router.sh" ]; then
	WOVE_LIB="${WOVE_LIB:-$ROOT/lib}"
	WOVE_SHARE="${WOVE_SHARE:-$ROOT/share}"
else
	WOVE_LIB="${WOVE_LIB:-/usr/lib/wove}"
	WOVE_SHARE="${WOVE_SHARE:-/usr/share/wove}"
fi
export WOVE_SHARE

# shellcheck source=/dev/null
. "$WOVE_LIB/logger.sh"
# shellcheck source=/dev/null
. "$WOVE_LIB/history.sh"
# shellcheck source=/dev/null
. "$WOVE_LIB/config.sh"
# shellcheck source=/dev/null
. "$WOVE_LIB/matcher.sh"
# shellcheck source=/dev/null
. "$WOVE_LIB/picolm-bridge.sh"
# shellcheck source=/dev/null
. "$WOVE_LIB/llama3pure-bridge.sh"
# shellcheck source=/dev/null
. "$WOVE_LIB/llamacpp-bridge.sh"
# shellcheck source=/dev/null
. "$WOVE_LIB/validator.sh"
# shellcheck source=/dev/null
. "$WOVE_LIB/executor.sh"
# Optional: multi-step flow handlers (orchestration on top of Trame).
# Each flow is sourced if present; missing files are tolerated so the
# core wove install can be lean.
for _flow in "$WOVE_LIB"/flows/*.sh; do
	[ -r "$_flow" ] && . "$_flow"
done
unset _flow
# shellcheck source=/dev/null
. "$WOVE_LIB/router.sh"
# shellcheck source=/dev/null
. "$WOVE_LIB/model.sh"
# shellcheck source=/dev/null
. "$WOVE_LIB/render.sh"
# shellcheck source=/dev/null
. "$WOVE_LIB/doctor.sh"
# shellcheck source=/dev/null
. "$WOVE_LIB/api.sh"

VERSION="0.11.0"

if ! command -v jq >/dev/null 2>&1; then
	echo "wove: 'jq' est requis mais introuvable. Installe-le : tazpkg get-install jq" >&2
	exit 127
fi

wove_config_load

# Migration hint: one-time suggestion when old ~/.tazai/ exists
if [ -d "$HOME/.tazai" ] && [ ! -d "$HOME/.wove" ]; then
	echo "Tip: rename ~/.tazai/ to ~/.wove/ — TazAI is now called Wove." >&2
fi

usage() {
	# Bold + dim only when stdout is a TTY (man-page-style headings).
	local B="" D="" R=""
	if [ -t 1 ]; then
		B='\033[1m'; D='\033[2m'; R='\033[0m'
	fi
	printf "${B}wove $VERSION${R} — natural-language sysadmin assistant for SliTaz\n\n"

	printf "${B}USAGE${R}\n"
	printf "  wove \"<request>\"            one-shot: route, confirm, run\n"
	printf "  wove                         interactive REPL (Ctrl-D to exit)\n\n"

	printf "${B}EXAMPLES${R}  ${D}(matcher → fast path, no LLM)${R}\n"
	printf "  wove \"infos système\"                 # → trame system info\n"
	printf "  wove \"renomme la machine en dev01\"   # → trame hostname set dev01\n"
	printf "  wove \"démarre httpd\"                 # → trame service start httpd\n"
	printf "  wove \"active httpd au démarrage\"     # → trame service enable httpd\n"
	printf "  wove \"installe gimp\"                 # → tazpkg get-install gimp\n"
	printf "  wove \"cherche éditeur photo\"         # → search packages.info\n"
	printf "  wove \"sync time\"                     # → trame time ntp\n"
	printf "  wove \"dhcp eth0\"                     # → trame network dhcp eth0\n"
	printf "  wove \"scan wifi\"                     # → trame wifi scan\n"
	printf "  wove \"configure le wifi\"             # → orchestrated flow\n\n"

	printf "${B}SAFETY LEVELS${R}\n"
	printf "  L1  read-only, auto-run                 (df, hostname, wifi scan)\n"
	printf "  L2  mutates state, asks ${B}y/N${R}            (start service, set tz, install)\n"
	printf "  L3  destructive, types ${B}yes${R} + word      (reboot, shutdown)\n\n"

	printf "${B}SUB-COMMANDS${R}\n"
	printf "  wove doctor                  health check (deps, schema, engine, model)\n"
	printf "  wove functions [--category=X] [--level=N]   dump schema as JSON (for TUI/front-ends)\n"
	printf "  wove history [N]             show last N log entries (default 20)\n"
	printf "  wove history --errors        only non-zero exit codes\n"
	printf "  wove history --dry           only dry-run entries\n"
	printf "  wove history --clear         truncate the log (confirms)\n"
	printf "  wove model list              available downloadable models\n"
	printf "  wove model pull <name>       download a model\n"
	printf "  wove model use  <name>       set active model\n"
	printf "  wove model rm   <name>       remove a downloaded model\n"
	printf "  wove model info <name>       model metadata\n\n"

	printf "${B}FLAGS${R}\n"
	printf "  --dry-run \"<r>\"             resolve + print, do not run\n"
	printf "  --api \"<r>\"                 emit one JSON object (route only, no exec)\n"
	printf "  -h, --help                   this message\n"
	printf "  -V, --version                print version and exit\n\n"

	printf "${B}ENVIRONMENT${R}\n"
	printf "  WOVE_DRY_RUN=1               same as --dry-run, applies to every call\n"
	printf "  WOVE_YES=1                   skip y/N confirmation on L2 (use with care)\n"
	printf "  WOVE_MODEL=<name|path>       override active LLM model\n"
	printf "  WOVE_ENGINE=llama3pure|picolm|auto    pin the LLM engine (default: auto)\n\n"

	printf "${B}REPL${R}\n"
	printf "  :dry on|off                  toggle dry-run mode\n"
	printf "  :yes on|off                  toggle auto-confirm for L2\n"
	printf "  :doctor                      health check (deps, engine, model)\n"
	printf "  :help                        REPL help\n"
	printf "  :q                           quit\n\n"

	printf "${B}SEE ALSO${R}\n"
	printf "  /usr/share/doc/wove/wove.html    full schema reference\n"
	printf "  https://hg.slitaz.org/wove       source\n"
}

# Friendly "which X?" hint when an action is resolved without its key
# argument — e.g. the LLM picks install_package off a how-to question
# ("comment on installe un paquet ?") with no package name. Mirrors the
# matcher's incomplete_* reasons but fires for any source. Returns 0 (and
# prints the hint) only when $fn is a known arg-taking action whose key
# parameter is empty; 1 otherwise, so callers fall back to the validator.
_wove_missing_arg_hint() {
	local fn="$1" call="$2" key val
	case "$fn" in
		install_package|remove_package) key=package ;;
		set_mirror)                     key=url ;;
		*) return 1 ;;
	esac
	val=$(printf '%s' "$call" | jq -r --arg k "$key" '.parameters[$k] // ""' 2>/dev/null)
	[ -n "$val" ] && return 1
	case "$fn" in
		install_package) echo "wove: Quel paquet ? Ex. : \"installe gimp\"" >&2 ;;
		remove_package)  echo "wove: Quel paquet à désinstaller ? Ex. : \"désinstalle vlc\"" >&2 ;;
		set_mirror)      echo "wove: Donne l'URL du miroir. Ex. : \"ajoute le miroir http://mirror.slitaz.org/\"" >&2 ;;
	esac
	return 0
}

handle() {
	local input="$1"
	[ -z "$input" ] && return 0
	local call fn reason _eng _l3 _pic kind flow
	if ! call=$(wove_route "$input"); then
		echo "wove: hors périmètre — essaie \`wove --help\` ou \`?\` dans la TUI" >&2
		echo "      pour des exemples concrets (paquets, services, wifi, etc.)." >&2
		return 1
	fi
	fn=$(printf '%s' "$call" | jq -r '.function // empty' 2>/dev/null)
	if [ "$fn" = "none" ] || [ -z "$fn" ]; then
		reason=$(printf '%s' "$call" | jq -r '.parameters.reason // empty' 2>/dev/null)
		case "$reason" in
			incomplete_install)
				echo "wove: Quel paquet ? Ex. : \"installe gimp\"" >&2 ;;
			incomplete_remove)
				echo "wove: Quel paquet à désinstaller ? Ex. : \"désinstalle vlc\"" >&2 ;;
			incomplete_mirror)
				echo "wove: Donne l'URL du miroir. Ex. : \"ajoute le miroir http://mirror.slitaz.org/\"" >&2 ;;
			add_user_unsupported)
				echo "wove: La création d'utilisateurs n'est pas encore exposée." >&2
				echo "       Utilise : sudo adduser <nom>" >&2 ;;
			meta_engine)
				# Short, no LLM round-trip. Full picture: `wove doctor`.
				_eng=$(_wove_engine 2>/dev/null)
				_l3=""; _pic=""
				if command -v "${LLAMA3PURE_BIN:-llama3pure}" >/dev/null 2>&1; then _l3="llama3pure"; fi
				if command -v "${PICOLM_BIN:-picolm}" >/dev/null 2>&1; then _pic="picolm"; fi
				if [ -z "$_l3" ] && [ -z "$_pic" ]; then
					echo "wove: no LLM engine installed — running on the matcher only." >&2
					echo "       Try: tazpkg get-install llama3pure  (then: wove model pull <name>)" >&2
				else
					echo "wove: engine=$_eng${_l3:+, llama3pure available}${_pic:+, picolm available}." >&2
					echo "       Détails : wove doctor" >&2
				fi ;;
			meta_version)
				echo "wove $VERSION" >&2 ;;
			meta_usage)
				_tool=$(printf '%s' "$call" | jq -r '.parameters.tool // "wove"' 2>/dev/null)
				case "$_tool" in
					spk)
						echo "wove: spk — gestionnaire de paquets SliTaz." >&2
						echo "       spk --help · spk-search <terme> · spk-add <paquet>" >&2 ;;
					trame)
						echo "wove: trame — couche système bas-niveau (sortie JSON)." >&2
						echo "       trame --help · trame <module> --help (ex. trame wifi --help)" >&2 ;;
					*)
						echo "wove: assistant sysadmin SliTaz en langage naturel (FR/EN)." >&2
						echo "       Demande directement, p. ex. \"installe gimp\", \"scan wifi\"." >&2
						echo "       wove --help (exemples) · wove puis :functions (liste) · wove doctor" >&2 ;;
				esac ;;
			meta_capabilities)
				_n=0
				[ -r "$WOVE_SHARE/wove-functions.json" ] && \
					_n=$(jq '.functions | length' "$WOVE_SHARE/wove-functions.json" 2>/dev/null)
				echo "wove: je connais $_n fonctions système (paquets, services, wifi, users, storage, locale…)." >&2
				echo "       Liste complète      : wove                  (puis :functions dans le REPL)" >&2
				echo "       Exemples groupés    : wove --help           (ou :examples dans le REPL)" >&2
				echo "       Tu peux aussi parler en langue naturelle, p. ex. \"installe gimp\", \"démarre httpd\"." >&2
				;;
			*)
				echo "wove: je gère le système, pas la conversation." >&2
				echo "       Essaye une commande, p. ex. \"espace disque\" ou \"installe gimp\"." >&2
				;;
		esac
		return 0
	fi
	# Multi-step flow dispatch (orchestration on top of Trame). Flows
	# don't go through wove_validate/wove_execute — they own their own
	# I/O, confirmation, sudo prefix and logging (see lib/flows/*.sh).
	kind=$(wove_schema_get "$fn" "kind")
	if [ "$kind" = "flow" ]; then
		flow=$(wove_schema_get "$fn" "flow")
		case "$flow" in
			wifi_configure) wove_flow_wifi_configure "$input" ;;
			*)
				echo "wove: unknown flow '$flow' (schema bug?)" >&2
				return 1 ;;
		esac
		return
	fi
	if ! wove_validate "$call" 2>/dev/null; then
		# Prefer the friendly "which X?" hint for a missing key argument;
		# otherwise surface the validator's real reason.
		_wove_missing_arg_hint "$fn" "$call" && return 1
		wove_validate "$call"
		return 1
	fi
	wove_execute "$call" "$input"
}

repl_help() {
	local D="" R=""
	if [ -t 1 ]; then D="$(printf '\033[2m')"; R="$(printf '\033[0m')"; fi
	local fcount=0
	[ -r "$WOVE_SHARE/wove-functions.json" ] && \
		fcount=$(jq '.functions | length' "$WOVE_SHARE/wove-functions.json" 2>/dev/null)

	title "Wove REPL — wove $VERSION"
	cat <<EOF
Natural-language sysadmin assistant for SliTaz. Type FR or EN.

Meta commands (start with ':')
  :help, :h           this message
  :functions, :fn     list the ${fcount:-?} known function names
  :examples, :ex      worked examples grouped by category
  :doctor             health check (deps, trame, engine, model, log)
  :history [N]        show last N entries from ~/.wove/wove.log
  :model              show / change the active LLM model
  :dry on|off         toggle dry-run mode      (currently: ${WOVE_DRY_RUN:-0})
  :yes on|off         toggle auto-confirm      (currently: ${WOVE_YES:-0})
  :q, :quit, exit     leave the REPL          (Ctrl-D also works)

Examples
  ${D}wove>${R} infos système
  ${D}wove>${R} installe gimp
  ${D}wove>${R} démarre httpd
  ${D}wove>${R} liste les utilisateurs
  ${D}wove>${R} scan wifi
  ${D}wove>${R} configure le wifi             ${D}# orchestrated flow${R}

Safety levels
  L1 read-only · L2 confirm (y/n) · L3 confirm word (yes/oui)
  Toggle :dry on  to preview the shell command without running it.
EOF
	footer "Type :examples for a longer cheat-sheet, or run \`wove --help\`."
}

repl_functions() {
	if [ ! -r "$WOVE_SHARE/wove-functions.json" ]; then
		echo "wove: schema not found at $WOVE_SHARE/wove-functions.json" >&2
		return 1
	fi
	local n
	n=$(jq '.functions | length' "$WOVE_SHARE/wove-functions.json" 2>/dev/null)
	title "Functions in schema ($n)"
	jq -r '.functions[] | "  L\(.safety_level // 1)  \(.name)\t\(.description)"' \
		"$WOVE_SHARE/wove-functions.json" | expand -t 28
	footer ""
}

repl_examples() {
	local D="" R=""
	if [ -t 1 ]; then D="$(printf '\033[2m')"; R="$(printf '\033[0m')"; fi

	title "System"
	cat <<EOF
  infos système                      ${D}# trame system info${R}
  hostname                           ${D}# show current hostname${R}
  renomme la machine en slitaz-lap   ${D}# set_hostname${R}
  log trame                          ${D}# trame mutation log${R}
EOF
	title "Packages"
	cat <<EOF
  installe gimp                      ${D}# tazpkg get-install${R}
  désinstalle vlc
  cherche éditeur photo
  mets à jour                        ${D}# recharge + up${R}
EOF
	title "Services"
	cat <<EOF
  liste les services
  statut du service httpd
  démarre httpd  /  arrête httpd  /  redémarre httpd
  active httpd au démarrage          ${D}# service_enable${R}
EOF
	title "Users (Trame 0.3)"
	cat <<EOF
  liste les utilisateurs
  crée l utilisateur alice
  supprime l utilisateur bob
EOF
	title "Storage (Trame 0.3)"
	cat <<EOF
  périphériques de stockage          ${D}# trame storage list${R}
  infos sur /dev/sda1
  monte /dev/sdb1 sur /mnt/usb
  démonte /mnt/usb
EOF
	title "Network & Wifi"
	cat <<EOF
  réseau                             ${D}# trame network show${R}
  scan wifi
  configure le wifi                  ${D}# multi-step flow${R}
EOF
	title "Time & Locale"
	cat <<EOF
  quelle heure est-il
  synchronise l heure                ${D}# ntp sync${R}
  langue système                     ${D}# get_locale${R}
  change la locale en fr_FR.UTF-8
EOF
	footer ""
}

repl_toggle() {
	local var="$1" val="$2"
	case "$val" in
		on|1|true)       eval "$var=1"; echo "$var=1" ;;
		off|0|false|"")  eval "$var=0"; echo "$var=0" ;;
		*) echo "usage: :${var#WOVE_} on|off" >&2; return 1 ;;
	esac
}

repl() {
	title "Wove REPL — wove $VERSION"
	echo "Natural-language sysadmin assistant for SliTaz."
	echo "Type :help for commands, :examples for a cheat-sheet, Ctrl-D to exit."
	separator
	printf '\033]0;Wove\007'
	local line arg
	while :; do
		printf 'wove> '
		if ! IFS= read -r line; then echo; break; fi
		[ -z "$line" ] && continue
		case "$line" in
			:q|:quit|exit)         break ;;
			:help|:h)              repl_help ;;
			:functions|:fn|:list)  repl_functions ;;
			:examples|:ex)         repl_examples ;;
			:doctor)               wove_doctor || true ;;
			:history*)             arg=${line#:history}; arg=${arg# }; wove_history ${arg:+-n "$arg"} ;;
			:model*)               arg=${line#:model}; arg=${arg# }; wove_model $arg ;;
			:dry*)                 arg=${line#:dry}; arg=${arg# }; repl_toggle WOVE_DRY_RUN "$arg" ;;
			:yes*)                 arg=${line#:yes}; arg=${arg# }; repl_toggle WOVE_YES "$arg" ;;
			:*)                    echo "unknown REPL command: $line (try :help)" >&2 ;;
			*)             handle "$line" || true ;;
		esac
	done
}

case "${1:-}" in
	-h|--help|help)  usage; exit 0 ;;
	-V|--version)    echo "$VERSION"; exit 0 ;;
	--dry-run)       shift; WOVE_DRY_RUN=1 handle "${1:-}" ;;
	--api)           shift; wove_api_route "${1:-}" ;;
	doctor)          wove_doctor ;;
	functions)       shift; wove_api_functions "$@" ;;
	status)
		echo "wove: 'status' has been folded into 'doctor' — running it now." >&2
		wove_doctor ;;
	history)         shift; wove_history "$@" ;;
	model)           shift; wove_model "$@" ;;
	"")              repl ;;
	*)               handle "$*" ;;
esac
