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

APP=traces
REPO="market-dot-dev/traces-binaries"
INSTALL_DIR_DEFAULT="$HOME/.traces/bin"

MUTED='\033[0;2m'
RED='\033[0;31m'
ORANGE='\033[38;5;214m'
NC='\033[0m'

requested_version="${TRACES_VERSION:-}"
install_dir="${TRACES_INSTALL_DIR:-}"
binary_path=""
no_modify_path=false
path_updated=false
path_updated_file=""
install_share_skill=false
share_skill_agent=""

usage() {
  cat <<EOF
Traces Installer

Usage: install.sh [options]

Options:
  -h, --help              Display this help message
  -v, --version <version> Install a specific version (e.g., 0.1.9)
  -b, --binary <path>     Install from a local binary instead of downloading
      --no-modify-path    Don't modify shell config files (.zshrc, .bashrc, etc.)
      --install-share-skill
                           Optionally run: npx skills add market-dot-dev/traces
      --share-skill-agent <agent-id>
                           Optional wrapper hint (claude-code, cursor, opencode, codex, gemini-cli)

Examples:
  curl -fsSL https://traces.com/install | bash
  curl -fsSL https://traces.com/install | bash -s -- --version 0.1.9
  curl -fsSL https://traces.com/install | bash -s -- --install-share-skill
  ./install.sh --binary /path/to/traces
EOF
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    -h|--help)
      usage
      exit 0
      ;;
    -v|--version)
      if [[ -n "${2:-}" ]]; then
        requested_version="$2"
        shift 2
      else
        echo -e "${RED}Error: --version requires a version argument${NC}"
        exit 1
      fi
      ;;
    -b|--binary)
      if [[ -n "${2:-}" ]]; then
        binary_path="$2"
        shift 2
      else
        echo -e "${RED}Error: --binary requires a path argument${NC}"
        exit 1
      fi
      ;;
    --no-modify-path)
      no_modify_path=true
      shift
      ;;
    --install-share-skill)
      install_share_skill=true
      shift
      ;;
    --share-skill-agent)
      if [[ -n "${2:-}" ]]; then
        share_skill_agent="$2"
        shift 2
      else
        echo -e "${RED}Error: --share-skill-agent requires an agent id${NC}"
        exit 1
      fi
      ;;
    *)
      echo -e "${ORANGE}Warning: Unknown option '$1'${NC}" >&2
      shift
      ;;
  esac
done

detect_os() {
  local raw_os
  raw_os="$(uname -s)"
  case "$raw_os" in
    Darwin*) echo "darwin" ;;
    Linux*) echo "linux" ;;
    MINGW*|MSYS*|CYGWIN*) echo "windows" ;;
    *) echo "unsupported" ;;
  esac
}

detect_arch() {
  local arch
  arch="$(uname -m)"
  case "$arch" in
    arm64|aarch64) echo "arm64" ;;
    x86_64|amd64) echo "x64" ;;
    *) echo "unsupported" ;;
  esac
}

latest_version() {
  curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \
    | grep -m 1 -E '"tag_name"' \
    | sed -nE 's/.*"tag_name":\s*"v?([^"]+)".*/\1/p' \
    | tr -d '[:space:]'
}

resolve_install_dir() {
  if [ -n "$install_dir" ]; then
    echo "$install_dir"
    return
  fi
  echo "$INSTALL_DIR_DEFAULT"
}

print_message() {
  local level=$1
  local message=$2
  local color=""

  case $level in
    info) color="${NC}" ;;
    warning) color="${NC}" ;;
    error) color="${RED}" ;;
  esac

  echo -e "${color}${message}${NC}"
}

install_from_binary() {
  print_message info "${MUTED}Installing${NC} ${APP} ${MUTED}from:${NC} ${binary_path}"
  if [ ! -f "$binary_path" ]; then
    print_message error "Binary not found at ${binary_path}"
    exit 1
  fi
  mkdir -p "$INSTALL_DIR"
  cp "$binary_path" "$INSTALL_DIR/${APP}"
  chmod 755 "$INSTALL_DIR/${APP}"
}

check_version() {
  if command -v "$APP" >/dev/null 2>&1; then
    local installed_version
    installed_version=$($APP --version 2>/dev/null || echo "")

    if [[ "$installed_version" != "$specific_version" ]]; then
      print_message info "${MUTED}Installed version: ${NC}$installed_version."
    else
      print_message info "${MUTED}Version ${NC}$specific_version${MUTED} already installed"
      exit 0
    fi
  fi
}

unbuffered_sed() {
  if echo | sed -u -e "" >/dev/null 2>&1; then
    sed -nu "$@"
  elif echo | sed -l -e "" >/dev/null 2>&1; then
    sed -nl "$@"
  else
    local pad="$(printf "\n%512s" "")"
    sed -ne "s/$/\\${pad}/" "$@"
  fi
}

