#!/usr/bin/env bash
set -euo pipefail

# Bash-only guard (avoid fish/zsh sourcing issues)
if [ -n "${FISH_VERSION:-}" ]; then
  echo "This script is bash-only. Run: bash install.sh"
  exit 1
fi

INSTALL_DIR_DEFAULT="/usr/local/bin"
FALLBACK_DIR="$HOME/.local/bin"

# Pinned ling-mem skill tag. Linggen depends on the ling-mem binary
# (encoder, dream mission, core inject, Memory_* tools), so install.sh
# brings it in alongside the engine. Override with LING_MEM_PIN to test
# a different tag / branch / SHA; the standalone wrapper at
# linggen.dev/install-ling-mem.sh can still pull a newer release on
# its own cadence.
LING_MEM_PIN_DEFAULT="ling-mem-v0.5.1"

LOCAL_PATH=""
VERSION_ARG=""

usage() {
  cat <<EOF
Usage: $(basename "$0") [--version <ver>] [--local-path <file://...tar.gz>]

Options:
  --version <ver>    Install a specific version. If omitted, installs latest.
  --local-path <url> Install from a local file URL or local path. Skips network fetch.

Environment:
  LING_MEM_PIN       Skill-repo ref for ling-mem (default: ${LING_MEM_PIN_DEFAULT}).

Installs the 'ling' CLI binary (Linggen AI coding agent) plus the
pinned 'ling-mem' memory backend.
EOF
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --version)
      VERSION_ARG="$2"; shift 2 ;;
    --local-path)
      LOCAL_PATH="$2"; shift 2 ;;
    -h|--help)
      usage; exit 0 ;;
    *)
      echo "Unknown option: $1" >&2
      usage; exit 1 ;;
  esac
done

detect_slug() {
  local os arch
  os="$(uname -s | tr '[:upper:]' '[:lower:]')"
  arch="$(uname -m)"
  case "$os" in
    darwin)
      case "$arch" in
        arm64|aarch64) echo "macos-aarch64" ;;
        x86_64|amd64)  echo "macos-x86_64" ;;
        *) echo "Unsupported architecture: $arch" >&2; exit 1 ;;
      esac
      ;;
    linux)
      case "$arch" in
        x86_64|amd64) echo "linux-x86_64" ;;
        arm64|aarch64) echo "linux-aarch64" ;;
        *) echo "Unsupported architecture: $arch" >&2; exit 1 ;;
      esac
      ;;
    *) echo "Unsupported OS: $os" >&2; exit 1 ;;
  esac
}

ensure_dir() {
  local dir="$1"
  if [ ! -d "$dir" ]; then
    mkdir -p "$dir" 2>/dev/null || return 1
  fi
  [ -w "$dir" ]
}

install_binary() {
  local tarball="$1" dest_dir="$2" binary_name="$3"
  local tmpdir binpath
  tmpdir="$(mktemp -d)"
  env -u TAR_OPTIONS tar -xzf "$tarball" -C "$tmpdir" >/dev/null
  binpath="$tmpdir/$binary_name"
  if [ ! -f "$binpath" ]; then
    echo "$binary_name binary not found in tarball" >&2
    rm -rf "$tmpdir"
    return 1
  fi

  cp "$binpath" "$dest_dir/"
  chmod +x "$dest_dir/$binary_name"
  rm -rf "$tmpdir"
}

