#!/usr/bin/env bash
# smart-install/install.sh — Hlavní vstupní bod
# Usage: curl -s install.uaml.ai | bash
#        ./install.sh [--product uaml|openclaw|all] [--key YOUR_KEY] [--dumb]
#                     [--license-key UAML-...] [--email you@example.com]
#                     [--accept-eula] [--no-telemetry] [--demo-sandbox]
#                     [--tiers core,memory,studio,localtools,agents,ops,security,bench]
#                     [--with-automation] [--print-plan]
#   RFC-035: --tiers selects components to install (deps auto-expand).
#   Omit / "all" = full install (back-compat). core is always included.
#   EXCEPTION 'bench' (ADR-027 B7): bench steps run ONLY with an explicit
#   --tiers ...,bench — never as part of a full/default install (the tier
#   carries a CC BY-NC 4.0 dataset license gate; unattended installs skip
#   the dataset with manual instructions).
#   ADR-026 T3: --with-automation DELIVERS the €19 voice unlock (adds the
#     amemor-voice-agent product, 1 station) orthogonally to --tiers — the 4
#     matrix cells are --tiers {lite|core,…} × --with-automation {off|on}.
#     Default OFF = today's behaviour exactly (install is otherwise unrestricted;
#     the runtime voice gate is ADR-026 T7, NOT here).
#   ADR-026 T4: --print-plan resolves the selected products/tiers and EMITS the
#     ordered install_steps as copy-paste shell, then exits 0 WITHOUT executing —
#     the "manual" symmetric path (automation = "we run these for you"). A free
#     --print-plan (no --with-automation) emits open-core steps identical to the
#     public GitHub open-core install; voice steps appear only with the unlock.
# --product all = instaluje UAML + OpenClaw + propojí je
set -euo pipefail

# Ensure HOME is always defined. Unattended runs (systemd-run, cron, CI, or
# `curl … | bash` with no login shell) can have HOME unset, which aborts any
# `$HOME` reference under `set -u` (notably lib/detect.sh). Resolve from passwd,
# fall back to /root.
if [[ -z "${HOME:-}" ]]; then
  # `|| HOME=""` so a failed getent (under set -e + pipefail) can't abort us;
  # the export below supplies the /root fallback.
  HOME="$(getent passwd "$(id -u)" 2>/dev/null | cut -d: -f6)" || HOME=""
  export HOME="${HOME:-/root}"
fi

# All UAML config/data MUST live in the service user's home, never the
# installer's (root) home. A real customer environment has no unlimited sudo,
# and root-owned UAML state is unreadable by the `uaml` service user. Resolve
# the service user's home and pin UAML_HOME/UAML_INSTALL_HOME to it so every
# writer (wizard lang.json, apply_config agent.json, …) targets /home/uaml.
UAML_USER="${UAML_USER:-uaml}"
# The uaml user does NOT exist yet this early in a clean install, so getent
# fails — `|| _uaml_user_home=""` prevents set -e + pipefail from aborting; the
# /home/uaml fallback below is the install convention anyway.
_uaml_user_home="$(getent passwd "$UAML_USER" 2>/dev/null | cut -d: -f6)" || _uaml_user_home=""
export UAML_INSTALL_HOME="${UAML_INSTALL_HOME:-${_uaml_user_home:-/home/uaml}}"
export UAML_HOME="${UAML_HOME:-$UAML_INSTALL_HOME/.uaml}"

# Fresh-boot apt-lock robustness. A just-booted Ubuntu runs unattended-upgrades,
# which holds /var/lib/dpkg/lock-frontend. Without this, the FIRST apt-get call
# (deps / Caddy / neo4j) fails immediately under `set -e` and aborts the whole
# install — observed repeatedly on clean test VMs. Make EVERY apt-get call WAIT
# for the lock (and retry transient fetch failures) instead of dying. Root-only,
# idempotent; tunable via UAML_APT_LOCK_TIMEOUT (seconds).
if [[ "${EUID:-$(id -u)}" -eq 0 ]] && command -v apt-get >/dev/null 2>&1; then
  if mkdir -p /etc/apt/apt.conf.d 2>/dev/null; then
    printf 'DPkg::Lock::Timeout "%s";\nAcquire::Retries "3";\n' \
      "${UAML_APT_LOCK_TIMEOUT:-300}" > /etc/apt/apt.conf.d/99uaml-lock-timeout 2>/dev/null \
      && echo "   apt lock-wait configured (DPkg::Lock::Timeout=${UAML_APT_LOCK_TIMEOUT:-300}s)"
  fi
fi

# ADR-025 build-stamp — the DEFAULT install channel when neither --channel nor
# SMART_INSTALL_CHANNEL is given. The fleet/source tree keeps `test`, so
# `curl install.uaml.ai | bash` is byte-for-byte unchanged (zero regression).
# The PUBLIC export (export_production.py) flips the value on the __SI_DEFAULT_CHANNEL__
# marker line below to `production`, so a customer's
# `curl raw.githubusercontent…/uaml-memory/uaml/main/install.sh | bash` (and the
# install.amemor.com 301) binds production WITHOUT an explicit flag — while an
# explicit --channel / SMART_INSTALL_CHANNEL always wins. A bare `curl | bash` pipe
# cannot know which host served it (install.uaml.ai and raw.githubusercontent serve
# the SAME file), so the default MUST live in the file content — a baked constant,
# not a runtime heuristic. The export flip + the install.amemor.com vhost go live
# together at the ostrý publish; until then the documented public one-liner carries
# `--channel production` explicitly.
: "${SMART_INSTALL_DEFAULT_CHANNEL:=production}"   # __SI_DEFAULT_CHANNEL__  export→production