print_progress() {
  local bytes="$1"
  local length="$2"
  [ "$length" -gt 0 ] || return 0

  local width=50
  local percent=$(( bytes * 100 / length ))
  [ "$percent" -gt 100 ] && percent=100
  local on=$(( percent * width / 100 ))
  local off=$(( width - on ))

  local filled
  local empty
  filled=$(printf "%*s" "$on" "")
  filled=${filled// /#}
  empty=$(printf "%*s" "$off" "")
  empty=${empty// /.}

  printf "\r${ORANGE}%s%s %3d%%${NC}" "$filled" "$empty" "$percent" >&4
}

download_with_progress() {
  local url="$1"
  local output="$2"

  if [ -t 2 ]; then
    exec 4>&2
  else
    exec 4>/dev/null
  fi

  local tmp_dir=${TMPDIR:-/tmp}
  local basename="${tmp_dir}/traces_install_$$"
  local tracefile="${basename}.trace"

  rm -f "$tracefile"
  mkfifo "$tracefile"

  printf "\033[?25l" >&4

  trap "trap - RETURN; rm -f \"$tracefile\"; printf '\033[?25h' >&4; exec 4>&-" RETURN

  (
    curl --trace-ascii "$tracefile" -s -L -o "$output" "$url"
  ) &
  local curl_pid=$!

  unbuffered_sed \
    -e 'y/ACDEGHLNORTV/acdeghlnortv/' \
    -e '/^0000: content-length:/p' \
    -e '/^<= recv data/p' \
    "$tracefile" | \
  {
    local length=0
    local bytes=0

    while IFS=" " read -r -a line; do
      [ "${#line[@]}" -lt 2 ] && continue
      local tag="${line[0]} ${line[1]}"

      if [ "$tag" = "0000: content-length:" ]; then
        length="${line[2]}"
        length=$(echo "$length" | tr -d '\r')
        bytes=0
      elif [ "$tag" = "<= recv" ]; then
        local size="${line[3]}"
        bytes=$(( bytes + size ))
        if [ "$length" -gt 0 ]; then
          print_progress "$bytes" "$length"
        fi
      fi
    done
  }

  wait $curl_pid
  local ret=$?
  echo "" >&4
  return $ret
}

download_and_install() {
  print_message info "\n${MUTED}Installing ${NC}${APP} ${MUTED}version: ${NC}$specific_version"
  local tmp_dir="${TMPDIR:-/tmp}/traces_install_$$"
  mkdir -p "$tmp_dir"

  if [[ "$os" == "windows" ]] || ! [ -t 2 ] || ! download_with_progress "$url" "$tmp_dir/$filename"; then
    curl -# -L -o "$tmp_dir/$filename" "$url"
  fi

  if [ "$os" = "linux" ]; then
    tar -xzf "$tmp_dir/$filename" -C "$tmp_dir"
  else
    unzip -q "$tmp_dir/$filename" -d "$tmp_dir"
  fi

  local binary_name="$APP"
  if [ "$os" = "windows" ]; then
    binary_name="${APP}.exe"
  fi

  if [ -f "$tmp_dir/$binary_name" ]; then
    mv "$tmp_dir/$binary_name" "$INSTALL_DIR/$binary_name"
  elif [ -f "$tmp_dir/${APP}-${os}-${arch}" ]; then
    mv "$tmp_dir/${APP}-${os}-${arch}" "$INSTALL_DIR/$binary_name"
  elif [ -f "$tmp_dir/${APP}-${os}-${arch}.exe" ]; then
    mv "$tmp_dir/${APP}-${os}-${arch}.exe" "$INSTALL_DIR/$binary_name"
  else
    print_message error "Installed archive did not include ${APP} binary"
    rm -rf "$tmp_dir"
    exit 1
  fi

  chmod 755 "$INSTALL_DIR/$binary_name"
  rm -rf "$tmp_dir"
}

add_to_path() {
  local config_file=$1
  local command=$2

  if grep -Fxq "$command" "$config_file"; then
    print_message info "Command already exists in $config_file, skipping write."
  elif [[ -w $config_file ]]; then
    echo -e "\n# ${APP}" >> "$config_file"
    echo "$command" >> "$config_file"
    print_message info "${MUTED}Added${NC} ${APP} ${MUTED}to PATH in${NC} $config_file"
    path_updated=true
    path_updated_file="$config_file"
  else
    print_message warning "Manually add the directory to $config_file (or similar):"
    print_message info "  $command"
  fi
}

validate_share_skill_agent() {
  local agent="$1"
  if [[ -z "$agent" ]]; then
    return
  fi

  case "$agent" in
    claude-code|cursor|opencode|codex|gemini-cli)
      ;;
    *)
      print_message error "Unsupported --share-skill-agent: $agent"
      print_message info "Allowed values: claude-code, cursor, opencode, codex, gemini-cli"
      exit 1
      ;;
  esac
}

