#!/usr/bin/env bash
# CodeSync CLI installer.  Usage:  curl -fsSL https://devshub.ai/cli/install.sh | bash
set -euo pipefail

INDEX_URL="${CODESYNC_INDEX_URL:-https://app.devshub.ai/simple/}"
PROJECT_URL="${INDEX_URL%/}/codesync-cli/"
CODESYNC_PYTHON="${CODESYNC_PYTHON:-3.13}"

cyan() { printf '\033[36m%s\033[0m\n' "$*"; }
red()  { printf '\033[31m%s\033[0m\n' "$*" >&2; }
fail() { red "Error: $*"; exit 1; }

command -v uname >/dev/null 2>&1 || fail "uname is required to detect this platform."
command -v curl >/dev/null 2>&1 || fail "curl is required; install it and re-run this installer."

OS="$(uname -s 2>/dev/null || true)"
ARCH="$(uname -m 2>/dev/null || true)"
IS_ANDROID=0
if [ -n "${TERMUX_VERSION:-}" ] || [ "${PREFIX:-}" = "/data/data/com.termux/files/usr" ]; then
    IS_ANDROID=1
elif [ "$OS" = "Linux" ] && [ "$(uname -o 2>/dev/null || true)" = "Android" ]; then
    IS_ANDROID=1
fi

case "$ARCH" in
    x86_64|amd64) ARCH="x86_64" ;;
    aarch64|arm64) ARCH="aarch64" ;;
    *) fail "unsupported architecture '$ARCH'; supported: x86_64 and arm64." ;;
esac

if [ "$IS_ANDROID" -eq 1 ]; then
    [ "$ARCH" = "aarch64" ] || fail "unsupported Termux architecture '$ARCH'; only ARM64 is supported."
    if [ -z "${TERMUX_VERSION:-}" ] && [ "${PREFIX:-}" != "/data/data/com.termux/files/usr" ]; then
        fail "Android was detected outside Termux. Install Termux, then run this installer inside it."
    fi
    PLATFORM="termux-arm64"
elif [ "$OS" = "Linux" ]; then
    if command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then
        fail "musl Linux is not supported; CodeSync currently requires glibc Linux."
    fi
    PLATFORM="linux-$ARCH"
elif [ "$OS" = "Darwin" ]; then
    PLATFORM="macos-$ARCH"
else
    fail "unsupported operating system '$OS'; use install.ps1 on Windows."
fi

cyan "Installing CodeSync CLI for $PLATFORM…"
PROJECT_INDEX="$(curl -fsSL "$PROJECT_URL")" || \
    fail "could not read the private CodeSync index at $PROJECT_URL."

require_artifact() {
    pattern="$1"
    description="$2"
    printf '%s' "$PROJECT_INDEX" | grep -Eq "$pattern" || \
        fail "no compatible $description artifact is published at $PROJECT_URL."
}

if [ "$PLATFORM" = "termux-arm64" ]; then
    command -v pkg >/dev/null 2>&1 || \
        fail "Termux package manager 'pkg' is unavailable; cannot install CodeSync's native dependencies."

    # The encrypted wheel targets CPython 3.13. Termux's 'python' package tracks
    # the newest CPython (3.14 since 2026), so when it is not 3.13 the installer
    # uses the Termux User Repository's side-by-side python3.13 package.
    python_minor() {
        "$1" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null
    }
    TERMUX_PYTHON=""
    for candidate in python3.13 python; do
        if command -v "$candidate" >/dev/null 2>&1 && [ "$(python_minor "$candidate")" = "3.13" ]; then
            TERMUX_PYTHON="$candidate"
            break
        fi
    done
    if [ -z "$TERMUX_PYTHON" ]; then
        cyan "  • installing CPython 3.13 from the Termux User Repository…"
        pkg install -y tur-repo
        pkg install -y python3.13
        [ "$(python_minor python3.13)" = "3.13" ] || \
            fail "python3.13 from the Termux User Repository could not be installed or run."
        TERMUX_PYTHON=python3.13
    fi
    "$TERMUX_PYTHON" -m pip --version >/dev/null 2>&1 || "$TERMUX_PYTHON" -m ensurepip >/dev/null 2>&1 || \
        fail "pip is unavailable for Termux $TERMUX_PYTHON."

    # PyPI publishes no Android wheels for cryptography or the Rust-backed
    # dependencies (jiter, pydantic-core, ...), so they build on the device with
    # Termux's Rust and Clang toolchains against Termux's OpenSSL. Termux's
    # prebuilt python-cryptography only serves its own 'python', so it is used
    # when that interpreter is the 3.13 one.
    cyan "  • installing native Termux build dependencies…"
    pkg install -y rust clang make pkg-config binutils patchelf openssl libffi libyaml
    if [ "$TERMUX_PYTHON" = "python" ]; then
        pkg install -y python-cryptography
    fi
    export ANDROID_API_LEVEL=24
    # Source builds run scripts with '#!/bin/sh' shebangs (uvloop's bundled
    # libuv ./configure), which Android can only exec through termux-exec. The
    # Termux app preloads it; sessions such as ssh or proot may not.
    TERMUX_EXEC="$PREFIX/lib/libtermux-exec-ld-preload.so"
    if [ -f "$TERMUX_EXEC" ]; then
        case ":${LD_PRELOAD:-}:" in
            *":$TERMUX_EXEC:"*) ;;
            *) export LD_PRELOAD="$TERMUX_EXEC${LD_PRELOAD:+:$LD_PRELOAD}" ;;
        esac
    fi

    # The Android wheel is published with a composite platform tag
    # (cp313-cp313-linux_aarch64.android_24_arm64_v8a) so it is installable
    # by interpreters that accept either individual lane. Existing Termux
    # Python 3.13 releases advertise the legacy linux_aarch64 lane; future
    # releases may advertise the PEP 738 android_24_arm64_v8a lane.
    # Parse compressed wheel tags according to the wheel specification. A
    # platform field such as ``linux_aarch64.android_24_arm64_v8a`` expands to
    # two tags; the CPython/ABI prefix appears only once in the filename.
    TERMUX_WHEEL="$(CODESYNC_PROJECT_INDEX="$PROJECT_INDEX" "$TERMUX_PYTHON" - <<'PY'