# Self-bootstrap: when invoked as `curl … | bash`, BASH_SOURCE[0] is empty
# and there is no lib/ or products/ next to us. Fetch the full smart-install
# tarball, extract it, and re-exec install.sh from there.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-}")" 2>/dev/null && pwd || echo "")"
if [[ -z "${_SMART_INSTALL_BOOTSTRAPPED:-}" \
      && ( -z "$SCRIPT_DIR" || ! -d "$SCRIPT_DIR/lib" || ! -d "$SCRIPT_DIR/products" ) ]]; then
  # ADR-025: the channel decides WHERE we fetch the installer and WHETHER we
  # verify it before running it as root. We are BEFORE the main arg loop here
  # (no lib/ next to us yet), so peek $@ for --channel/--upstream and honour the
  # SMART_INSTALL_CHANNEL env. Default stays `test` → the historical
  # install.uaml.ai plaintext tarball path below is byte-for-byte UNCHANGED
  # (zero fleet regression). `production` takes the signed-GitHub-release path.
  _boot_channel="${SMART_INSTALL_CHANNEL:-$SMART_INSTALL_DEFAULT_CHANNEL}"
  _boot_upstream=""
  _boot_prev=""
  for _boot_a in "$@"; do
    case "$_boot_prev" in
      --channel)  _boot_channel="$_boot_a" ;;
      --upstream) _boot_upstream="$_boot_a" ;;
    esac
    _boot_prev="$_boot_a"
  done

  # Fail-CLOSED on an unknown/typo channel. Without this a `--channel Production`
  # (capital) or `SMART_INSTALL_CHANNEL=prod` typo would not match "production"
  # below and would SILENTLY fall through to the unverified test-channel
  # bootstrap (no sig/pin/sha) — a security fail-open. The main arg loop repeats
  # this guard, but that runs only AFTER the bootstrap download+re-exec, too late.
  if [[ "$_boot_channel" != "test" && "$_boot_channel" != "production" ]]; then
    echo "❌ --channel: neznámá hodnota '$_boot_channel' (povoleno: test | production)" >&2
    echo "   Unknown --channel value '$_boot_channel' (allowed: test | production)" >&2
    exit 2
  fi

  if [[ "$_boot_channel" == "production" ]]; then
    # ── Production public bootstrap: fetch + VERIFY the signed release tarball ──
    # Fail-safe: ANY fetch/verify failure aborts (exit 1); we NEVER exec an
    # unverified installer. These curls intentionally DO NOT use `|| true` — a
    # miss here must abort, not silently degrade (rule #8 spirit). Order matches
    # ADR-025 §First-install TOFU pin: pin-gate the key, THEN sha256 + ed25519.
    # F68 (2026-08-08): the default used to be the ADR-025 GitHub line, which is
    # NOT deployed (raw VERSION 404). F65 made `production` the baked default for
    # public installs, so every public one-liner aborted on the very first fetch.
    # The default is now the endpoint that actually publishes signed bundles —
    # install.uaml.ai, whose layout (<base>/uaml/VERSION, <base>/uaml/<ver>/…) is
    # exactly the non-github passthrough below. Switching to the GitHub line later
    # is a one-value change here (or --upstream at install time).
    _base="${_boot_upstream:-https://install.uaml.ai}"; _base="${_base%/}"
    # Derive the VERSION + asset URLs inline — lib/upstream-resolve.sh is not on
    # disk yet during `curl|bash`, so we mirror ITS mapping by hand (keep the two
    # in sync). github.com/<owner>/<repo> → raw main/VERSION + releases/download
    # assets; ANY OTHER https base → the nginx-style layout <base>/<product>/…,
    # exactly the resolver's passthrough. This makes a mirror OR a controlled test
    # endpoint (--upstream https://host --channel production) work identically to
    # the installed update client — not a github-only hardcode.
    if [[ "$_base" == https://github.com/*/* || "$_base" == http://github.com/*/* ]]; then
      _gh_path="${_base#http*://github.com/}"
      _boot_ver_url="https://raw.githubusercontent.com/${_gh_path}/main/VERSION"
      # ADR-025 tag scheme: smart-install releases use the `install-v<ver>` tag
      # prefix (not bare `v<ver>`) to stay out of the pip package's semver tag
      # namespace on the shared repo. Must match lib/upstream-resolve.sh +
      # tools/publish-github.sh + verify-release.yml.
      _boot_asset_pre="https://github.com/${_gh_path}/releases/download/install-v"  # +<ver>/<file>
    else
      _boot_ver_url="$_base/uaml/VERSION"
      _boot_asset_pre="$_base/uaml/"                                         # +<ver>/<file>
    fi
    # UAML_VERSION_URL override (parity with lib/check-updates.sh's <PRODUCT>_VERSION_URL):
    # point VERSION resolution at an explicit URL — e.g. a specific tag ref during a
    # release-only rig test, or a mirror. Only VERSION is overridden; the signed asset
    # base (_boot_asset_pre) is unchanged, so integrity/verify are unaffected.
    if [[ -n "${UAML_VERSION_URL:-}" ]]; then
      _boot_ver_url="$UAML_VERSION_URL"
    fi

    if ! command -v openssl >/dev/null 2>&1 || ! command -v sha256sum >/dev/null 2>&1; then
      echo "❌ Production install vyžaduje openssl + sha256sum pro ověření podpisu." >&2
      exit 1
    fi

    echo "🔐 Production channel: ověřuji podpis release balíčku z $_base…"
    _stage="$(mktemp -d /tmp/smart-install-prod-XXXXXX)"

    # 1) Resolve the exact version and pin the tag v<ver> (never releases/latest
    #    → that races the VERSION read; ADR-025 rejected alternative).
    # `|| _ver=""` so a curl/pipefail miss does NOT abort at the assignment under
    # `set -euo pipefail` — that would skip the friendly diagnostic below.
    _ver="$(curl -fsSL --max-time 30 "$_boot_ver_url" 2>/dev/null | head -1 | tr -d ' \r\n')" || _ver=""
    if [[ -z "$_ver" ]]; then
      echo "❌ Nelze získat VERSION z $_boot_ver_url — přerušuji." >&2; exit 1
    fi
    # _asset already carries the correct prefix (github: .../releases/download/install-v<ver>;
    # non-github: <base>/uaml/<ver>). _tag is a display label for the messages below.
    _tag="install-v$_ver"; _asset="${_boot_asset_pre}${_ver}"; _tgz="uaml-$_ver.tar.gz"

    # 2) Fetch the SIGNED release bundle + sha256 + ed25519 sig + signer pubkey.
    if ! curl -fsSL --max-time 120 "$_asset/$_tgz"              -o "$_stage/$_tgz"              2>/dev/null \
       || ! curl -fsSL --max-time 30  "$_asset/$_tgz.sha256"    -o "$_stage/$_tgz.sha256"      2>/dev/null \
       || ! curl -fsSL --max-time 30  "$_asset/$_tgz.sig"       -o "$_stage/$_tgz.sig"         2>/dev/null \
       || ! curl -fsSL --max-time 30  "$_asset/update-signer.pub" -o "$_stage/update-signer.pub" 2>/dev/null; then
      echo "❌ Stažení podepsaného release ($_tag) z GitHubu selhalo — přerušuji." >&2; exit 1
    fi

    # 3) TOFU-pin gate. Fetch the out-of-band fingerprint we publish on an origin
    #    WE own (amemor.com) and require the just-fetched pubkey to match BEFORE
    #    trusting it to verify anything. Fingerprint recipe = "sha256:" + sha256
    #    of the update-signer.pub file bytes AS SHIPPED (the PEM). This exact
    #    recipe is documented next to the .fp file (Task C3,
    #    deploy/amemor-release-fp.README.md). The URL is overridable
    #    (UAML_RELEASE_FP_URL) for a mirror / test endpoint — the default is the
    #    amemor.com origin. This does NOT weaken the model: the pin defends against
    #    a compromised GitHub, not against a caller who already controls the env.
    # F68: amemor.com serves no HTTPS; the pin lives on amemor.ai — a DIFFERENT
    # origin/host from the bundle endpoint (install.uaml.ai), which is the whole
    # point: compromising the release host must not also yield the pin.
    _fp_url="${UAML_RELEASE_FP_URL:-https://amemor.ai/.well-known/uaml-release-signer.fp}"
    _pin="$(curl -fsSL --max-time 30 "$_fp_url" 2>/dev/null | tr -d ' \r\n')" || _pin=""
    if [[ -z "$_pin" ]]; then
      echo "❌ TOFU pin ($_fp_url) nedostupný — přerušuji." >&2; exit 1
    fi
    _fp="sha256:$(sha256sum "$_stage/update-signer.pub" | cut -d' ' -f1)"
    if [[ "$_pin" != "$_fp" ]]; then
      echo "❌ TOFU pin MISMATCH — release pubkey neodpovídá amemor.com pinu (možná kompromitace)." >&2
      echo "   expected(pin)=$_pin  got(key)=$_fp — přerušuji." >&2
      exit 1
    fi
    echo "   ✅ TOFU pin OK ($_fp)"

    # 4) sha256 integrity of the tarball.
    _want="$(awk '{print $1}' "$_stage/$_tgz.sha256" | head -1)"
    _got="$(sha256sum "$_stage/$_tgz" | cut -d' ' -f1)"
    if [[ -z "$_want" || "$_want" != "$_got" ]]; then
      echo "❌ sha256 balíčku nesouhlasí (want=$_want got=$_got) — přerušuji." >&2; exit 1
    fi

    # 5) ed25519 signature over the RAW tarball bytes, against the now-pinned key
    #    (same recipe the node update path uses: pkeyutl -verify -pubin -rawin).
    if ! openssl pkeyutl -verify -pubin -inkey "$_stage/update-signer.pub" \
         -rawin -in "$_stage/$_tgz" -sigfile "$_stage/$_tgz.sig" >/dev/null 2>&1; then
      echo "❌ ed25519 verifikace podpisu selhala — přerušuji." >&2; exit 1
    fi
    echo "   ✅ sha256 + ed25519 podpis ověřen ($_tgz @ $_tag)"

    # 6) Extract the VERIFIED bundle and hand off to its install.sh. Support both
    #    a top-level install.sh and a one-dir-deep layout.
    if ! tar xzf "$_stage/$_tgz" -C "$_stage" 2>/dev/null; then
      echo "❌ Rozbalení ověřeného tarballu selhalo — přerušuji." >&2; exit 1
    fi
    _inst="$_stage/install.sh"
    [[ -f "$_inst" ]] || _inst="$(find "$_stage" -maxdepth 2 -name install.sh -type f | head -1)"
    if [[ -z "$_inst" || ! -f "$_inst" ]]; then
      echo "❌ Ověřený tarball neobsahuje install.sh — přerušuji." >&2; exit 1
    fi
    chmod +x "$_inst"
    # F69 (2026-08-08): propagate the RESOLVED channel across the handoff. The
    # old code relied on "$@ still carries --channel production" — true only when
    # the operator typed the flag. A public `curl | bash` resolves production
    # from the BAKED default (the F65 export flip), so nothing was passed on and
    # the bundle's own install.sh fell back to ITS baked default (`test`, since
    # GATE A requires the bundle to equal the git tree byte-for-byte and the git
    # tree keeps `test`). Result: a node verified and installed through the
    # production path, then recorded channel=test and self-updated on the test
    # pointer. Exporting the resolved value fixes it without touching the signed
    # bundle: the inner install.sh reads SMART_INSTALL_CHANNEL before its default.
    export SMART_INSTALL_CHANNEL="$_boot_channel"
    # Re-exec with the original args. The
    # re-exec'd copy has lib/ next to it so this bootstrap won't fire again; the
    # _SMART_INSTALL_BOOTSTRAPPED sentinel is a belt-and-braces loop-guard in case
    # the verified bundle's install.sh lacks a co-located products/ (stripped/lite
    # layout) — without it a re-exec could re-enter the bootstrap and hammer
    # GitHub + amemor.com in a download loop.
    export _SMART_INSTALL_BOOTSTRAPPED=1
    exec "$_inst" "$@"
  fi

  # ── test channel (DEFAULT) — historical plaintext bootstrap, UNCHANGED ──
  # F-30c (audit 2026-07-01): protocol upgraded to HTTPS — install.uaml.ai now
  # serves a valid Let's Encrypt cert (verified 200), so the initial bootstrap
  # tarball hop is no longer plaintext. The *domain* choice (install.uaml.ai vs
  # amemor-ai) still needs Pavel's decision — keep the host as-is until then;
  # only the scheme changed. Override with SMART_INSTALL_TARBALL_URL if needed.
  TARBALL_URL="${SMART_INSTALL_TARBALL_URL:-https://install.uaml.ai/smart-install.tar.gz}"
  STAGE_DIR="$(mktemp -d /tmp/smart-install-XXXXXX)"
  echo "📦 Bootstrap: stahuji smart-install z $TARBALL_URL ..."
  if ! curl -fsSL --max-time 60 "$TARBALL_URL" | tar xz -C "$STAGE_DIR" 2>/dev/null; then
    echo "❌ Nepodařilo se stáhnout smart-install tarball z $TARBALL_URL"
    echo "   Zkontroluj síť nebo nastav SMART_INSTALL_TARBALL_URL."
    exit 1
  fi
  if [[ ! -f "$STAGE_DIR/install.sh" ]]; then
    echo "❌ Tarball neobsahuje install.sh — pravděpodobně poškozený"
    exit 1
  fi
  chmod +x "$STAGE_DIR/install.sh"
  export _SMART_INSTALL_BOOTSTRAPPED=1
  exec "$STAGE_DIR/install.sh" "$@"
fi
LIB_DIR="$SCRIPT_DIR/lib"
PRODUCTS_DIR="$SCRIPT_DIR/products"

# Smart-install version (from VERSION file, fallback to git tag) — telemetry uses this
SMART_INSTALL_VERSION="$(cat "$SCRIPT_DIR/VERSION" 2>/dev/null || echo 'smart-install')"
export SMART_INSTALL_VERSION

# --- Subcommand dispatcher (RFC-027 iter 0 skeleton) ---
# Recognized subcommands: install (default), update, upgrade, status, rollback.
# Non-install modes are scaffolded here but not yet implemented (see RFC-027).
SUBCMD="install"
if [[ $# -ge 1 ]]; then
  case "$1" in
    install|update|upgrade|status|rollback|deps|fan-out|updates|doctor|verify|provision-hub)
      SUBCMD="$1"; shift ;;
    -*)
      : ;;  # a flag — the install path owns it
    *)
      # An unrecognized WORD used to fall through to SUBCMD=install, so a typo
      # or a not-yet-wired subcommand ran a full unattended install instead of
      # the read-only thing that was asked for. Found 2026-08-05: `install.sh
      # verify --to local` — the exact form lib/verify.sh documents — reinstalled
      # the box. A verb we do not know must never be interpreted as "install".
      echo "smart-install: unknown subcommand '$1'" >&2
      echo "  subcommands: install (default) update upgrade status rollback deps" >&2
      echo "               fan-out updates doctor verify provision-hub" >&2
      echo "  to pass flags to an install, start them with '-' (e.g. --product uaml)" >&2
      exit 2 ;;
  esac
fi

case "$SUBCMD" in
  provision-hub)
    # RFC-050: turn this machine into a UAML hub (own private hub, or our hub's
    # standby). Idempotent. See provision-hub.sh for flags.
    _PH="$(dirname "${BASH_SOURCE[0]}")/provision-hub.sh"
    if [[ ! -x "$_PH" ]]; then
      echo "provision-hub.sh not present (smart-install < RFC-050)" >&2
      exit 1
    fi
    exec bash "$_PH" "$@"
    ;;