install_share_skill_from_registry() {
  local skills_repo="market-dot-dev/traces"
  local install_cmd="npx --yes skills add ${skills_repo}"

  if ! command -v npx >/dev/null 2>&1; then
    print_message warning "Skipping optional skill install: npx is not available."
    print_message info "Run later: ${install_cmd}"
    return
  fi

  print_message info "Installing optional share skill: ${skills_repo}"
  if npx --yes skills add "${skills_repo}"; then
    print_message info "Installed share skill from ${skills_repo}"
  else
    print_message warning "Skill install failed (non-fatal). Retry manually:"
    print_message info "  ${install_cmd}"
  fi
}

print_share_wrapper_hint() {
  local agent="$1"
  if [[ -z "$agent" ]]; then
    return
  fi

  print_message info "Agent wrapper reference:"
  print_message info "  https://github.com/market-dot-dev/traces/blob/main/skills/share-to-traces/wrappers/${agent}.md"
}

main() {
  validate_share_skill_agent "$share_skill_agent"

  os="$(detect_os)"
  arch="$(detect_arch)"

  if [ "$os" = "unsupported" ] || [ "$arch" = "unsupported" ]; then
    print_message error "Unsupported platform: $(uname -s) $(uname -m)"
    exit 1
  fi

  if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then
    local rosetta_flag
    rosetta_flag=$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)
    if [ "$rosetta_flag" = "1" ]; then
      arch="arm64"
    fi
  fi

  local combo="$os-$arch"
  case "$combo" in
    linux-x64|linux-arm64|darwin-x64|darwin-arm64|windows-x64)
      ;;
    *)
      print_message error "Unsupported OS/Arch: $os/$arch"
      exit 1
      ;;
  esac

  archive_ext=".zip"
  if [ "$os" = "linux" ]; then
    archive_ext=".tar.gz"
  fi

  if [ "$os" = "linux" ]; then
    if ! command -v tar >/dev/null 2>&1; then
      print_message error "Error: 'tar' is required but not installed."
      exit 1
    fi
  else
    if ! command -v unzip >/dev/null 2>&1; then
      print_message error "Error: 'unzip' is required but not installed."
      exit 1
    fi
  fi

  filename="${APP}-${os}-${arch}${archive_ext}"
  INSTALL_DIR="$(resolve_install_dir)"
  mkdir -p "$INSTALL_DIR"

  if [ -n "$binary_path" ]; then
    specific_version="local"
  else
    if [ -z "$requested_version" ]; then
      url="https://github.com/${REPO}/releases/latest/download/$filename"
      specific_version=$(curl -s "https://api.github.com/repos/${REPO}/releases/latest" | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')

      if [[ $? -ne 0 || -z "$specific_version" ]]; then
        print_message error "Failed to fetch version information"
        exit 1
      fi
    else
      requested_version="${requested_version#v}"
      url="https://github.com/${REPO}/releases/download/v${requested_version}/$filename"
      specific_version=$requested_version

      http_status=$(curl -sI -o /dev/null -w "%{http_code}" "https://github.com/${REPO}/releases/tag/v${requested_version}")
      if [ "$http_status" = "404" ]; then
        print_message error "Error: Release v${requested_version} not found"
        print_message info "${MUTED}Available releases: https://github.com/${REPO}/releases${NC}"
        exit 1
      fi
    fi
  fi

  if [ -n "$binary_path" ]; then
    install_from_binary
  else
    check_version
    download_and_install
  fi

  if [[ "$no_modify_path" != "true" ]]; then
    local config_files
    local config_file=""
    local shell_name

    shell_name=$(basename "$SHELL")
    XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}

    case $shell_name in
      fish)
        config_files="$HOME/.config/fish/config.fish"
      ;;
      zsh)
        config_files="${ZDOTDIR:-$HOME}/.zshrc ${ZDOTDIR:-$HOME}/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv"
      ;;
      bash)
        config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
      ;;
      *)
        config_files="$HOME/.bashrc $HOME/.bash_profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
      ;;
    esac

    for file in $config_files; do
      if [[ -f $file ]]; then
        config_file=$file
        break
      fi
    done

    if [[ -z $config_file ]]; then
      print_message warning "No config file found for $shell_name. You may need to add to PATH manually:"
      print_message info "  export PATH=$INSTALL_DIR:\$PATH"
    elif [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
      case $shell_name in
        fish)
          add_to_path "$config_file" "fish_add_path $INSTALL_DIR"
        ;;
        *)
          add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
        ;;
      esac
    fi
  fi

  if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" = "true" ]; then
    echo "$INSTALL_DIR" >> $GITHUB_PATH
    print_message info "Added $INSTALL_DIR to \$GITHUB_PATH"
  fi

  if [[ "$install_share_skill" = "true" ]]; then
    install_share_skill_from_registry
  fi

  print_share_wrapper_hint "$share_skill_agent"

  print_message info "Installed ${APP} to ${INSTALL_DIR}/${APP}"
  if [ "$path_updated" = "true" ] && [ -n "$path_updated_file" ]; then
    print_message info "Restart your shell or run: source $path_updated_file"
  fi
  print_message info "Run: ${APP} --version"
}

main "$@"