import os
import re

try:
    from packaging.tags import sys_tags
    from packaging.utils import parse_wheel_filename
except ImportError:
    from pip._vendor.packaging.tags import sys_tags
    from pip._vendor.packaging.utils import parse_wheel_filename

supported = set(sys_tags())
filenames = re.findall(
    r"(codesync_cli-[A-Za-z0-9_.+!-]+\.whl)",
    os.environ["CODESYNC_PROJECT_INDEX"],
)
for filename in filenames:
    try:
        _, _, _, wheel_tags = parse_wheel_filename(filename)
    except ValueError:
        continue
    if supported.intersection(wheel_tags):
        print(filename)
        break
PY
)"
    [ -n "$TERMUX_WHEEL" ] || \
        fail "no published Termux ARM64 artifact matches the wheel tags accepted by Termux Python."

    VENV="${CODESYNC_TERMUX_VENV:-$HOME/.local/share/codesync/venv}"
    BIN_DIR="${CODESYNC_BIN_DIR:-$PREFIX/bin}"
    cyan "  • creating an isolated Termux Python environment…"
    # A venv made by another interpreter (an older CodeSync install on Termux's
    # own 'python') is replaced, never upgraded across CPython versions.
    if [ -x "$VENV/bin/python" ] && [ "$(python_minor "$VENV/bin/python")" != "3.13" ]; then
        rm -rf "$VENV"
    fi
    "$TERMUX_PYTHON" -m venv --system-site-packages "$VENV"
    "$VENV/bin/python" -m pip install --upgrade --index-url "$INDEX_URL" \
        --extra-index-url https://pypi.org/simple codesync-cli
    # The Android wheel carries a native PyArmor runtime. A republished build of
    # the same version (for example one relinked against Termux's libpython) is
    # invisible to pip's "already satisfied" shortcut, which would keep a broken
    # runtime installed forever, so reinstall this one wheel unconditionally.
    "$VENV/bin/python" -m pip install --force-reinstall --no-deps \
        --index-url "$INDEX_URL" codesync-cli
    # Android's linker only resolves Python symbols through an explicit
    # libpython NEEDED entry. PyO3 adds it for version-specific builds but not
    # for stable-ABI (abi3) ones such as cryptography and watchfiles, so link
    # every source-built extension that uses Python symbols without it.
    LIBPYTHON="libpython$(python_minor "$VENV/bin/python").so"
    find "$VENV" -name '*.so' | while IFS= read -r extension; do
        patchelf --print-needed "$extension" | grep -q '^libpython' && continue
        nm -D --undefined-only "$extension" 2>/dev/null | grep -qE ' _?Py' || continue
        patchelf --add-needed "$LIBPYTHON" "$extension"
    done
    "$VENV/bin/python" -c 'import codesync, cryptography.hazmat.bindings._rust' >/dev/null || \
        fail "the installed CodeSync build cannot run on this Termux Python; report the error above."
    mkdir -p "$BIN_DIR"
    ln -sf "$VENV/bin/codesync" "$BIN_DIR/codesync"
    ln -sf "$VENV/bin/cs" "$BIN_DIR/cs"
else
    case "$PLATFORM" in
        linux-x86_64)
            require_artifact 'cp313-cp313-(linux|manylinux[^"<]*)_x86_64\.whl' "Linux x64"
            ;;
        linux-aarch64)
            require_artifact 'cp313-cp313-(linux|manylinux[^"<]*)_aarch64\.whl' "Linux ARM64"
            ;;
        macos-x86_64)
            require_artifact 'cp313-cp313-macosx_[^"<]*_x86_64\.whl' "macOS Intel"
            ;;
        macos-aarch64)
            require_artifact 'cp313-cp313-macosx_[^"<]*_arm64\.whl' "macOS Apple Silicon"
            ;;
    esac

    if ! command -v uv >/dev/null 2>&1; then
        cyan "  • installing uv (manages the Python runtime for you)…"
        curl -fsSL https://astral.sh/uv/install.sh | sh
        export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
    fi
    command -v uv >/dev/null 2>&1 || \
        fail "uv installation failed; see https://docs.astral.sh/uv/ and re-run."

    cyan "  • downloading the CodeSync agent…"
    uv tool install --force --python "$CODESYNC_PYTHON" --index "$INDEX_URL" codesync-cli
    uv tool update-shell >/dev/null 2>&1 || true
fi

cyan ""
cyan "✓ CodeSync installed."
if command -v codesync >/dev/null 2>&1; then
    cyan "  Next:  codesync auth login   then   codesync"
else
    cyan "  Next: restart your shell (or add ~/.local/bin to PATH), then:  codesync auth login"
fi