esac

case "$SUBCMD" in
  doctor)
    # Autonomous node self-heal — encodes field lessons (FIELD-LESSONS.md) as
    # idempotent detect→heal→verify rules. Safe to re-run; never destroys data.
    if [[ ! -x "$LIB_DIR/doctor.sh" ]]; then
      echo "lib/doctor.sh not present (smart-install < 1.4.12)" >&2
      exit 1
    fi
    exec bash "$LIB_DIR/doctor.sh" "$@"
    ;;
  verify)
    # Read-only parity + health check for this node or an ssh-reachable peer.
    # lib/verify.sh has documented itself as `smart-install verify --to <node>`
    # since it was written; the dispatcher just never carried the verb.
    if [[ ! -x "$LIB_DIR/verify.sh" ]]; then
      echo "lib/verify.sh not present (smart-install < 1.3.0)" >&2
      exit 1
    fi
    exec bash "$LIB_DIR/verify.sh" "$@"
    ;;
  updates)
    # RFC-044 Phase 2: operator CLI over the hash-chained updates.db (one of three
    # channels). VPS-safe — decisions/policy/list/stage only, never applies.
    if [[ ! -x "$LIB_DIR/updates-cli.sh" ]]; then
      echo "lib/updates-cli.sh not present (smart-install < RFC-044 Phase 2)" >&2
      exit 1
    fi
    exec bash "$LIB_DIR/updates-cli.sh" "$@"
    ;;
  status)
    if [[ -x "$LIB_DIR/registry.sh" ]]; then
      "$LIB_DIR/registry.sh" show
    else
      echo "registry helper not yet installed (RFC-027 iter 0)" >&2
      exit 1
    fi
    exit 0
    ;;
  deps)
    # RFC-027 iter 4: dependency resolver.
    if [[ ! -x "$LIB_DIR/deps.sh" ]]; then
      echo "lib/deps.sh not present (smart-install < 1.2.1)" >&2
      exit 1
    fi
    exec "$LIB_DIR/deps.sh" "$@"
    ;;
  update)
    # RFC-027 iter 2: re-apply install steps that changed since last run.
    # Idempotent + DB-safe (snapshots before each step that touches DB rows).
    if [[ ! -x "$LIB_DIR/update.sh" ]]; then
      echo "lib/update.sh not present (smart-install < 1.1.16)" >&2
      exit 1
    fi
    exec "$LIB_DIR/update.sh" "$@"
    ;;
  upgrade)
    # RFC-027 iter 3: run versioned migrations products/<n>/migrations/.
    # Takes a named pre-upgrade snapshot of every touched DB. Stops on first
    # failure with rollback instructions.
    if [[ ! -x "$LIB_DIR/upgrade.sh" ]]; then
      echo "lib/upgrade.sh not present (smart-install < 1.2.0)" >&2
      exit 1
    fi
    exec "$LIB_DIR/upgrade.sh" "$@"
    ;;
  rollback)
    # RFC-027 iter 5: restore product DBs from a named pre-upgrade snapshot.
    # Refuses if a NEWER upgrade is recorded since that snapshot (override
    # via --force).
    if [[ ! -x "$LIB_DIR/rollback.sh" ]]; then
      echo "lib/rollback.sh not present (smart-install < 1.2.2)" >&2
      exit 1
    fi
    exec "$LIB_DIR/rollback.sh" "$@"
    ;;
  fan-out)
    # RFC-027 iter 5: distribute a smart-install command across the fleet.
    # Uses SSH today; RFC-022 hub-mediated mode planned (--via-hub stub).
    if [[ ! -x "$LIB_DIR/fan-out.sh" ]]; then
      echo "lib/fan-out.sh not present (smart-install < 1.2.2)" >&2
      exit 1
    fi
    exec "$LIB_DIR/fan-out.sh" "$@"
    ;;
esac

# --- Argumenty ---
PRODUCT="${INSTALL_PRODUCT:-uaml}"
API_KEY=""
DUMB_MODE=false
DRY_RUN=false
LICENSE_KEY="${UAML_LICENSE_KEY:-}"
LICENSE_EMAIL="${UAML_LICENSE_EMAIL:-}"
ACCEPT_EULA="${UAML_ACCEPT_EULA:-0}"
# Named lite bundles (fleet [lite D7] #2368): --bundle <name> expands a preset
# from products/uaml/bundles.json into --product + --tiers (+ langpacks + env).
# Track whether --product/--tiers were given EXPLICITLY on the CLI so --bundle
# can refuse to silently mix with an ad-hoc selection (conflict handling below).
BUNDLE="${SMART_INSTALL_BUNDLE:-}"
_PRODUCT_EXPLICIT=0
_TIERS_EXPLICIT=0
# ADR-025 install-source channel. Default `test` → SMART_INSTALL_UPSTREAM=
# install.uaml.ai, i.e. an unflagged install stays byte-for-byte today's
# behaviour (zero fleet regression). `production` points the node at the public
# signed GitHub release channel. --upstream <url> is a verbatim override that
# wins over --channel (any https base — the install.uaml.ai fallback, a mirror…).
CHANNEL="${SMART_INSTALL_CHANNEL:-$SMART_INSTALL_DEFAULT_CHANNEL}"
UPSTREAM_OVERRIDE=""
# ADR-026 T3/T4 flags. Both default to today's behaviour (no flag = unchanged):
#   WITH_AUTOMATION=0 → no voice product added, no entitlement marker.
#   PRINT_PLAN=false  → normal install path (execute), never a plan-only exit.
# WITH_AUTOMATION honours SMART_INSTALL_WITH_AUTOMATION so a bundle/env can pre-set it.
WITH_AUTOMATION="${SMART_INSTALL_WITH_AUTOMATION:-0}"
PRINT_PLAN=false

while [[ $# -gt 0 ]]; do
  case $1 in
    --product)       PRODUCT="$2"; _PRODUCT_EXPLICIT=1; shift 2 ;;
    --key)           API_KEY="$2"; shift 2 ;;
    --license-key)   LICENSE_KEY="$2"; shift 2 ;;
    --email)         LICENSE_EMAIL="$2"; shift 2 ;;
    --accept-eula)   ACCEPT_EULA=1; shift ;;
    --no-telemetry)  export UAML_TELEMETRY=0; shift ;;
    --demo-sandbox)  export UAML_DEMO_SANDBOX=1; shift ;;   # predvadeci box: full_agent local-tools (cele VM = sandbox)
    --no-agent)      NO_AGENT=true; shift ;;
    --dumb)          DUMB_MODE=true; shift ;;
    --dry-run)       DRY_RUN=true; DUMB_MODE=true; shift ;;
    --tiers)         export SMART_INSTALL_TIERS="$2"; _TIERS_EXPLICIT=1; shift 2 ;;
    --bundle)        BUNDLE="$2"; shift 2 ;;
    --channel)       CHANNEL="$2"; shift 2 ;;
    --upstream)      UPSTREAM_OVERRIDE="$2"; shift 2 ;;
    # ADR-026 T3: boolean — deliver the €19 voice unlock (adds amemor-voice-agent).
    --with-automation) WITH_AUTOMATION=1; shift ;;
    # ADR-026 T4: boolean — emit the ordered install plan as shell, then exit 0.
    --print-plan)    PRINT_PLAN=true; shift ;;
    *) shift ;;
  esac
done