download_tarball() {
  local url="$1" dest="$2"
  if [[ "$url" == file://* ]]; then
    cp "${url#file://}" "$dest"
  else
    if ! curl -fsSL "$url" -o "$dest"; then
      echo "Failed to download from $url" >&2
      echo "   This may be a temporary GitHub CDN issue. Please try again." >&2
      return 1
    fi
  fi
}

resolve_latest_tag() {
  local repo="$1"
  local tag
  tag=$(curl -s "https://api.github.com/repos/${repo}/releases/latest" | grep -o '"tag_name": "[^"]*' | cut -d'"' -f4 || echo "")
  echo "$tag"
}

resolve_download_url() {
  local repo="$1" binary="$2" slug="$3" version="$4"

  if [ "$version" = "latest" ]; then
    local tag
    tag=$(resolve_latest_tag "$repo")
    if [ -n "$tag" ]; then
      echo "   Latest ${binary} version: ${tag}" >&2
      echo "https://github.com/${repo}/releases/download/${tag}/${binary}-${slug}.tar.gz"
    else
      echo "   Falling back to /latest/download/ for ${binary}" >&2
      echo "https://github.com/${repo}/releases/latest/download/${binary}-${slug}.tar.gz"
    fi
  else
    echo "https://github.com/${repo}/releases/download/${version}/${binary}-${slug}.tar.gz"
  fi
}

check_path_conflicts() {
  local binary="$1" dest="$2" installed_version="$3"

  local current_in_path
  current_in_path=$(command -v "$binary" || echo "")
  if [ -n "$current_in_path" ]; then
    local path_version
    path_version=$("$binary" --version | awk '{print $2}' || echo "unknown")
    if [ "$current_in_path" != "$dest/$binary" ]; then
      echo ""
      echo "Warning: A different $binary binary was found in your PATH at $current_in_path"
      echo "   It reports version $path_version, while the new version is $installed_version at $dest/$binary"
      echo "   To use the new version, you may need to remove the old one or adjust your PATH."
    elif [ "$path_version" != "$installed_version" ]; then
      echo ""
      echo "Note: Your shell may have cached the old '$binary' binary location."
      echo "   Run 'hash -r' (bash) or 'rehash' (zsh) to refresh it."
    fi
  fi
}

remove_old_binary() {
  local binary_path="$1"
  if [ ! -f "$binary_path" ]; then
    return
  fi
  echo "   Removing old binary: $binary_path"
  rm -f "$binary_path" 2>/dev/null || true
}

cleanup_legacy_install() {
  local dest_dir="$1"
  local found=false

  # Old binary names from pre-consolidation Linggen
  local old_binaries=("linggen" "linggen-server")
  local search_dirs=("$dest_dir" "/usr/local/bin" "$HOME/.local/bin")

  for dir in "${search_dirs[@]}"; do
    for old in "${old_binaries[@]}"; do
      if [ -f "$dir/$old" ]; then
        if [ "$found" = "false" ]; then
          echo ""
          echo "Found legacy Linggen binaries (renamed: linggen -> ling, linggen-server -> ling-mem):"
          found=true
        fi
        remove_old_binary "$dir/$old"
      fi
    done
  done

  # Clean up old PID files
  local old_pids=("$HOME/.linggen/linggen-server.pid" "$HOME/.linggen/linggen-agent.pid")
  for pidfile in "${old_pids[@]}"; do
    if [ -f "$pidfile" ]; then
      # Try to stop the old process gracefully
      local pid
      pid=$(cat "$pidfile" 2>/dev/null || echo "")
      if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
        echo "   Stopping old process (PID $pid) from $pidfile"
        kill "$pid" 2>/dev/null || true
        sleep 1
      fi
      rm -f "$pidfile"
    fi
  done

  if [ "$found" = "true" ]; then
    echo "   Legacy cleanup complete."
    echo ""
  fi
}

install_ling_mem() {
  local pin="${LING_MEM_PIN:-$LING_MEM_PIN_DEFAULT}"
  local wrapper_url="https://linggen.dev/install-ling-mem.sh"
  local installer
  installer="$(mktemp)"

  echo ""
  echo "Installing ling-mem (memory backend, pinned ${pin})..."

  if ! curl -fsSL "$wrapper_url" -o "$installer"; then
    echo "Warning: couldn't fetch ${wrapper_url}." >&2
    echo "         Linggen is installed but memory features will be unavailable" >&2
    echo "         until you run:" >&2
    echo "             curl -fsSL ${wrapper_url} | bash" >&2
    rm -f "$installer"
    return 0
  fi

  # Two-step (download then exec) for the same supply-chain reason
  # install-ling-mem.sh itself avoids `curl | tar`: leaves the script
  # bytes on disk so they're inspectable between fetch and execution.
  # LINGGEN_SOURCE tags the chained install for ling-mem's telemetry so
  # we can tell wrapper-chained from direct installs apart.
  if ! LING_MEM_REPO_REF="$pin" LINGGEN_SOURCE="linggen-installer" \
       bash "$installer"; then
    echo "" >&2
    echo "Warning: ling-mem install step failed (see above)." >&2
    echo "         Linggen is installed; re-run ling-mem alone with:" >&2
    echo "             curl -fsSL ${wrapper_url} | bash" >&2
  fi
  rm -f "$installer"
}

main() {
  local slug dest="$INSTALL_DIR_DEFAULT"
  local version="${VERSION_ARG:-latest}"

  slug="$(detect_slug)"

  # Determine install directory
  if ! ensure_dir "$dest"; then
    echo "Using fallback install dir: $FALLBACK_DIR"
    dest="$FALLBACK_DIR"
    mkdir -p "$dest"
  fi

  # Clean up old linggen/linggen-server binaries
  cleanup_legacy_install "$dest"

  # --- Install ling ---
  echo "Installing ling (Linggen)..."

  local ling_url ling_tarball
  if [ -n "$LOCAL_PATH" ]; then
    if [[ "$LOCAL_PATH" == file://* ]]; then
      ling_url="$LOCAL_PATH"
    else
      ling_url="file://$LOCAL_PATH"
    fi
  else
    echo "Fetching latest release info from GitHub..." >&2
    ling_url=$(resolve_download_url "linggen/linggen" "ling" "$slug" "$version")
  fi

  echo "Downloading $ling_url"
  ling_tarball="$(mktemp)"
  download_tarball "$ling_url" "$ling_tarball"
  install_binary "$ling_tarball" "$dest" "ling"
  rm -f "$ling_tarball"

  local ling_version
  ling_version=$("$dest/ling" --version | awk '{print $2}' || echo "unknown")
  echo "Installed ling v${ling_version} to $dest/ling"
  check_path_conflicts "ling" "$dest" "$ling_version"

  # Telemetry source marker — read by the engine on its first launch (or
  # after a version change) to record how this machine was reached. Writes
  # to ~/.linggen so the engine can attribute installs to wrapper / brew /
  # cargo / sys-doctor (chained installers set LINGGEN_SOURCE).
  local linggen_via="${LINGGEN_SOURCE:-wrapper}"
  mkdir -p "$HOME/.linggen"
  {
    printf 'via=%s\n' "$linggen_via"
    printf 'installer_version=%s\n' "$ling_version"
    printf 'installed_at=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
  } > "$HOME/.linggen/.linggen-install-source"

  # --- Install ling-mem (pinned) ---
  # ling-mem is mandatory: the engine's encoder, dream mission, core
  # memory inject, and Memory_* tools all need it. We pull the standalone
  # wrapper here so install-ling-mem.sh stays the canonical entry for
  # non-Linggen hosts, and pin LING_MEM_REPO_REF so the Linggen install
  # owns the version on Linggen hosts. Soft-fails: a network blip
  # shouldn't take down the whole install — re-runnable separately.
  install_ling_mem

  # --- Post-install ---
  echo ""
  if [[ ":$PATH:" != *":$dest:"* ]]; then
    echo "Add to PATH if needed:"
    echo "    export PATH=\"$dest:\$PATH\""
    echo ""
  fi

  echo "Get started:"
  echo "    ling init         # Set up default config & download skills"
  echo "    ling              # Start agent (opens browser)"
  echo "    ling --web        # Start server (foreground)"
  echo "    ling status       # Show status"
  echo ""
  echo "Note: Linggen sends anonymous usage pings (install, engine.start, skill.open, …)"
  echo "      to help improve it. No content, no identity. Disable any time:"
  echo "        touch ~/.linggen/no-telemetry"
}

main "$@"