# --- Bundle preset resolver (fleet [lite D7] #2368) ─────────────────────────
# ADDITIVE: with no --bundle this whole block is skipped and PRODUCT/tiers stay
# byte-identical to the pre-change selector. A named bundle expands to its
# products (→ PRODUCT, feeds the D2 normalizer) + tiers (→ SMART_INSTALL_TIERS,
# feeds the D1/D2 tier machinery) + langpacks + env, all as DATA read from
# products/uaml/bundles.json (jq if present, python3 fallback — both on metod).
# Unknown name → clear error listing the valid bundles. Conflict with an
# explicit --product/--tiers → error (the SAFER choice: a curated preset must
# not be silently half-overridden into an incoherent install).
_bundle_names() {
  local bj="$1"
  if command -v jq >/dev/null 2>&1; then
    jq -r '.bundles | keys[]' "$bj" 2>/dev/null
  else
    python3 -c 'import json,sys; print("\n".join(json.load(open(sys.argv[1]))["bundles"].keys()))' "$bj" 2>/dev/null
  fi
}
_resolve_bundle() {
  # $1 = bundle name, $2 = bundles.json path. Emits KEY=VALUE lines on stdout;
  # returns non-zero (no output) when the bundle name is unknown.
  local b="$1" bj="$2"
  if command -v jq >/dev/null 2>&1; then
    jq -e --arg b "$b" '.bundles[$b]' "$bj" >/dev/null 2>&1 || return 1
    jq -r --arg b "$b" '
      .bundles[$b] as $x
      | "PRODUCT="   + (($x.products  // []) | join(","))
      , "TIERS="     + (($x.tiers     // []) | join(","))
      , "LANGPACKS=" + (($x.langpacks // []) | join(","))
      , "RFC052_GATE=" + (($x.rfc052_gate // false) | tostring)
      , (($x.env // {}) | to_entries[] | "ENV=" + .key + "=" + (.value|tostring))
    ' "$bj"
  else
    python3 - "$b" "$bj" <<'PY'
import json, sys
b, path = sys.argv[1], sys.argv[2]
x = json.load(open(path)).get("bundles", {}).get(b)
if x is None:
    sys.exit(1)
print("PRODUCT="   + ",".join(x.get("products", [])))
print("TIERS="     + ",".join(x.get("tiers", [])))
print("LANGPACKS=" + ",".join(x.get("langpacks", [])))
print("RFC052_GATE=" + ("true" if x.get("rfc052_gate") else "false"))
for k, v in (x.get("env") or {}).items():
    print("ENV=%s=%s" % (k, v))
PY
  fi
}

if [[ -n "$BUNDLE" ]]; then
  BUNDLES_JSON="$PRODUCTS_DIR/uaml/bundles.json"
  if [[ ! -f "$BUNDLES_JSON" ]]; then
    echo "❌ --bundle vyžaduje products/uaml/bundles.json (nenalezen: $BUNDLES_JSON)" >&2
    exit 1
  fi
  if [[ "$_PRODUCT_EXPLICIT" == 1 || "$_TIERS_EXPLICIT" == 1 ]]; then
    echo "❌ --bundle '$BUNDLE' nelze kombinovat s explicitním --product/--tiers." >&2
    echo "   Bundle už definuje produkty i tiery. Použij buď --bundle, NEBO --product/--tiers." >&2
    exit 1
  fi
  if _rb="$(_resolve_bundle "$BUNDLE" "$BUNDLES_JSON")" && [[ -n "$_rb" ]]; then
    :
  else
    echo "❌ Neznámý bundle: '$BUNDLE'" >&2
    echo "   Platné bundly:" >&2
    _bundle_names "$BUNDLES_JSON" | sed 's/^/     • /' >&2
    exit 1
  fi
  while IFS= read -r _line; do
    case "$_line" in
      PRODUCT=*)     PRODUCT="${_line#PRODUCT=}" ;;
      TIERS=*)       [[ -n "${_line#TIERS=}" ]] && export SMART_INSTALL_TIERS="${_line#TIERS=}" ;;
      LANGPACKS=*)   [[ -n "${_line#LANGPACKS=}" ]] && export SMART_INSTALL_LANGPACKS="${_line#LANGPACKS=}" ;;
      RFC052_GATE=*) export SMART_INSTALL_RFC052_GATE="${_line#RFC052_GATE=}" ;;
      ENV=*)         export "${_line#ENV=}" ;;
    esac
  done <<< "$_rb"
  export SMART_INSTALL_BUNDLE="$BUNDLE"
  echo "🎁 Bundle '$BUNDLE' → --product ${PRODUCT} --tiers ${SMART_INSTALL_TIERS:-(default)}"
  [[ -n "${SMART_INSTALL_LANGPACKS:-}" ]] && echo "   langpacks: ${SMART_INSTALL_LANGPACKS}"
fi

# --- ADR-025: resolve install-source channel → SMART_INSTALL_UPSTREAM ────────
# Logical: an unflagged install resolves `test` → install.uaml.ai, keeping the
# fleet byte-identical to today. `production` → the public signed GitHub base.
# An explicit --upstream is a verbatim override that wins over --channel. The
# resolved base is persisted on success (below) so "install-source == update-
# source" (Invariant 3): the pipeline pulls updates from the same origin that
# served the install. Unknown --channel value is a hard error (exit 2).
case "$CHANNEL" in
  test)        _RESOLVED_UPSTREAM="https://install.uaml.ai" ;;
  # F70 (2026-08-08): production resolved to the ADR-025 GitHub base, which does
  # not publish (404). Invariant 3 says install-source == update-source, so a
  # public install — which now genuinely comes from install.uaml.ai (F68) — was
  # persisting an upstream it had NOT installed from and could never update
  # from. Same origin as the bootstrap until the GitHub line actually ships;
  # --upstream still overrides verbatim.
  production)  _RESOLVED_UPSTREAM="https://install.uaml.ai" ;;
  *)
    echo "❌ --channel: neznámá hodnota '$CHANNEL' (povoleno: test | production)" >&2
    echo "   Unknown --channel value '$CHANNEL' (allowed: test | production)" >&2
    exit 2 ;;
esac
if [[ -n "$UPSTREAM_OVERRIDE" ]]; then
  _RESOLVED_UPSTREAM="$UPSTREAM_OVERRIDE"   # verbatim, wins over --channel
fi
export SMART_INSTALL_CHANNEL="$CHANNEL"
export SMART_INSTALL_UPSTREAM="$_RESOLVED_UPSTREAM"

# Persist the resolved channel/upstream on a SUCCESSFUL install (Invariant 3).
# Called once near the end of the script; defined here so the value is in scope.
_persist_install_source() {
  # $1 = channel, $2 = resolved upstream base.
  # Writes BOTH:
  #   1) update.conf → SMART_INSTALL_UPSTREAM (+ UAML_VERSION_URL for production,
  #      honoured by check-updates.sh's <PRODUCT>_VERSION_URL i.e. UAML_VERSION_URL).
  #      Existing keys (signer pubkey path, updates.db, staging dir…) are kept —
  #      we only update-in-place the two keys we own.
  #   2) node-version.json → MERGE channel/upstream/channel_bound_at + the
  #      version/code_sha256 stamp of what this run just deployed.
  #
  # The version stamp used to be omitted here, on the theory that only
  # apply-approved.sh (an UPDATE) may touch the verify baseline. Consequence
  # (found on the vds-xl test node 2026-08-04): a freshly installed node could
  # not state which uaml version it was running at all — /opt/uaml-package has
  # no VERSION file, the stamp had no "version" key, so registry.sh recorded
  # "unknown" and the dashboard's release-parity showed permanent drift. An
  # install is the most authoritative statement of what is deployed there is;
  # it stamps. Re-verification metadata (verified_at/verified_by) stays the
  # property of apply/verify — we only claim what we installed.
  # /etc may be read-only on locked-down hosts → fall back to ~/.uaml like the
  # rest of install.sh (install_steps.json update.conf step). Fresh files are
  # created under umask 022 (→ 644, never group/other-writable).
  local _ch="$1" _up="$2"
  # Derive the VERSION URL from the RESOLVED upstream ($_up), not a hardcoded repo,
  # so a `--upstream <fork|mirror> --channel production` install binds its OWN
  # version-source — install-source == update-source (Invariant 3) even for a
  # non-canonical repo. The resolver lib is on disk here (past bootstrap); fall
  # back to the canonical github path only if it is somehow unavailable.
  local _ver_url=""
  if [[ "$_ch" == "production" ]]; then
    local _resolver="$SCRIPT_DIR/lib/upstream-resolve.sh"
    if [[ -r "$_resolver" ]]; then
      # shellcheck source=/dev/null
      . "$_resolver"
      _ver_url="$(_resolve_upstream_url "$_up" "uaml/VERSION" 2>/dev/null || true)"
    fi
    [[ -z "$_ver_url" ]] && _ver_url="https://raw.githubusercontent.com/uaml-memory/uaml/main/VERSION"
  fi

  local _old_umask; _old_umask="$(umask)"; umask 022

  # Choose the config location: /etc/uaml (root:uaml 750, the convention used by
  # the license block above) when writable, else the per-user fallback.
  local _conf _nv _etc_ok=0
  if install -d -o root -g uaml -m 750 /etc/uaml 2>/dev/null && [[ -w /etc/uaml ]]; then
    _etc_ok=1
  elif [[ -w /etc/uaml ]]; then
    _etc_ok=1
  fi
  if [[ "$_etc_ok" == 1 ]]; then
    _conf=/etc/uaml/update.conf
    _nv=/etc/uaml/node-version.json
  else
    mkdir -p "$HOME/.uaml/config" "$HOME/.uaml" 2>/dev/null || true
    _conf="$HOME/.uaml/config/update.conf"
    _nv="$HOME/.uaml/node-version.json"
  fi

  # --- update.conf: update-in-place, preserve other keys ---
  # Rule #8: never swallow the failure silently — capture the python stderr and
  # surface its last line (ExceptionType: message) so a persist failure (read-only
  # /etc, bad existing conf) is diagnosable instead of a silent unbound node.
  local _perr
  if command -v python3 >/dev/null 2>&1; then
    if _perr="$(UAML_CONF_PATH="$_conf" UAML_CONF_UPSTREAM="$_up" UAML_CONF_VERURL="$_ver_url" \
        python3 - <<'PY' 2>&1 1>/dev/null
import os
path = os.environ["UAML_CONF_PATH"]
sets = {"SMART_INSTALL_UPSTREAM": os.environ["UAML_CONF_UPSTREAM"]}
vu = os.environ.get("UAML_CONF_VERURL", "")
if vu:
    sets["UAML_VERSION_URL"] = vu
try:
    with open(path) as f:
        lines = f.read().splitlines()
except FileNotFoundError:
    lines = []
seen, out = set(), []
for ln in lines:
    stripped = ln.lstrip()
    key = ln.split("=", 1)[0].strip() if ("=" in ln and not stripped.startswith("#")) else None
    if key in sets:
        out.append("%s=%s" % (key, sets[key]))
        seen.add(key)
    else:
        out.append(ln)
for k, v in sets.items():
    if k not in seen:
        out.append("%s=%s" % (k, v))
with open(path, "w") as f:
    f.write("\n".join(out) + "\n")
PY
    )"; then
      echo "   ✅ install-source → $_conf (SMART_INSTALL_UPSTREAM=$_up${_ver_url:+, UAML_VERSION_URL=$_ver_url})"
      [[ "$_conf" == /etc/* ]] && { chmod 644 "$_conf" 2>/dev/null || true; chown root:uaml "$_conf" 2>/dev/null || true; }
    else
      echo "   ⚠️  nepodařilo se zapsat $_conf: ${_perr##*$'\n'} (pokračuji)" >&2
    fi
  fi

  # --- node-version.json: MERGE (never clobber unrelated keys) ---
  # What this run deployed: the bundle VERSION out of the tree, and the content
  # hash of the module it put on disk (same formula as lib/tree-hash.sh, which
  # is what node-selfcheck / release-parity CONTENT compare against — an absent
  # code_sha256 was the single cause of two months of false CODE DRIFT on
  # pepa+cyril, so it is stamped here rather than left to the first update).
  local _si_ver="" _code_sha=""
  [[ -r "$SCRIPT_DIR/VERSION" ]] && _si_ver="$(head -1 "$SCRIPT_DIR/VERSION" | tr -d ' \r\n')"
  if [[ -r "$SCRIPT_DIR/lib/tree-hash.sh" && -d "${UAML_PKG_PATH:-/opt/uaml-package}/uaml" ]]; then
    # shellcheck source=/dev/null
    . "$SCRIPT_DIR/lib/tree-hash.sh" 2>/dev/null || true
    command -v _uaml_tree_hash >/dev/null 2>&1 \
      && _code_sha="$(_uaml_tree_hash "${UAML_PKG_PATH:-/opt/uaml-package}/uaml" 2>/dev/null || true)"
  fi
  if command -v python3 >/dev/null 2>&1; then
    if _perr="$(UAML_NV_PATH="$_nv" UAML_NV_CH="$_ch" UAML_NV_UP="$_up" \
        UAML_NV_VER="$_si_ver" UAML_NV_SHA="$_code_sha" \
        python3 - <<'PY' 2>&1 1>/dev/null
import os, json, datetime
p = os.environ["UAML_NV_PATH"]
try:
    d = json.load(open(p))
    if not isinstance(d, dict):
        d = {}
except Exception:
    d = {}
now = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
d["channel"] = os.environ["UAML_NV_CH"]
d["upstream"] = os.environ["UAML_NV_UP"]
d["channel_bound_at"] = now
ver = os.environ.get("UAML_NV_VER", "")
sha = os.environ.get("UAML_NV_SHA", "")
if ver:
    d["version"] = ver
    d["version_stamped_at"] = now
    d["version_stamped_by"] = "install.sh"
if sha:
    d["code_sha256"] = sha
os.makedirs(os.path.dirname(p) or ".", exist_ok=True)
with open(p, "w") as f:
    json.dump(d, f, indent=2)
PY
    )"; then
      echo "   ✅ node-version.json ← channel=$_ch upstream=$_up${_si_ver:+ version=$_si_ver}${_code_sha:+ code_sha256=${_code_sha:0:12}}"
      [[ "$_nv" == /etc/* ]] && { chmod 644 "$_nv" 2>/dev/null || true; chown root:uaml "$_nv" 2>/dev/null || true; }
    else
      echo "   ⚠️  nepodařilo se zapsat $_nv: ${_perr##*$'\n'} (pokračuji)" >&2
    fi
  fi

  umask "$_old_umask"
}

# Default to --no-agent in non-TTY runs (e.g. curl | bash) so agent install
# failures don't abort the whole install. Users can still pass --no-agent
# explicitly or run `curl … | bash -s -- ` with a TTY redirect to override.
NO_AGENT="${NO_AGENT:-}"
if [[ -z "$NO_AGENT" && ! -t 0 ]]; then
  NO_AGENT=true
fi

export SMART_INSTALL_DRY_RUN="$DRY_RUN"

# Dry-run helper — tiskne příkaz místo spuštění
dry_run_cmd() {
  if [[ "$DRY_RUN" == true ]]; then
    echo "   [DRY-RUN] $ $*"
  else
    eval "$@"
  fi
}

# --- Telemetry (best-effort, non-blocking, schema-correct) ---
# Mirrors the payload schema of uaml/telemetry.py exactly:
#   event, version, os, os_version, python, arch, anonymous_id, error, extra
# Before `pip install uaml` succeeds we fall back to pure curl; after that
# we prefer `python3 -m uaml.telemetry` for richer payloads.
TELEMETRY_URL="${UAML_TELEMETRY_URL:-https://telemetry.uaml.ai/v1/report}"
export TELEMETRY_URL

_telemetry_anonymous_id() {
  # Match uaml/telemetry.py P1-1 contract: persist a per-install random hex,
  # generated ONCE on first call. Falls back to sha256(hostname) only when
  # no writable config dir is available (very rare; ramdisks, etc.).
  local cfg_dir="${UAML_CONFIG_DIR:-${HOME:-/root}/.uaml}"
  local cfg_file="$cfg_dir/telemetry.json"
  local existing
  if command -v python3 >/dev/null 2>&1 && [[ -f "$cfg_file" ]]; then
    existing=$(python3 -c '
import json, sys
try:
    with open(sys.argv[1]) as f: d = json.load(f)
    print(d.get("anonymous_id",""))
except Exception:
    pass
' "$cfg_file" 2>/dev/null)
    if [[ -n "$existing" ]]; then
      printf '%s' "$existing"
      return 0
    fi
  fi
  # No persisted id yet — generate, persist, return.
  local new_id
  if command -v python3 >/dev/null 2>&1; then
    new_id=$(python3 -c 'import secrets; print(secrets.token_hex(8))')
  else
    new_id=$(head -c 16 /dev/urandom 2>/dev/null | xxd -p 2>/dev/null | tr -d '\n')
  fi
  if [[ -z "$new_id" ]]; then
    # Ultimate fallback — hostname hash so we still ship a 16-char value
    new_id=$(printf '%s' "$(hostname)" | sha256sum | cut -c1-16)
  fi
  if command -v python3 >/dev/null 2>&1 && mkdir -p "$cfg_dir" 2>/dev/null; then
    python3 -c '
import json, sys
path = sys.argv[1]
new_id = sys.argv[2]
try:
    with open(path) as f: d = json.load(f)
except Exception:
    d = {"enabled": True}
d["anonymous_id"] = new_id
with open(path, "w") as f: json.dump(d, f, indent=2)
' "$cfg_file" "$new_id" 2>/dev/null
  fi
  printf '%s' "$new_id"
}

telemetry_event() {
  # Args: event_name [extra_json] [error_msg]
  [[ "${UAML_TELEMETRY:-1}" == "0" ]] && return 0
  local event="${1:-unknown}"
  local extra="${2:-}"
  local err="${3:-}"

  # Prefer the Python module once uaml is installed — single source of truth
  if command -v python3 >/dev/null 2>&1 && python3 -c "import uaml.telemetry" 2>/dev/null; then
    if [[ -n "$err" ]]; then
      ( python3 -m uaml.telemetry --error "$err" "$event" "$extra" 2>/dev/null ) &
    else
      ( python3 -m uaml.telemetry "$event" "$extra" 2>/dev/null ) &
    fi
    disown 2>/dev/null || true
    return 0
  fi

  # Bash fallback — same schema as uaml/telemetry.py
  local anon python_ver os_name os_ver arch
  anon=$(_telemetry_anonymous_id)
  python_ver=$(python3 --version 2>&1 | awk '{print $2}' || echo "unknown")
  os_name=$(uname -s)
  os_ver=$(uname -r)
  arch=$(uname -m)

  # Build JSON with python3 if available (proper escaping); otherwise plain printf
  local payload
  if command -v python3 >/dev/null 2>&1; then
    payload=$(python3 -c '
import json, sys
event, version, os_name, os_ver, py, arch, anon, err, extra = sys.argv[1:10]
p = {"event":event,"version":version or "smart-install","os":os_name,
     "os_version":os_ver,"python":py,"arch":arch,"anonymous_id":anon}
if err: p["error"] = err[:500]
if extra:
    try: p["extra"] = json.loads(extra)
    except Exception: p["extra"] = {"raw": extra}
print(json.dumps(p))
' "$event" "smart-install" "$os_name" "$os_ver" "$python_ver" "$arch" "$anon" "$err" "$extra")
  else
    payload=$(printf '{"event":"%s","version":"smart-install","os":"%s","os_version":"%s","python":"%s","arch":"%s","anonymous_id":"%s"}' \
      "$event" "$os_name" "$os_ver" "$python_ver" "$arch" "$anon")
  fi

  ( curl -fsS --max-time 5 -X POST -H "Content-Type: application/json" \
      -d "$payload" "$TELEMETRY_URL" >/dev/null 2>&1 ) &
  disown 2>/dev/null || true
}
export -f telemetry_event _telemetry_anonymous_id

# Error trap: fires install_error event on any unexpected install failure.
# Disarmed once install_ok fires (at end of script). Provides line/cmd/phase
# so support can triage exactly where install crashed.
_TELEMETRY_INSTALL_OK_FIRED=0
SMART_INSTALL_PHASE="${SMART_INSTALL_PHASE:-init}"
_install_error_handler() {
  local rc=$?
  local lineno="${BASH_LINENO[0]:-0}"
  local cmd="${BASH_COMMAND:-unknown}"
  if [[ $rc -ne 0 && "$_TELEMETRY_INSTALL_OK_FIRED" -eq 0 ]]; then
    # Build extra payload — escape quotes in cmd for JSON safety
    local cmd_escaped="${cmd//\"/\\\"}"
    telemetry_event "install_error" \
      "{\"product\":\"${PRODUCT:-unknown}\",\"rc\":$rc,\"line\":$lineno,\"cmd\":\"$cmd_escaped\",\"phase\":\"$SMART_INSTALL_PHASE\"}" \
      "rc=$rc line=$lineno cmd=$cmd"
  fi
  exit $rc
}
trap _install_error_handler ERR
export -f _install_error_handler

# --- Telemetry consent banner (P0-3) ---
# Shown BEFORE the first telemetry_event call so users have a chance to
# Ctrl-C and re-run with UAML_TELEMETRY=0 if they prefer not to send.
# 2-second pause is short enough not to annoy interactive users but long
# enough to read.
if [[ "${UAML_TELEMETRY:-1}" != "0" && "$PRINT_PLAN" != true ]]; then
  echo ""
  echo "📊 Telemetry: install + version events go to telemetry.uaml.ai."
  echo "   What's collected:  event name, UAML version, OS major.minor,"
  echo "                       Python version, CPU arch, random per-install ID."
  echo "   What's NOT sent:   hostname, IP, email, file paths, DB content."
  echo "   Opt-out now:       Ctrl-C, then: UAML_TELEMETRY=0 curl install.uaml.ai | bash"
  echo "   Full details:      https://install.uaml.ai/privacy.html"
  if [[ -t 0 ]]; then sleep 2; fi
fi

# --- Banner ---
# ADR-026 T4: --print-plan is a side-effect-free emission — no banner, no
# install_start telemetry, so stdout carries ONLY copy-paste shell.
if [[ "$PRINT_PLAN" != true ]]; then
  echo ""
  echo "╔══════════════════════════════════════════════╗"
  echo "║        SMART INSTALL — $PRODUCT              "
  echo "╚══════════════════════════════════════════════╝"
  telemetry_event "install_start" "{\"product\":\"$PRODUCT\"}"
  echo ""
fi

# --- API klíč ---
# ADR-026 T4: skip the interactive key prompt for --print-plan (a plan needs no
# AI bootstrap; it must not block on a read).
if [[ "$PRINT_PLAN" != true && -z "$API_KEY" && "$DUMB_MODE" == false ]]; then
  echo "Pro inteligentní instalaci zadej OpenRouter API klíč."
  echo "(Enter = přeskočit, instalace bez AI průvodce)"
  # Only prompt on an interactive TTY. Non-interactive stdin (pipe, systemd,
  # cron, </dev/null) would EOF here and — under `set -e` — abort the whole
  # install. No TTY → proceed without a key (basic install).
  if [[ -t 0 ]]; then
    read -r -p "OpenRouter API Key: " API_KEY || true
  else
    echo "   (neinteraktivní vstup — pokračuji bez AI klíče)"
  fi
  echo ""
fi

if [[ "$PRINT_PLAN" != true && -z "$API_KEY" ]]; then
  echo "⚠️  Bez API klíče — spouštím základní instalaci..."
  DUMB_MODE=true
fi

# --- ADR-026 T3: --with-automation → deliver the €19 voice unlock ────────────
# ORTHOGONAL to --tiers: this only ADDS the amemor-voice-agent product (1
# station) to whatever product/tier selection is already resolved (default,
# bundle, or explicit --product/--tiers), giving the 4 matrix cells
# --tiers {lite|core,…} × --with-automation {off|on}. We compose by appending to
# the PRODUCT string BEFORE the normalizer below (which de-dupes, so a bundle
# that already ships voice — e.g. amemor-home — stays a single entry) rather than
# poking PRODUCTS_TO_INSTALL, so the existing alias/dedup/uaml-first ordering
# handles it uniformly. Install is DELIBERATELY unrestricted here: --with-automation
# only DELIVERS voice; the runtime voice entitlement gate is ADR-026 T7, not install.
# The SMART_INSTALL_WITH_AUTOMATION marker records that this install is
# automation-entitled (the €19 delivery) for downstream steps (David env, T7/T11).
if [[ "$WITH_AUTOMATION" == 1 ]]; then
  case ",${PRODUCT}," in
    *",amemor-voice-agent,"*|*",voice,"*|*",david,"*) : ;;  # already selected — no dupe
    *) PRODUCT="${PRODUCT},amemor-voice-agent" ;;
  esac
  export SMART_INSTALL_WITH_AUTOMATION=1
  # Suppress the notice under --print-plan so stdout stays pure copy-paste shell
  # (the plan header already reports the automation state). The compose logic
  # above STILL runs so voice steps appear in the emitted plan.
  if [[ "$PRINT_PLAN" != true ]]; then
    echo "🎙️  --with-automation: doručuji hlasový unlock (amemor-voice-agent, 1 stanice)."
    echo "    Delivering the €19 voice unlock (amemor-voice-agent, 1 station) — composes with --tiers."
  fi
fi

# --- Normalize product selection into PRODUCTS_TO_INSTALL[] ---
# Accepts a comma- OR space-separated list and friendly aliases.
#   all                     -> uaml + openclaw   (UNCHANGED)
#   voice|david             -> amemor-voice-agent (task #2363, lite-voice / David-first)
#   amemor-voice-agent      -> itself
#   <anything else>         -> itself (dir validated below)
# Cross-product ordering (task #2363 §3): uaml is force-ordered FIRST whenever
# it is selected, because amemor-voice-agent's P9 agent needs the UAML API
# (:8775) reachable. Single-value/`all` inputs stay byte-identical to before.
PRODUCTS_TO_INSTALL=()
_norm_products=()
for _p in ${PRODUCT//,/ }; do
  case "$_p" in
    all)                             _norm_products+=("uaml" "openclaw") ;;
    voice|david|amemor-voice-agent)  _norm_products+=("amemor-voice-agent") ;;
    *)                               _norm_products+=("$_p") ;;
  esac
done
# de-dupe (first occurrence wins), then force uaml first for dep ordering
_seen=" "
_dedup=()
for _p in "${_norm_products[@]}"; do
  [[ "$_seen" == *" $_p "* ]] && continue
  _seen+="$_p "
  _dedup+=("$_p")
done
for _p in "${_dedup[@]}"; do [[ "$_p" == "uaml" ]] && PRODUCTS_TO_INSTALL+=("uaml"); done
for _p in "${_dedup[@]}"; do [[ "$_p" == "uaml" ]] || PRODUCTS_TO_INSTALL+=("$_p"); done
# Soft warning: amemor-voice-agent needs UAML API (:8775) reachable at install
# time (avg-selfcheck/avg-health). Flag if selected without uaml in this run.
if printf '%s\n' "${PRODUCTS_TO_INSTALL[@]}" | grep -qx "amemor-voice-agent" \
   && ! printf '%s\n' "${PRODUCTS_TO_INSTALL[@]}" | grep -qx "uaml"; then
  echo "ℹ️  amemor-voice-agent vybrán bez uaml — P9 agent potřebuje běžící UAML API (:8775)."
  echo "    Pokud UAML na tomto stroji neběží, přidej uaml: --product uaml,amemor-voice-agent"
fi

# --- Validate product dirs ---
for p in "${PRODUCTS_TO_INSTALL[@]}"; do
  if [[ ! -d "$PRODUCTS_DIR/$p" ]]; then
    echo "❌ Neznámý produkt: $p"
    echo "   Dostupné: $(ls "$PRODUCTS_DIR" | tr '\n' ' ')"
    exit 1
  fi
done

export SMART_INSTALL_API_KEY="$API_KEY"
export SMART_INSTALL_DUMB="$DUMB_MODE"
export SMART_INSTALL_DIR="$SCRIPT_DIR"
export SMART_INSTALL_LOG="/tmp/smart-install-${PRODUCT}-$(date +%Y%m%d-%H%M%S).log"

# --- ADR-026 T4: --print-plan — emit the ordered install steps, then exit 0 ──
# The MANUAL, symmetric path to the automated install ("automation = we run these
# for you"). It resolves the SAME PRODUCTS_TO_INSTALL as a real install — incl.
# amemor-voice-agent ONLY when --with-automation delivered the €19 voice unlock —
# and applies the SAME RFC-035 tier filter (via print_install_plan in
# dumb-install.sh, which shares run_dumb_install's exact selection logic). So a
# free --print-plan (no --with-automation) emits open-core steps IDENTICAL to the
# public GitHub open-core install; voice steps appear only with the unlock.
# Placed BEFORE Fáze 1 detection / prompt_tiers: no detection, no interactive
# prompt, no services, no writes — pure, deterministic emission (side-effect free).
if [[ "$PRINT_PLAN" == true ]]; then
  # shellcheck source=lib/dumb-install.sh
  source "$LIB_DIR/dumb-install.sh"
  echo "# ============================================================"
  echo "# UAML smart-install — plán instalace (manuální, ke zkopírování)"
  echo "# UAML smart-install — install plan (manual path, copy-paste shell)"
  echo "#   produkty / products : ${PRODUCTS_TO_INSTALL[*]}"
  echo "#   tiery / tiers        : ${SMART_INSTALL_TIERS:-all}"
  if [[ "$WITH_AUTOMATION" == 1 ]]; then
    echo "#   automation (voice)   : on — €19 voice unlock delivered (amemor-voice-agent)"
  else
    echo "#   automation (voice)   : off — open-core only (voice needs --with-automation)"
  fi
  echo "# ============================================================"
  echo ""
  for CURRENT_PRODUCT in "${PRODUCTS_TO_INSTALL[@]}"; do
    export SMART_INSTALL_PRODUCT="$CURRENT_PRODUCT"
    export SMART_INSTALL_PRODUCT_DIR="$PRODUCTS_DIR/$CURRENT_PRODUCT"
    print_install_plan
  done
  exit 0
fi

# --- Fáze 1: Detekce prostředí (jednou pro celý run) ---
echo "🔍 Fáze 1: Detekce prostředí..."
source "$LIB_DIR/detect.sh"
run_detection

# --- Instalace produktů v pořadí ---

# ── RFC-035: interactive progressive-trust tier selection ─────────────────
prompt_tiers() {
  # Skip if tiers already chosen (--tiers), non-interactive, or not uaml.
  [[ -n "${SMART_INSTALL_TIERS:-}" ]] && return 0
  [[ "${PRODUCT:-uaml}" != "uaml" ]] && return 0
  [[ -t 0 ]] || return 0   # no TTY (curl|bash) -> leave unset = full install
  local tj="$PRODUCTS_DIR/uaml/tiers.json"
  [[ -f "$tj" ]] || return 0
  echo ""
  echo "  ── Které součásti nainstalovat? (RFC-035 progressive install) ──"
  echo "  core se instaluje vždy. Vyber další (mezerou oddělené), Enter = jen core:"
  python3 - "$tj" <<'PYEOF'
import json, sys
t = json.load(open(sys.argv[1]))["tiers"]
for k, m in t.items():
    if m.get("always"): continue
    trust = "  ⚠️ disk/shell/mail" if m.get("trust") == "high" else ""
    req = ",".join(m.get("requires", []))
    print(f"    {k:11} — {m.get('label_cs', k)}{trust}  (vyžaduje: {req or '-'})")
PYEOF
  echo ""
  read -r -p "  > tiery [core]: " _sel || _sel=""
  if [[ -z "$_sel" ]]; then
    export SMART_INSTALL_TIERS="core"
  else
    export SMART_INSTALL_TIERS="core,${_sel// /,}"
  fi
  # high-trust confirmation
  if [[ ",$SMART_INSTALL_TIERS," == *",localtools,"* || ",$SMART_INSTALL_TIERS," == *",security,"* ]]; then
    echo "  ⚠️  Vybrané tiery dávají agentovi přístup na disk/shell nebo odchozí mail."
    read -r -p "  Potvrdit? [y/N]: " _ok || _ok="n"
    [[ "$_ok" =~ ^[Yy] ]] || { echo "  Zrušeno — instaluji jen core."; export SMART_INSTALL_TIERS="core"; }
  fi
  echo "  → instaluji tiery: $SMART_INSTALL_TIERS"
}

prompt_tiers

for CURRENT_PRODUCT in "${PRODUCTS_TO_INSTALL[@]}"; do
  PRODUCT_DIR="$PRODUCTS_DIR/$CURRENT_PRODUCT"
  export SMART_INSTALL_PRODUCT="$CURRENT_PRODUCT"
  export SMART_INSTALL_PRODUCT_DIR="$PRODUCT_DIR"

  echo ""
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  echo "  📦 Instaluji: $CURRENT_PRODUCT"
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  echo ""

  # --- Pre-install: zkopíruj statické soubory produktu na cílový stroj ---
  if [[ "$CURRENT_PRODUCT" == "nemoclaw" ]]; then
    echo "   📂 Kopíruji NemoClaw soubory..."
    mkdir -p "$HOME/nemoclaw"
    for f in agent.py run.sh; do
      [[ -f "$PRODUCT_DIR/$f" ]] && cp "$PRODUCT_DIR/$f" "$HOME/nemoclaw/$f"
    done
    chmod +x "$HOME/nemoclaw/run.sh" 2>/dev/null || true
    echo "   ✅ agent.py a run.sh zkopírovány"
  fi

  # --- Pre-install: zkopíruj UAML source pokud je dostupný lokálně ---
  if [[ "$CURRENT_PRODUCT" == "uaml" ]]; then
    if [[ -d "$SCRIPT_DIR/../src/uaml" ]] && [[ ! -d "$HOME/src/uaml" ]]; then
      echo "   📂 Kopíruji UAML source z lokálního zdroje..."
      mkdir -p "$HOME/src"
      cp -r "$SCRIPT_DIR/../src/uaml" "$HOME/src/uaml"
    elif [[ -d "$HOME/src/uaml" ]]; then
      echo "   ✅ UAML source již přítomen (~~/src/uaml)"
    else
      echo "   ℹ️  UAML source není lokálně — AI bootstrap se pokusí stáhnout ze sítě"
    fi
  fi

  echo "🤖 Bootstrap: $CURRENT_PRODUCT..."
  if [[ "$DUMB_MODE" == true ]]; then
    source "$LIB_DIR/dumb-install.sh"
    run_dumb_install
  else
    source "$LIB_DIR/ai-bootstrap.sh"
    set +e
    run_ai_bootstrap
    set -e
  fi

  # --- Validace po instalaci produktu ---
  if [[ -f "$PRODUCT_DIR/validation.json" ]]; then
    echo ""
    echo "🔎 Validace: $CURRENT_PRODUCT..."
    source "$LIB_DIR/validation.sh"
    set +e
    run_validation
    _val_exit=$?
    set -e
    if [[ $_val_exit -ne 0 ]]; then
      echo "   ⚠️  Validace selhala — pokračuji, ale zkontroluj logy: $SMART_INSTALL_LOG"
    fi
  fi
done

# --- Post-install: univerzální UAML MCP integrace pro všechny agenty ---
echo ""
echo "🔗 UAML MCP integrace..."

# OpenClaw wiring is handled by a shipped, idempotent, user-agnostic script
# (detects the real OpenClaw user/home, enables memory plugins, repairs the
# session bridge + ACL across users). It also runs as a dumb_install step;
# this call covers the AI-bootstrap path. Safe to run more than once.
if [[ -f "$SCRIPT_DIR/assets/scripts/uaml_openclaw_integrate.sh" ]]; then
  UAML_SKILLS_SRC="$SCRIPT_DIR/assets/skills" \
    bash "$SCRIPT_DIR/assets/scripts/uaml_openclaw_integrate.sh" || true
fi

# Detekce UAML — API (8775) i MCP (8770)
_uaml_api_ok=false
_uaml_mcp_ok=false
curl -sf http://localhost:8775/api/v1/health >/dev/null 2>&1 && _uaml_api_ok=true
curl -sf -X POST http://localhost:8770/message \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"ping","params":{}}' >/dev/null 2>&1 && _uaml_mcp_ok=true

if [[ "$_uaml_api_ok" == false && "$_uaml_mcp_ok" == false ]]; then
  echo "   ℹ️  UAML neběží (API 8775, MCP 8770) — integrace přeskočena"
  echo "      Nainstaluj UAML (--product all) a spusť znovu pro propojení."
else
  [[ "$_uaml_api_ok" == true ]] && echo "   ✅ UAML API detekováno (port 8775)"
  [[ "$_uaml_mcp_ok" == true ]] && echo "   ✅ UAML MCP detekováno (port 8770)"

  # --- Zkopíruj UAML skills na cílový stroj ---
  # OpenClaw/NanoClaw: skill jako adresář v ~/.openclaw/workspace/skills/
  _SKILLS_SRC="$SCRIPT_DIR/assets/skills"
  _OC_SKILLS_DIR="$HOME/.openclaw/workspace/skills"

  if [[ -d "$_SKILLS_SRC" ]] && command -v openclaw >/dev/null 2>&1; then
    mkdir -p "$_OC_SKILLS_DIR"
    for skill_src in "$_SKILLS_SRC"/*/; do
      skill_name="$(basename "$skill_src")"
      skill_dst="$_OC_SKILLS_DIR/$skill_name"
      if [[ ! -d "$skill_dst" ]]; then
        cp -r "$skill_src" "$skill_dst"
        echo "   ✅ Skill nainstalován: $skill_name → $skill_dst"
      else
        # Aktualizuj SKILL.md pokud existuje
        [[ -f "$skill_src/SKILL.md" ]] && cp "$skill_src/SKILL.md" "$skill_dst/SKILL.md"
        echo "   🔄 Skill aktualizován: $skill_name"
      fi
    done
  fi

  # --- OpenClaw / NanoClaw: přidej uaml-memory skill do openclaw.json ---
  # Skills v OpenClaw jsou adresáře — nepotřebujeme měnit openclaw.json pro skills.
  # Ale ujistíme se že AGENTS.md má odkaz na UAML.
  if command -v openclaw >/dev/null 2>&1; then
    _OC_AGENTS_MD="$HOME/.openclaw/workspace/AGENTS.md"
    if [[ -f "$_OC_AGENTS_MD" ]] && ! grep -q 'UAML' "$_OC_AGENTS_MD" 2>/dev/null; then
      cat >> "$_OC_AGENTS_MD" <<'AGENTS_APPEND'

## UAML Memory Integration

UAML je nainstalován na tomto stroji. Používej pro ukládání a hledání znalostí:
- **API:** http://localhost:8775/api/v1/
- **MCP:** http://localhost:8770/message
- **Skill:** uaml-memory (vyhledávání), uaml-search (rychlé dotazy)
AGENTS_APPEND
      echo "   ✅ UAML reference přidána do AGENTS.md"
    fi

    openclaw gateway restart 2>/dev/null || true
    sleep 2
    echo "   ✅ OpenClaw/NanoClaw restarted s UAML skills"
  fi

  # --- NemoClaw: MCP URL do .env ---
  if [[ -f "$HOME/nemoclaw/.env" ]]; then
    grep -q 'UAML_MCP_URL' "$HOME/nemoclaw/.env" \
      || echo 'UAML_MCP_URL=http://localhost:8770/message' >> "$HOME/nemoclaw/.env"
    grep -q 'UAML_API_URL' "$HOME/nemoclaw/.env" \
      || echo 'UAML_API_URL=http://localhost:8775/api/v1' >> "$HOME/nemoclaw/.env"
    echo "   ✅ UAML URLs přidány do NemoClaw .env"
  fi

  # --- Hermes: MCP URL do config ---
  if [[ -f "$HOME/.hermes/config.json" ]]; then
    python3 - <<'PYEOF' 2>/dev/null && echo "   ✅ UAML přidáno do Hermes config" || true
import json, os
cfg_path = os.path.expanduser('~/.hermes/config.json')
with open(cfg_path) as f:
    cfg = json.load(f)
changed = False
if 'uaml_mcp_url' not in cfg:
    cfg['uaml_mcp_url'] = 'http://localhost:8770/message'
    changed = True
if 'uaml_api_url' not in cfg:
    cfg['uaml_api_url'] = 'http://localhost:8775/api/v1'
    changed = True
if changed:
    with open(cfg_path, 'w') as f:
        json.dump(cfg, f, indent=2)
    print('updated')
PYEOF
  fi

  # --- NanoClaw (Claude Code CLI): CLAUDE.md hint ---
  if [[ -d "$HOME/nanoclaw" ]]; then
    _NC_CLAUDE_MD="$HOME/nanoclaw/CLAUDE.md"
    if [[ ! -f "$_NC_CLAUDE_MD" ]] || ! grep -q 'UAML' "$_NC_CLAUDE_MD" 2>/dev/null; then
      cat >> "$_NC_CLAUDE_MD" <<'CLAUDE_APPEND'

## UAML Memory

UAML is installed on this machine. Use for knowledge storage and retrieval:
- API: http://localhost:8775/api/v1/
- MCP: http://localhost:8770/message
- Search: `curl -s "http://localhost:8775/api/v1/knowledge?q=QUERY&limit=5"`
CLAUDE_APPEND
      echo "   ✅ UAML hint přidán do NanoClaw CLAUDE.md"
    fi
  fi
fi

# --- License & trial flow (runs before agent select so trial registration
#     and install_ok telemetry happen even if a later phase aborts) ---
# Only meaningful when uaml is installed. Registers a 14-day trial (or
# activates --license-key) and reports install_ok with rich context.
_HAS_UAML=false
for p in "${PRODUCTS_TO_INSTALL[@]}"; do
  [[ "$p" == "uaml" ]] && _HAS_UAML=true
done

if [[ "$_HAS_UAML" == true ]]; then
  # Make uaml package importable from /opt/uaml-package without needing
  # `pip install` to have populated site-packages (it might not have).
  export PYTHONPATH="${PYTHONPATH:-}${PYTHONPATH:+:}/opt/uaml-package"
  if command -v python3 >/dev/null 2>&1 \
     && python3 -c "import uaml.install_flow" 2>/dev/null; then
    echo ""
    echo "🔑 Licence a trial..."

    # -s, not -f: we pre-create an EMPTY license.json below so the uaml user can
    # write it, so `-f` is true from the first second of every install and would
    # brand a never-registered node "existing" forever (found 2026-08-05).
    _existing=false
    [[ -s "/etc/uaml/license.json" || -s "$HOME/.uaml/license.json" ]] && _existing=true

    # Trial DB lives in /home/uaml/.uaml/installations.db (only the uaml
    # service user reads it from there). Run registration AS uaml so the
    # DB lands in the right place; license.json is host-wide /etc/uaml/.
    # /etc/uaml/license.json must be writable by uaml during registration,
    # so pre-create it owned by uaml; tighten to 644 root:uaml at the end.
    set +e
    if id uaml >/dev/null 2>&1; then
      install -d -o root -g uaml -m 750 /etc/uaml 2>/dev/null
      install -d -o uaml -g uaml -m 750 /home/uaml/.uaml 2>/dev/null
      [[ -f /etc/uaml/license.json ]] || install -m 660 -o uaml -g uaml /dev/null /etc/uaml/license.json
      runuser -u uaml -- env PYTHONPATH=/opt/uaml-package \
        UAML_CONFIG_DIR=/home/uaml/.uaml \
        UAML_LICENSE_FILE=/etc/uaml/license.json \
        LICENSE_EMAIL="${LICENSE_EMAIL}" python3 - <<'PYEOF'
import os
from uaml.install_flow import get_or_register_install, current_tier_info
info = get_or_register_install(email=os.environ.get("LICENSE_EMAIL",""))
tier = current_tier_info()
if not info.get("persisted", True):
    # Announcing a trial we could not write to disk is how a node ends up
    # silently on the community tier while the installer says otherwise.
    print(f"   ⚠️  Trial NOT recorded — {os.environ.get('UAML_LICENSE_FILE')} "
          f"is not writable. Node stays on the community tier.")
    print(f"      Fix the file's ownership, then: uaml license status")
elif info.get("just_registered"):
    print(f"   🎁 14-day trial started — all features unlocked.")
    print(f"      Trial expires: {info['trial_expires_at']}")
    print(f"      After expiry: community tier (free, limited).")
else:
    print(f"   ℹ️  Existing install — tier={tier['tier']}, "
          f"trial_active={tier['trial_active']}")
PYEOF
      # /etc/uaml/license.json was written by uaml — make it world-readable
      # but keep it writable by the uaml group so current_tier_info() can
      # rewrite the tier on trial→community transition.
      chmod 664 /etc/uaml/license.json 2>/dev/null
      chown root:uaml /etc/uaml/license.json 2>/dev/null
    else
      # Fallback: run as current user (legacy behaviour pre-§5)
      python3 - <<PYEOF
from uaml.install_flow import get_or_register_install, current_tier_info
info = get_or_register_install(email="${LICENSE_EMAIL}")
tier = current_tier_info()
if not info.get("persisted", True):
    print(f"   ⚠️  Trial NOT recorded — the install file is not writable. "
          f"Node stays on the community tier.")
elif info.get("just_registered"):
    print(f"   🎁 14-day trial started — all features unlocked.")
    print(f"      Trial expires: {info['trial_expires_at']}")
    print(f"      After expiry: community tier (free, limited).")
else:
    print(f"   ℹ️  Existing install — tier={tier['tier']}, "
          f"trial_active={tier['trial_active']}")
PYEOF
    fi
    if [[ -n "$LICENSE_KEY" ]]; then
      if id uaml >/dev/null 2>&1; then
        runuser -u uaml -- env PYTHONPATH=/opt/uaml-package \
          UAML_CONFIG_DIR=/home/uaml/.uaml \
          UAML_LICENSE_FILE=/etc/uaml/license.json \
          LICENSE_KEY="${LICENSE_KEY}" python3 - <<'PYEOF'
import os
from uaml.install_flow import activate_license_key
result = activate_license_key(os.environ["LICENSE_KEY"])
if result.get("success"):
    print(f"   ✅ License activated — tier: {result['tier']}")
else:
    print(f"   ⚠️  Activation failed: {result.get('error')}")
    print(f"      Trial will continue. Try again: uaml license activate {os.environ['LICENSE_KEY']}")
PYEOF
        chmod 664 /etc/uaml/license.json 2>/dev/null
        chown root:uaml /etc/uaml/license.json 2>/dev/null
      else
        python3 - <<PYEOF
from uaml.install_flow import activate_license_key
result = activate_license_key("${LICENSE_KEY}")
if result.get("success"):
    print(f"   ✅ License activated — tier: {result['tier']}")
else:
    print(f"   ⚠️  Activation failed: {result.get('error')}")
PYEOF
      fi
    fi
    set -e

    _extra_json=$(_TI_PRODUCT="$PRODUCT" _TI_EXISTING="$_existing" _TI_LICENSE="$LICENSE_KEY" \
      python3 -c '
import json, os
print(json.dumps({
    "product": os.environ.get("_TI_PRODUCT", ""),
    "existing_install": os.environ.get("_TI_EXISTING", "false") == "true",
    "license_activated": bool(os.environ.get("_TI_LICENSE", "")),
    "license_key": os.environ.get("_TI_LICENSE", ""),
}))' 2>/dev/null)
    telemetry_event "install_ok" "$_extra_json"
    _TELEMETRY_INSTALL_OK_FIRED=1
  else
    # uaml package not yet importable — fall back to a basic event
    telemetry_event "install_ok" "{\"product\":\"$PRODUCT\",\"flow\":\"no-uaml-import\"}"
    _TELEMETRY_INSTALL_OK_FIRED=1
  fi
fi

# --- Fáze 2: Výběr agenta (jen pro uaml/openclaw workflow) ---
# Standalone produkty (nanoclaw, hermes, nemoclaw) přeskočí výběr agenta.
# `--no-agent` flag (or non-TTY default) skips this entirely.
_NEEDS_AGENT_SELECT=false
if [[ "$NO_AGENT" != "true" ]]; then
  for p in "${PRODUCTS_TO_INSTALL[@]}"; do
    [[ "$p" == "uaml" || "$p" == "openclaw" ]] && _NEEDS_AGENT_SELECT=true
  done
fi

if [[ "$_NEEDS_AGENT_SELECT" == true ]]; then
  echo ""
  echo "🤖 Výběr agenta..."
  export SMART_INSTALL_PRODUCT="${PRODUCTS_TO_INSTALL[-1]}"  # poslední produkt
  export SMART_INSTALL_PRODUCT_DIR="$PRODUCTS_DIR/uaml"  # agent list je v uaml
  set +e
  source "$LIB_DIR/agent-select.sh"
  run_agent_select
  set -e
elif [[ "$NO_AGENT" == "true" ]]; then
  echo ""
  echo "ℹ️  --no-agent: výběr agenta přeskočen (UAML poběží samostatně)"
fi

# --- Fáze 3: Interaktivní průvodce ---
echo ""
echo "🎯 Nastavení a průvodce..."
source "$LIB_DIR/wizard.sh"
run_wizard

echo ""
echo "╔══════════════════════════════════════════════════════════╗"
echo "║  ✅ Instalace dokončena!                                 ║"
echo "╚══════════════════════════════════════════════════════════╝"
echo ""
for p in "${PRODUCTS_TO_INSTALL[@]}"; do
  case "$p" in
    uaml)      echo "  UAML API:       http://localhost:8775/api/v1/health" ;;
    openclaw)  echo "  OpenClaw:       openclaw gateway status" ;;
    nanoclaw)  echo "  NanoClaw:       cd ~/nanoclaw && claude  (pak /setup)" ;;
    nemoclaw)  echo "  NemoClaw:       ~/nemoclaw/run.sh" ;;
    hermes)    echo "  Hermes:         ollama run hermes3" ;;
    paperclip) echo "  Paperclip:      http://localhost:3100/  (loopback; reverse-proxy si nakonfiguruj sám)" ;;
  esac
done
if [[ "${SELECTED_AGENT_ID:-skip}" != "skip" ]]; then
  echo "  Agent:           $SELECTED_AGENT_ID"
fi
echo ""

# Where the box is and how to log in. Before 2026-08-05 the completion banner
# stopped at the localhost API URL above: an install that had just provisioned
# eight public names, eight LE certificates and a first-admin account told the
# customer none of it, and the generated password was lost with the step's
# discarded stdout. Guarded so a missing lib can never break completion (rule 8);
# uaml_access_summary is pure emission and stays silent about public URLs on a
# LAN-only install.
if [[ -r "$LIB_DIR/access-summary.sh" ]]; then
  # shellcheck source=/dev/null
  source "$LIB_DIR/access-summary.sh" 2>/dev/null || true
  if declare -F uaml_access_summary >/dev/null 2>&1; then
    uaml_access_summary || true
    echo ""
  fi
fi

# ADR-025 Invariant 3: reaching here means the install succeeded (the ERR trap
# exits on any earlier failure), so bind this node to its install-source now —
# the update pipeline (stage/check/parity) will pull from the same origin.
_persist_install_source "$CHANNEL" "$SMART_INSTALL_UPSTREAM"
echo ""

# --- Lite / lite-voice completion wiring (fleet [lite D7] #2368) ────────────
# WIRE POINT for the D3 David-first onboarding block + the D5 RFC-052 branding
# obligation. STRICTLY ADDITIVE + self-gating: on a full/core install this whole
# stanza is a no-op, so `--product uaml --tiers core` (the default) completion
# output stays BYTE-IDENTICAL. Lite context is decided by _smart_install_lite_active
# (defined in detect.sh, sourced in Fáze 1; SMART_INSTALL_LITE / SMART_INSTALL_TIERS
# markers), falling back to the env marker if detect.sh was unavailable.
# Every `source` is guarded so a missing lite lib can NEVER break completion
# (rule 8). lite_david_completion_message ALREADY emits the powered-by wordmark
# inside David's box, so to avoid a duplicated "powered by amemor-ai" line we call
# the standalone lite_branding_maybe_emit ONLY when David's block did not run
# (i.e. a lite install without amemor-voice-agent) — RFC-052 §6 branding still
# appears in EVERY lite install, exactly once.
_is_lite=false
if declare -F _smart_install_lite_active >/dev/null 2>&1; then
  _smart_install_lite_active && _is_lite=true
elif [[ "${SMART_INSTALL_LITE:-0}" == "1" ]]; then
  _is_lite=true
fi
if [[ "$_is_lite" == true ]]; then
  _david_ran=false
  # David-first onboarding — only when amemor-voice-agent was installed.
  if printf '%s\n' "${PRODUCTS_TO_INSTALL[@]}" | grep -qx 'amemor-voice-agent'; then
    if [[ -f "$LIB_DIR/lite-david.sh" ]]; then
      # shellcheck disable=SC1090
      source "$LIB_DIR/lite-david.sh" 2>/dev/null || true
      if declare -F lite_david_completion_message >/dev/null 2>&1; then
        lite_david_completion_message || true
        _david_ran=true
      fi
    fi
  fi
  # RFC-052 branding obligation — self-gates on the license (white-label may
  # omit). Emitted standalone only when David's box didn't already carry it.
  if [[ "$_david_ran" == false && -f "$LIB_DIR/lite-branding.sh" ]]; then
    # shellcheck disable=SC1090
    source "$LIB_DIR/lite-branding.sh" 2>/dev/null || true
    if declare -F lite_branding_maybe_emit >/dev/null 2>&1; then
      _pby="$(lite_branding_maybe_emit 2>/dev/null || true)"
      [[ -n "$_pby" ]] && echo "  $_pby"
    fi
  fi
fi

# v1.1.5: install_ok bezpodmínečně pro VŠECHNY produkty (was: only --product uaml).
# Hermes/openclaw/nemoclaw/nanoclaw left support blind on completion until now.
# Pokud _HAS_UAML blok už fajroval install_ok (s rich extras), tady neopakujeme.
if [[ "$_TELEMETRY_INSTALL_OK_FIRED" -eq 0 ]]; then
  telemetry_event "install_ok" \
    "{\"product\":\"$PRODUCT\",\"existing_install\":false,\"license_activated\":false,\"license_key\":\"${LICENSE_KEY:-}\",\"products\":\"${PRODUCTS_TO_INSTALL[*]}\"}"
  _TELEMETRY_INSTALL_OK_FIRED=1
fi

echo "  Log: $SMART_INSTALL_LOG"
